mirror of https://github.com/certusone/oyster.git
Working sol -> eth wormhole (#87)
* Workin sol -> eth wormhole * remove console * fixed selecting same chains and showing same balances
This commit is contained in:
parent
dce6790f4e
commit
2fddc4d024
|
@ -28,24 +28,6 @@ export function Input(props: {
|
|||
}) {
|
||||
const { connected } = useWallet();
|
||||
const [lastAmount, setLastAmount] = useState<string>('');
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const [balance, setBalance] = useState<number>(0);
|
||||
const mint = useMint(props.asset?.startsWith('0x') ? '' : props.asset);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.chain === ASSET_CHAIN.Solana) {
|
||||
const currentAccount = userAccounts?.find(
|
||||
a => a.info.mint.toBase58() === props.asset,
|
||||
);
|
||||
if (currentAccount && mint) {
|
||||
setBalance(
|
||||
currentAccount.info.amount.toNumber() / Math.pow(10, mint.decimals),
|
||||
);
|
||||
} else {
|
||||
setBalance(0);
|
||||
}
|
||||
}
|
||||
}, [props.asset, props.chain, userAccounts, mint]);
|
||||
|
||||
return (
|
||||
<div className={`dashed-input-container ${props.className}`}>
|
||||
|
@ -53,25 +35,14 @@ export function Input(props: {
|
|||
<div className="input-chain">
|
||||
<TokenChain chain={props.chain} className={'input-icon'} />
|
||||
{chainToName(props.chain)}
|
||||
{props.chain !== ASSET_CHAIN.Solana ? (
|
||||
typeof props.balance === 'number' && (
|
||||
<div
|
||||
className="balance"
|
||||
onClick={() =>
|
||||
props.onInputChange && props.onInputChange(props.balance)
|
||||
}
|
||||
>
|
||||
{props.balance.toFixed(10)}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
className="balance"
|
||||
onClick={() => props.onInputChange && props.onInputChange(balance)}
|
||||
>
|
||||
{balance.toFixed(10)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="balance"
|
||||
onClick={() =>
|
||||
props.onInputChange && props.onInputChange(props.balance)
|
||||
}
|
||||
>
|
||||
{props.balance}
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-container">
|
||||
<NumericInput
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Table } from 'antd';
|
||||
import React from 'react';
|
||||
import { Button, Table, Tabs, notification } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import './index.less';
|
||||
|
||||
|
@ -12,22 +12,39 @@ import { toChainSymbol } from '../../contexts/chainPair';
|
|||
import {
|
||||
formatUSD,
|
||||
shortenAddress,
|
||||
EtherscanLink,
|
||||
ExplorerLink,
|
||||
Identicon,
|
||||
programIds,
|
||||
} from '@oyster/common';
|
||||
import { useWormholeTransactions } from '../../hooks/useWormholeTransactions';
|
||||
import { ASSET_CHAIN } from '../../utils/assets';
|
||||
import { TokenChain } from '../TokenDisplay/tokenChain';
|
||||
import bs58 from 'bs58';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { typeToIcon } from '../Transfer';
|
||||
import { ProgressUpdate } from '../../models/bridge';
|
||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
||||
import { useEthereum } from '../../contexts';
|
||||
import { useBridge } from '../../contexts/bridge';
|
||||
|
||||
TimeAgo.addDefaultLocale(en);
|
||||
const timeAgo = new TimeAgo('en-US');
|
||||
|
||||
export const RecentTransactionsTable = () => {
|
||||
const { loading: loadingTransfers, transfers } = useWormholeTransactions();
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const columns = [
|
||||
export const RecentTransactionsTable = (props: {
|
||||
showUserTransactions?: boolean;
|
||||
}) => {
|
||||
const {
|
||||
loading: loadingTransfers,
|
||||
transfers,
|
||||
userTransfers,
|
||||
} = useWormholeTransactions();
|
||||
const { provider } = useEthereum();
|
||||
const bridge = useBridge();
|
||||
|
||||
const [completedVAAs, setCompletedVAAs] = useState<Array<string>>([]);
|
||||
|
||||
const baseColumns = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'logo',
|
||||
|
@ -68,31 +85,29 @@ export const RecentTransactionsTable = () => {
|
|||
dataIndex: 'symbol',
|
||||
key: 'symbol',
|
||||
render(text: string, record: any) {
|
||||
const urlText = record.symbol || record.address;
|
||||
return {
|
||||
props: { style: {} },
|
||||
children: record.symbol ? (
|
||||
<Link
|
||||
to={`/move?from=${toChainSymbol(record.chain)}&token=${
|
||||
record.symbol
|
||||
}`}
|
||||
>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
||||
{record.symbol}
|
||||
</span>
|
||||
</Link>
|
||||
) : record.lockup.assetChain === ASSET_CHAIN.Solana ? (
|
||||
<ExplorerLink
|
||||
address={record.address}
|
||||
length={5}
|
||||
type={'address'}
|
||||
/>
|
||||
) : (
|
||||
<EtherscanLink
|
||||
address={record.address}
|
||||
type={'address'}
|
||||
length={5}
|
||||
/>
|
||||
),
|
||||
children:
|
||||
record.lockup.assetChain === ASSET_CHAIN.Solana ? (
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${record.address}`}
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
target="_blank"
|
||||
title={urlText}
|
||||
>
|
||||
{record.symbol || shortenAddress(urlText, 5)}
|
||||
</a>
|
||||
) : (
|
||||
<a
|
||||
href={`https://etherscan.io/address/${record.address}`}
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
target="_blank"
|
||||
title={urlText}
|
||||
>
|
||||
{record.symbol || shortenAddress(urlText, 5)}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -138,6 +153,9 @@ export const RecentTransactionsTable = () => {
|
|||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
const columns = [
|
||||
...baseColumns,
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'status',
|
||||
|
@ -154,20 +172,203 @@ export const RecentTransactionsTable = () => {
|
|||
},
|
||||
},
|
||||
];
|
||||
|
||||
const userColumns = [
|
||||
...baseColumns,
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render(text: string, record: any) {
|
||||
const status =
|
||||
completedVAAs.indexOf(record.txhash) > 0
|
||||
? 'Completed'
|
||||
: record.status;
|
||||
return {
|
||||
props: { style: {} },
|
||||
children: (
|
||||
<>
|
||||
<span className={`${record.status?.toLowerCase()}`}>
|
||||
{status}
|
||||
</span>
|
||||
{status === 'Failed' ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
const NotificationContent = () => {
|
||||
const [activeSteps, setActiveSteps] = useState<
|
||||
ProgressUpdate[]
|
||||
>([]);
|
||||
let counter = 0;
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const signer = provider?.getSigner();
|
||||
if (!signer || !bridge) {
|
||||
setActiveSteps([
|
||||
...activeSteps,
|
||||
{
|
||||
message: 'Connect your Ethereum Wallet',
|
||||
type: 'error',
|
||||
group: 'error',
|
||||
step: counter++,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
const lockup = record.lockup;
|
||||
let vaa = lockup.vaa;
|
||||
for (let i = vaa.length; i > 0; i--) {
|
||||
if (vaa[i] == 0xff) {
|
||||
vaa = vaa.slice(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let signatures = await bridge.fetchSignatureStatus(
|
||||
lockup.signatureAccount,
|
||||
);
|
||||
let sigData = Buffer.of(
|
||||
...signatures.reduce(
|
||||
(previousValue, currentValue) => {
|
||||
previousValue.push(currentValue.index);
|
||||
previousValue.push(...currentValue.signature);
|
||||
|
||||
return previousValue;
|
||||
},
|
||||
new Array<number>(),
|
||||
),
|
||||
);
|
||||
|
||||
vaa = Buffer.concat([
|
||||
vaa.slice(0, 5),
|
||||
Buffer.of(signatures.length),
|
||||
sigData,
|
||||
vaa.slice(6),
|
||||
]);
|
||||
let wh = WormholeFactory.connect(
|
||||
programIds().wormhole.bridge,
|
||||
signer,
|
||||
);
|
||||
let group = 'Finalizing transfer';
|
||||
setActiveSteps([
|
||||
...activeSteps,
|
||||
{
|
||||
message: 'Sign the claim...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
},
|
||||
]);
|
||||
let tx = await wh.submitVAA(vaa);
|
||||
setActiveSteps([
|
||||
...activeSteps,
|
||||
{
|
||||
message:
|
||||
'Waiting for tokens unlock to be mined...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
},
|
||||
]);
|
||||
await tx.wait(1);
|
||||
setActiveSteps([
|
||||
...activeSteps,
|
||||
{
|
||||
message: 'Execution of VAA succeeded',
|
||||
type: 'done',
|
||||
group,
|
||||
step: counter++,
|
||||
},
|
||||
]);
|
||||
}
|
||||
})();
|
||||
}, [setActiveSteps]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'left',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
let group = '';
|
||||
return activeSteps.map((step, i) => {
|
||||
let prevGroup = group;
|
||||
group = step.group;
|
||||
let newGroup = prevGroup !== group;
|
||||
return (
|
||||
<>
|
||||
{newGroup && <span>{group}</span>}
|
||||
<span style={{ marginLeft: 15 }}>
|
||||
{typeToIcon(
|
||||
step.type,
|
||||
activeSteps.length - 1 === i,
|
||||
)}{' '}
|
||||
{step.message}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
});
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
notification.open({
|
||||
message: '',
|
||||
duration: 0,
|
||||
placement: 'bottomLeft',
|
||||
description: <NotificationContent />,
|
||||
className: 'custom-class',
|
||||
style: {
|
||||
width: 500,
|
||||
},
|
||||
});
|
||||
}}
|
||||
shape="circle"
|
||||
size="large"
|
||||
type="text"
|
||||
style={{ color: '#547595', fontSize: '18px' }}
|
||||
title={'Retry Transaction'}
|
||||
icon={<SyncOutlined />}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div id={'recent-tx-container'}>
|
||||
<div className={'home-subtitle'} style={{ marginBottom: '70px' }}>
|
||||
Recent Transactions
|
||||
Transactions
|
||||
</div>
|
||||
<Table
|
||||
scroll={{
|
||||
scrollToFirstRowOnChange: false,
|
||||
x: 900,
|
||||
}}
|
||||
dataSource={transfers.sort((a, b) => b.date - a.date)}
|
||||
columns={columns}
|
||||
loading={loadingTransfers}
|
||||
/>
|
||||
<Tabs defaultActiveKey="1" centered>
|
||||
<TabPane tab="Recent Transactions" key="1">
|
||||
<Table
|
||||
scroll={{
|
||||
scrollToFirstRowOnChange: false,
|
||||
x: 900,
|
||||
}}
|
||||
dataSource={transfers.sort((a, b) => b.date - a.date)}
|
||||
columns={columns}
|
||||
loading={loadingTransfers}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab="My Transactions" key="2">
|
||||
<Table
|
||||
scroll={{
|
||||
scrollToFirstRowOnChange: false,
|
||||
x: 900,
|
||||
}}
|
||||
dataSource={userTransfers.sort((a, b) => b.date - a.date)}
|
||||
columns={userColumns}
|
||||
loading={loadingTransfers}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ import { useTokenChainPairState } from '../../contexts/chainPair';
|
|||
import { LABELS } from '../../constants';
|
||||
import { useCorrectNetwork } from '../../hooks/useCorrectNetwork';
|
||||
import { RecentTransactionsTable } from '../RecentTransactionsTable';
|
||||
import { useBridge } from '../../contexts/bridge';
|
||||
|
||||
const { useConnection } = contexts.Connection;
|
||||
const { useWallet } = contexts.Wallet;
|
||||
|
@ -40,6 +41,7 @@ export const typeToIcon = (type: string, isLast: boolean) => {
|
|||
|
||||
export const Transfer = () => {
|
||||
const connection = useConnection();
|
||||
const bridge = useBridge();
|
||||
const { wallet, connected } = useWallet();
|
||||
const { provider, tokenMap } = useEthereum();
|
||||
const hasCorrespondingNetworks = useCorrectNetwork();
|
||||
|
@ -50,6 +52,7 @@ export const Transfer = () => {
|
|||
setMintAddress,
|
||||
setLastTypedAccount,
|
||||
} = useTokenChainPairState();
|
||||
|
||||
const [request, setRequest] = useState<TransferRequest>({
|
||||
from: ASSET_CHAIN.Ethereum,
|
||||
to: ASSET_CHAIN.Solana,
|
||||
|
@ -77,7 +80,7 @@ export const Transfer = () => {
|
|||
to: B.chain,
|
||||
info: A.info,
|
||||
});
|
||||
}, [A, B, mintAddress]);
|
||||
}, [A, B, mintAddress, A.info]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -92,7 +95,9 @@ export const Transfer = () => {
|
|||
onChain={(chain: ASSET_CHAIN) => {
|
||||
const from = A.chain;
|
||||
A.setChain(chain);
|
||||
B.setChain(from);
|
||||
if (B.chain === chain) {
|
||||
B.setChain(from);
|
||||
}
|
||||
}}
|
||||
onInputChange={amount => {
|
||||
setLastTypedAccount(A.chain);
|
||||
|
@ -126,7 +131,9 @@ export const Transfer = () => {
|
|||
onChain={(chain: ASSET_CHAIN) => {
|
||||
const to = B.chain;
|
||||
B.setChain(chain);
|
||||
A.setChain(to);
|
||||
if (A.chain === chain) {
|
||||
A.setChain(to);
|
||||
}
|
||||
}}
|
||||
onInputChange={amount => {
|
||||
setLastTypedAccount(B.chain);
|
||||
|
@ -171,6 +178,7 @@ export const Transfer = () => {
|
|||
|
||||
setActiveSteps(steps);
|
||||
},
|
||||
bridge,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ import {
|
|||
TransferRequestInfo,
|
||||
wrappedAssetMintKey,
|
||||
} from '../models/bridge';
|
||||
import { useBridge } from './bridge';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export interface TokenChainContextState {
|
||||
info?: TransferRequestInfo;
|
||||
|
||||
|
@ -91,11 +94,22 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
const [chain, setChain] = useState(ASSET_CHAIN.Ethereum);
|
||||
const [info, setInfo] = useState<TransferRequestInfo>();
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const bridge = useBridge();
|
||||
|
||||
const { provider, tokens: ethTokens } = useEthereum();
|
||||
const { tokens: solTokens } = useConnectionConfig();
|
||||
const connection = useConnection();
|
||||
|
||||
const defaultCoinInfo = {
|
||||
address: '',
|
||||
name: '',
|
||||
balance: new BigNumber(0),
|
||||
decimals: 0,
|
||||
allowance: new BigNumber(0),
|
||||
isWrapped: false,
|
||||
chainID: 0,
|
||||
assetAddress: new Buffer(0),
|
||||
mint: '',
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!provider || !connection) {
|
||||
return;
|
||||
|
@ -111,23 +125,44 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
// sol asset on sol chain
|
||||
|
||||
//let ethAddress: string = '';
|
||||
if (solToken) {
|
||||
// let signer = provider.getSigner();
|
||||
// let e = WrappedAssetFactory.connect(asset, provider);
|
||||
// let addr = await signer.getAddress();
|
||||
// let decimals = await e.decimals();
|
||||
// let symbol = await e.symbol();
|
||||
|
||||
// console.log({ chain, solToken, ethToken });
|
||||
if (chain === ASSET_CHAIN.Solana) {
|
||||
if (!solToken) {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
// TODO: checked if mint is wrapped mint from eth...
|
||||
const currentAccount = userAccounts?.find(
|
||||
a => a.info.mint.toBase58() === solToken.address,
|
||||
);
|
||||
const assetMeta = await bridge?.fetchAssetMeta(
|
||||
new PublicKey(solToken.address),
|
||||
);
|
||||
|
||||
const accounts = userAccounts
|
||||
.filter(a => a.info.mint.toBase58() === solToken.address)
|
||||
.sort((a, b) => a.info.amount.toNumber() - b.info.amount.toNumber());
|
||||
if (!assetMeta || !currentAccount) {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(accounts);
|
||||
let info = {
|
||||
address: currentAccount.pubkey.toBase58(),
|
||||
name: solToken.symbol,
|
||||
balance: new BigNumber(currentAccount?.info.amount.toNumber() || 0),
|
||||
allowance: new BigNumber(0),
|
||||
decimals: solToken.decimals,
|
||||
isWrapped: assetMeta.chain != ASSET_CHAIN.Solana,
|
||||
chainID: assetMeta.chain,
|
||||
assetAddress: assetMeta.address,
|
||||
mint: solToken.address,
|
||||
};
|
||||
setInfo(info);
|
||||
}
|
||||
|
||||
if (ethToken) {
|
||||
if (chain === ASSET_CHAIN.Ethereum) {
|
||||
if (!ethToken) {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
let signer = provider.getSigner();
|
||||
let e = WrappedAssetFactory.connect(mintAddress, provider);
|
||||
let addr = await signer.getAddress();
|
||||
|
@ -170,12 +205,9 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
address: info.assetAddress,
|
||||
chain: info.chainID,
|
||||
});
|
||||
|
||||
console.log(mint.toBase58());
|
||||
}
|
||||
|
||||
console.log(info);
|
||||
|
||||
// console.log({ info });
|
||||
setInfo(info);
|
||||
}
|
||||
})();
|
||||
|
@ -190,16 +222,13 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
userAccounts,
|
||||
]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
amount: amount,
|
||||
setAmount: setAmount,
|
||||
chain: chain,
|
||||
setChain: setChain,
|
||||
info,
|
||||
}),
|
||||
[amount, setAmount, chain, setChain],
|
||||
);
|
||||
return {
|
||||
amount: amount,
|
||||
setAmount: setAmount,
|
||||
chain: chain,
|
||||
setChain: setChain,
|
||||
info,
|
||||
};
|
||||
};
|
||||
|
||||
export function TokenChainPairProvider({ children = null as any }) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import assert from 'assert';
|
|||
// @ts-ignore
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import * as bs58 from 'bs58';
|
||||
import { AssetMeta } from '../models/bridge';
|
||||
|
||||
export interface Lockup {
|
||||
lockupAddress: PublicKey;
|
||||
|
@ -71,7 +72,40 @@ class SolanaBridge {
|
|||
data,
|
||||
});
|
||||
}
|
||||
// fetchAssetMeta fetches the AssetMeta for an SPL token
|
||||
async fetchAssetMeta(mint: PublicKey): Promise<AssetMeta> {
|
||||
// @ts-ignore
|
||||
let configKey = await this.getConfigKey();
|
||||
let seeds: Array<Buffer> = [
|
||||
Buffer.from('meta'),
|
||||
configKey.toBuffer(),
|
||||
mint.toBuffer(),
|
||||
];
|
||||
// @ts-ignore
|
||||
let metaKey = (
|
||||
await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID)
|
||||
)[0];
|
||||
let metaInfo = await this.connection.getAccountInfo(metaKey);
|
||||
if (metaInfo == null || metaInfo.lamports == 0) {
|
||||
return {
|
||||
address: mint.toBuffer(),
|
||||
chain: CHAIN_ID_SOLANA,
|
||||
decimals: 0,
|
||||
};
|
||||
} else {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('assetChain'),
|
||||
BufferLayout.blob(32, 'assetAddress'),
|
||||
]);
|
||||
let wrappedMeta = dataLayout.decode(metaInfo?.data);
|
||||
|
||||
return {
|
||||
address: wrappedMeta.assetAddress,
|
||||
chain: wrappedMeta.assetChain,
|
||||
decimals: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
// fetchSignatureStatus fetches the signatures for a VAA
|
||||
async fetchSignatureStatus(signatureStatus: PublicKey): Promise<Signature[]> {
|
||||
let signatureInfo = await this.connection.getAccountInfo(
|
||||
|
|
|
@ -4,13 +4,17 @@ import {
|
|||
useConnectionConfig,
|
||||
programIds,
|
||||
notify,
|
||||
useWallet,
|
||||
} from '@oyster/common';
|
||||
import { WORMHOLE_PROGRAM_ID, POSTVAA_INSTRUCTION } from '../utils/ids';
|
||||
import {
|
||||
WORMHOLE_PROGRAM_ID,
|
||||
POSTVAA_INSTRUCTION,
|
||||
TRANSFER_ASSETS_OUT_INSTRUCTION,
|
||||
} from '../utils/ids';
|
||||
import { ASSET_CHAIN } from '../utils/assets';
|
||||
import { useEthereum } from '../contexts';
|
||||
import {
|
||||
Connection,
|
||||
ParsedInstruction,
|
||||
PartiallyDecodedInstruction,
|
||||
PublicKey,
|
||||
} from '@solana/web3.js';
|
||||
|
@ -31,11 +35,6 @@ import { ethers } from 'ethers';
|
|||
import { useBridge } from '../contexts/bridge';
|
||||
import { SolanaBridge } from '../core';
|
||||
|
||||
interface ParsedData {
|
||||
info: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
type WrappedTransferMeta = {
|
||||
chain: number;
|
||||
decimals: number;
|
||||
|
@ -53,6 +52,7 @@ type WrappedTransferMeta = {
|
|||
txhash?: string;
|
||||
date: number; // timestamp
|
||||
status?: string;
|
||||
owner?: string;
|
||||
lockup?: any;
|
||||
vaa?: any;
|
||||
};
|
||||
|
@ -105,7 +105,9 @@ const queryWrappedMetaTransactions = async (
|
|||
|
||||
const dec = new BN(10).pow(new BN(metaTransfer.assetDecimals));
|
||||
const rawAmount = new BN(metaTransfer.amount, 2, 'le');
|
||||
const amount = rawAmount.div(dec).toNumber();
|
||||
const div = rawAmount.div(dec).toNumber();
|
||||
const mod = rawAmount.mod(dec).toNumber();
|
||||
const amount = parseFloat(div + '.' + mod.toString());
|
||||
const txhash = acc.publicKey.toBase58();
|
||||
|
||||
transfers.set(assetAddress, {
|
||||
|
@ -142,6 +144,14 @@ const queryWrappedMetaTransactions = async (
|
|||
if (filteredInstructions && filteredInstructions?.length > 0) {
|
||||
for (const ins of filteredInstructions) {
|
||||
const data = bs58.decode((ins as PartiallyDecodedInstruction).data);
|
||||
if (data[0] === TRANSFER_ASSETS_OUT_INSTRUCTION) {
|
||||
try {
|
||||
transfer.owner = (ins as PartiallyDecodedInstruction).accounts[10].toBase58();
|
||||
} catch {
|
||||
// Catch no owner
|
||||
transfer.owner = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
data[0] === POSTVAA_INSTRUCTION &&
|
||||
|
@ -207,10 +217,12 @@ export const useWormholeTransactions = () => {
|
|||
const { tokenMap: ethTokens } = useEthereum();
|
||||
const { tokenMap } = useConnectionConfig();
|
||||
const { coinList } = useCoingecko();
|
||||
const { wallet, connected: walletConnected } = useWallet();
|
||||
const bridge = useBridge();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [transfers, setTransfers] = useState<WrappedTransferMeta[]>([]);
|
||||
const [userTransfers, setUserTransfers] = useState<WrappedTransferMeta[]>([]);
|
||||
const [amountInUSD, setAmountInUSD] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -241,6 +253,16 @@ export const useWormholeTransactions = () => {
|
|||
})();
|
||||
}, [connection, setTransfers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (transfers && walletConnected && wallet?.publicKey) {
|
||||
setUserTransfers(
|
||||
transfers.filter(t => {
|
||||
return t.owner === wallet?.publicKey?.toBase58();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [wallet, walletConnected, transfers]);
|
||||
|
||||
const coingeckoTimer = useRef<number>(0);
|
||||
const dataSourcePriceQuery = useCallback(async () => {
|
||||
if (transfers.length === 0) return;
|
||||
|
@ -306,6 +328,7 @@ export const useWormholeTransactions = () => {
|
|||
return {
|
||||
loading,
|
||||
transfers,
|
||||
userTransfers,
|
||||
totalInUSD: amountInUSD,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ProgressUpdate, TransferRequest } from './interface';
|
|||
import BN from 'bn.js';
|
||||
import { createLockAssetInstruction } from '../lock';
|
||||
import { TransferOutProposalLayout } from '../transferOutProposal';
|
||||
import { SolanaBridge } from '../../../core';
|
||||
|
||||
export const fromSolana = async (
|
||||
connection: Connection,
|
||||
|
@ -16,17 +17,17 @@ export const fromSolana = async (
|
|||
request: TransferRequest,
|
||||
provider: ethers.providers.Web3Provider,
|
||||
setProgress: (update: ProgressUpdate) => void,
|
||||
bridge?: SolanaBridge,
|
||||
) => {
|
||||
if (
|
||||
!request.asset ||
|
||||
!request.amount ||
|
||||
!request.recipient ||
|
||||
!request.to ||
|
||||
!request.info
|
||||
!request.info ||
|
||||
!bridge
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const walletName = 'MetaMask';
|
||||
const signer = provider?.getSigner();
|
||||
request.recipient = Buffer.from((await signer.getAddress()).slice(2), 'hex');
|
||||
const nonce = await provider.getTransactionCount(
|
||||
|
@ -34,11 +35,6 @@ export const fromSolana = async (
|
|||
'pending',
|
||||
);
|
||||
|
||||
const amountBN = ethers.utils.parseUnits(
|
||||
request.amount.toString(),
|
||||
request.info.decimals,
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
// check difference between lock/approve (invoke lock if allowance < amount)
|
||||
const steps = {
|
||||
|
@ -66,7 +62,7 @@ export const fromSolana = async (
|
|||
return;
|
||||
}
|
||||
|
||||
let group = 'Lock assets';
|
||||
let group = 'Initiate transfer';
|
||||
const programs = programIds();
|
||||
const bridgeId = programs.wormhole.pubkey;
|
||||
const authorityKey = await bridgeAuthorityKey(bridgeId);
|
||||
|
@ -79,7 +75,7 @@ export const fromSolana = async (
|
|||
wallet.publicKey,
|
||||
new PublicKey(request.info.address),
|
||||
new PublicKey(request.info.mint),
|
||||
new BN(request.amount.toString()),
|
||||
new BN(amount),
|
||||
request.to,
|
||||
request.recipient,
|
||||
{
|
||||
|
@ -100,6 +96,12 @@ export const fromSolana = async (
|
|||
amount,
|
||||
);
|
||||
|
||||
setProgress({
|
||||
message: 'Waiting for Solana approval...',
|
||||
type: 'user',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
let fee_ix = SystemProgram.transfer({
|
||||
fromPubkey: wallet.publicKey,
|
||||
toPubkey: authorityKey,
|
||||
|
@ -126,29 +128,28 @@ export const fromSolana = async (
|
|||
let startSlot = slot;
|
||||
|
||||
let group = 'Lock assets';
|
||||
|
||||
const solConfirmationMessage = (current: number) =>
|
||||
`Awaiting ETH confirmations: ${current} out of 32`;
|
||||
let slotUpdateListener = connection.onSlotChange(slot => {
|
||||
if (completed) return;
|
||||
const passedSlots = slot.slot - startSlot;
|
||||
const isLast = passedSlots - 1 === 31;
|
||||
if (passedSlots < 32) {
|
||||
// setLoading({
|
||||
// loading: true,
|
||||
// message: "Awaiting confirmations",
|
||||
// progress: {
|
||||
// completion: (slot.slot - startSlot) / 32 * 100,
|
||||
// content: `${slot.slot - startSlot}/${32}`
|
||||
// }
|
||||
// })
|
||||
// setProgress({
|
||||
// message: ethConfirmationMessage(passedBlocks),
|
||||
// type: isLast ? 'done' : 'wait',
|
||||
// step: counter++,
|
||||
// group,
|
||||
// replace: passedBlocks > 0,
|
||||
// });
|
||||
} else {
|
||||
//setLoading({loading: true, message: "Awaiting guardian confirmation"})
|
||||
setProgress({
|
||||
message: solConfirmationMessage(passedSlots),
|
||||
type: isLast ? 'done' : 'wait',
|
||||
step: counter++,
|
||||
group,
|
||||
replace: passedSlots > 0,
|
||||
});
|
||||
if (isLast) {
|
||||
setProgress({
|
||||
message: 'Awaiting guardian confirmation',
|
||||
type: 'wait',
|
||||
step: counter++,
|
||||
group,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -176,27 +177,27 @@ export const fromSolana = async (
|
|||
connection.removeAccountChangeListener(accountChangeListener);
|
||||
connection.removeSlotChangeListener(slotUpdateListener);
|
||||
|
||||
// let signatures = await bridge.fetchSignatureStatus(
|
||||
// lockup.signatureAccount,
|
||||
// );
|
||||
// let sigData = Buffer.of(
|
||||
// ...signatures.reduce((previousValue, currentValue) => {
|
||||
// previousValue.push(currentValue.index);
|
||||
// previousValue.push(...currentValue.signature);
|
||||
let signatures = await bridge.fetchSignatureStatus(
|
||||
lockup.signatureAccount,
|
||||
);
|
||||
let sigData = Buffer.of(
|
||||
...signatures.reduce((previousValue, currentValue) => {
|
||||
previousValue.push(currentValue.index);
|
||||
previousValue.push(...currentValue.signature);
|
||||
|
||||
// return previousValue;
|
||||
// }, new Array<number>()),
|
||||
// );
|
||||
return previousValue;
|
||||
}, new Array<number>()),
|
||||
);
|
||||
|
||||
vaa = Buffer.concat([
|
||||
vaa.slice(0, 5),
|
||||
Buffer.of(signatures.length),
|
||||
sigData,
|
||||
vaa.slice(6),
|
||||
]);
|
||||
|
||||
// vaa = Buffer.concat([
|
||||
// vaa.slice(0, 5),
|
||||
// Buffer.of(signatures.length),
|
||||
// sigData,
|
||||
// vaa.slice(6),
|
||||
// ]);
|
||||
// transferVAA = vaa
|
||||
try {
|
||||
await steps.postVAA(request);
|
||||
await steps.postVAA(request, vaa);
|
||||
resolve();
|
||||
} catch {
|
||||
reject();
|
||||
|
@ -206,22 +207,30 @@ export const fromSolana = async (
|
|||
);
|
||||
});
|
||||
},
|
||||
postVAA: async (request: TransferRequest) => {
|
||||
postVAA: async (request: TransferRequest, vaa: any) => {
|
||||
let wh = WormholeFactory.connect(programIds().wormhole.bridge, signer);
|
||||
|
||||
// setLoading({
|
||||
// ...loading,
|
||||
// loading: true,
|
||||
// message: "Sign the claim...",
|
||||
// })
|
||||
// let tx = await wh.submitVAA(vaa);
|
||||
// setLoading({
|
||||
// ...loading,
|
||||
// loading: true,
|
||||
// message: "Waiting for tokens unlock to be mined...",
|
||||
// })
|
||||
// await tx.wait(1);
|
||||
// message.success({content: "Execution of VAA succeeded", key: "eth_tx"})
|
||||
let group = 'Finalizing transfer';
|
||||
setProgress({
|
||||
message: 'Sign the claim...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
let tx = await wh.submitVAA(vaa);
|
||||
setProgress({
|
||||
message: 'Waiting for tokens unlock to be mined...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
await tx.wait(1);
|
||||
setProgress({
|
||||
message: 'Execution of VAA succeeded',
|
||||
type: 'done',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
//message.success({content: "", key: "eth_tx"})
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -37,12 +37,10 @@ export interface TransferRequest {
|
|||
|
||||
export const displayBalance = (info?: TransferRequestInfo) => {
|
||||
try {
|
||||
return (
|
||||
new BN(info?.balance?.toString() || 0)
|
||||
.div(new BN(10).pow(new BN(Math.min((info?.decimals || 0) - 2, 0))))
|
||||
.toNumber() / 100
|
||||
);
|
||||
} catch {
|
||||
const balance = info?.balance || new BigNumber(0);
|
||||
const precision = new BigNumber(10).pow(info?.decimals || new BigNumber(0));
|
||||
return balance.div(precision).toNumber();
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,11 +4,12 @@ export const WORMHOLE_PROGRAM_ID = new PublicKey(
|
|||
'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC',
|
||||
);
|
||||
|
||||
export const TRANSFER_ASSETS_OUT_INSTRUCTION: number = 1;
|
||||
export const POSTVAA_INSTRUCTION: number = 2;
|
||||
|
||||
const INSTRUCTION_LOOKUP: { [key: number]: string } = {
|
||||
0: 'Initialize Bridge',
|
||||
1: 'Transfer Assets Out',
|
||||
[TRANSFER_ASSETS_OUT_INSTRUCTION]: 'Transfer Assets Out',
|
||||
[POSTVAA_INSTRUCTION]: 'Post VAA',
|
||||
3: 'Evict Transfer Proposal',
|
||||
4: 'Evict Claimed VAA',
|
||||
|
|
Loading…
Reference in New Issue