Working sol -> eth wormhole (#87)

* Workin sol -> eth wormhole

* remove console

* fixed selecting same chains and showing same balances
This commit is contained in:
Juan Diego García 2021-04-22 19:52:18 -05:00 committed by GitHub
parent dce6790f4e
commit 2fddc4d024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 457 additions and 183 deletions

View File

@ -28,24 +28,6 @@ export function Input(props: {
}) { }) {
const { connected } = useWallet(); const { connected } = useWallet();
const [lastAmount, setLastAmount] = useState<string>(''); 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 ( return (
<div className={`dashed-input-container ${props.className}`}> <div className={`dashed-input-container ${props.className}`}>
@ -53,25 +35,14 @@ export function Input(props: {
<div className="input-chain"> <div className="input-chain">
<TokenChain chain={props.chain} className={'input-icon'} /> <TokenChain chain={props.chain} className={'input-icon'} />
{chainToName(props.chain)} {chainToName(props.chain)}
{props.chain !== ASSET_CHAIN.Solana ? ( <div
typeof props.balance === 'number' && ( className="balance"
<div onClick={() =>
className="balance" props.onInputChange && props.onInputChange(props.balance)
onClick={() => }
props.onInputChange && props.onInputChange(props.balance) >
} {props.balance}
> </div>
{props.balance.toFixed(10)}
</div>
)
) : (
<div
className="balance"
onClick={() => props.onInputChange && props.onInputChange(balance)}
>
{balance.toFixed(10)}
</div>
)}
</div> </div>
<div className="input-container"> <div className="input-container">
<NumericInput <NumericInput

View File

@ -1,5 +1,5 @@
import { Table } from 'antd'; import { Button, Table, Tabs, notification } from 'antd';
import React from 'react'; import React, { useEffect, useState } from 'react';
import './index.less'; import './index.less';
@ -12,22 +12,39 @@ import { toChainSymbol } from '../../contexts/chainPair';
import { import {
formatUSD, formatUSD,
shortenAddress, shortenAddress,
EtherscanLink,
ExplorerLink,
Identicon, Identicon,
programIds,
} from '@oyster/common'; } from '@oyster/common';
import { useWormholeTransactions } from '../../hooks/useWormholeTransactions'; import { useWormholeTransactions } from '../../hooks/useWormholeTransactions';
import { ASSET_CHAIN } from '../../utils/assets'; import { ASSET_CHAIN } from '../../utils/assets';
import { TokenChain } from '../TokenDisplay/tokenChain'; import { TokenChain } from '../TokenDisplay/tokenChain';
import bs58 from 'bs58'; 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); TimeAgo.addDefaultLocale(en);
const timeAgo = new TimeAgo('en-US'); const timeAgo = new TimeAgo('en-US');
export const RecentTransactionsTable = () => { const { TabPane } = Tabs;
const { loading: loadingTransfers, transfers } = useWormholeTransactions();
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: '', title: '',
dataIndex: 'logo', dataIndex: 'logo',
@ -68,31 +85,29 @@ export const RecentTransactionsTable = () => {
dataIndex: 'symbol', dataIndex: 'symbol',
key: 'symbol', key: 'symbol',
render(text: string, record: any) { render(text: string, record: any) {
const urlText = record.symbol || record.address;
return { return {
props: { style: {} }, props: { style: {} },
children: record.symbol ? ( children:
<Link record.lockup.assetChain === ASSET_CHAIN.Solana ? (
to={`/move?from=${toChainSymbol(record.chain)}&token=${ <a
record.symbol href={`https://explorer.solana.com/address/${record.address}`}
}`} // eslint-disable-next-line react/jsx-no-target-blank
> target="_blank"
<span style={{ display: 'inline-flex', alignItems: 'center' }}> title={urlText}
{record.symbol} >
</span> {record.symbol || shortenAddress(urlText, 5)}
</Link> </a>
) : record.lockup.assetChain === ASSET_CHAIN.Solana ? ( ) : (
<ExplorerLink <a
address={record.address} href={`https://etherscan.io/address/${record.address}`}
length={5} // eslint-disable-next-line react/jsx-no-target-blank
type={'address'} target="_blank"
/> title={urlText}
) : ( >
<EtherscanLink {record.symbol || shortenAddress(urlText, 5)}
address={record.address} </a>
type={'address'} ),
length={5}
/>
),
}; };
}, },
}, },
@ -138,6 +153,9 @@ export const RecentTransactionsTable = () => {
}; };
}, },
}, },
];
const columns = [
...baseColumns,
{ {
title: 'Status', title: 'Status',
dataIndex: '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 ( return (
<div id={'recent-tx-container'}> <div id={'recent-tx-container'}>
<div className={'home-subtitle'} style={{ marginBottom: '70px' }}> <div className={'home-subtitle'} style={{ marginBottom: '70px' }}>
Recent Transactions Transactions
</div> </div>
<Table <Tabs defaultActiveKey="1" centered>
scroll={{ <TabPane tab="Recent Transactions" key="1">
scrollToFirstRowOnChange: false, <Table
x: 900, scroll={{
}} scrollToFirstRowOnChange: false,
dataSource={transfers.sort((a, b) => b.date - a.date)} x: 900,
columns={columns} }}
loading={loadingTransfers} 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> </div>
); );
}; };

View File

@ -18,6 +18,7 @@ import { useTokenChainPairState } from '../../contexts/chainPair';
import { LABELS } from '../../constants'; import { LABELS } from '../../constants';
import { useCorrectNetwork } from '../../hooks/useCorrectNetwork'; import { useCorrectNetwork } from '../../hooks/useCorrectNetwork';
import { RecentTransactionsTable } from '../RecentTransactionsTable'; import { RecentTransactionsTable } from '../RecentTransactionsTable';
import { useBridge } from '../../contexts/bridge';
const { useConnection } = contexts.Connection; const { useConnection } = contexts.Connection;
const { useWallet } = contexts.Wallet; const { useWallet } = contexts.Wallet;
@ -40,6 +41,7 @@ export const typeToIcon = (type: string, isLast: boolean) => {
export const Transfer = () => { export const Transfer = () => {
const connection = useConnection(); const connection = useConnection();
const bridge = useBridge();
const { wallet, connected } = useWallet(); const { wallet, connected } = useWallet();
const { provider, tokenMap } = useEthereum(); const { provider, tokenMap } = useEthereum();
const hasCorrespondingNetworks = useCorrectNetwork(); const hasCorrespondingNetworks = useCorrectNetwork();
@ -50,6 +52,7 @@ export const Transfer = () => {
setMintAddress, setMintAddress,
setLastTypedAccount, setLastTypedAccount,
} = useTokenChainPairState(); } = useTokenChainPairState();
const [request, setRequest] = useState<TransferRequest>({ const [request, setRequest] = useState<TransferRequest>({
from: ASSET_CHAIN.Ethereum, from: ASSET_CHAIN.Ethereum,
to: ASSET_CHAIN.Solana, to: ASSET_CHAIN.Solana,
@ -77,7 +80,7 @@ export const Transfer = () => {
to: B.chain, to: B.chain,
info: A.info, info: A.info,
}); });
}, [A, B, mintAddress]); }, [A, B, mintAddress, A.info]);
return ( return (
<> <>
@ -92,7 +95,9 @@ export const Transfer = () => {
onChain={(chain: ASSET_CHAIN) => { onChain={(chain: ASSET_CHAIN) => {
const from = A.chain; const from = A.chain;
A.setChain(chain); A.setChain(chain);
B.setChain(from); if (B.chain === chain) {
B.setChain(from);
}
}} }}
onInputChange={amount => { onInputChange={amount => {
setLastTypedAccount(A.chain); setLastTypedAccount(A.chain);
@ -126,7 +131,9 @@ export const Transfer = () => {
onChain={(chain: ASSET_CHAIN) => { onChain={(chain: ASSET_CHAIN) => {
const to = B.chain; const to = B.chain;
B.setChain(chain); B.setChain(chain);
A.setChain(to); if (A.chain === chain) {
A.setChain(to);
}
}} }}
onInputChange={amount => { onInputChange={amount => {
setLastTypedAccount(B.chain); setLastTypedAccount(B.chain);
@ -171,6 +178,7 @@ export const Transfer = () => {
setActiveSteps(steps); setActiveSteps(steps);
}, },
bridge,
); );
} }

View File

@ -24,6 +24,9 @@ import {
TransferRequestInfo, TransferRequestInfo,
wrappedAssetMintKey, wrappedAssetMintKey,
} from '../models/bridge'; } from '../models/bridge';
import { useBridge } from './bridge';
import { PublicKey } from '@solana/web3.js';
export interface TokenChainContextState { export interface TokenChainContextState {
info?: TransferRequestInfo; info?: TransferRequestInfo;
@ -91,11 +94,22 @@ export const useCurrencyLeg = (mintAddress: string) => {
const [chain, setChain] = useState(ASSET_CHAIN.Ethereum); const [chain, setChain] = useState(ASSET_CHAIN.Ethereum);
const [info, setInfo] = useState<TransferRequestInfo>(); const [info, setInfo] = useState<TransferRequestInfo>();
const { userAccounts } = useUserAccounts(); const { userAccounts } = useUserAccounts();
const bridge = useBridge();
const { provider, tokens: ethTokens } = useEthereum(); const { provider, tokens: ethTokens } = useEthereum();
const { tokens: solTokens } = useConnectionConfig(); const { tokens: solTokens } = useConnectionConfig();
const connection = useConnection(); 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(() => { useEffect(() => {
if (!provider || !connection) { if (!provider || !connection) {
return; return;
@ -111,23 +125,44 @@ export const useCurrencyLeg = (mintAddress: string) => {
// sol asset on sol chain // sol asset on sol chain
//let ethAddress: string = ''; //let ethAddress: string = '';
if (solToken) { // console.log({ chain, solToken, ethToken });
// let signer = provider.getSigner(); if (chain === ASSET_CHAIN.Solana) {
// let e = WrappedAssetFactory.connect(asset, provider); if (!solToken) {
// let addr = await signer.getAddress(); setInfo(defaultCoinInfo);
// let decimals = await e.decimals(); return;
// let symbol = await e.symbol(); }
// TODO: checked if mint is wrapped mint from eth... // 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 if (!assetMeta || !currentAccount) {
.filter(a => a.info.mint.toBase58() === solToken.address) setInfo(defaultCoinInfo);
.sort((a, b) => a.info.amount.toNumber() - b.info.amount.toNumber()); 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 signer = provider.getSigner();
let e = WrappedAssetFactory.connect(mintAddress, provider); let e = WrappedAssetFactory.connect(mintAddress, provider);
let addr = await signer.getAddress(); let addr = await signer.getAddress();
@ -170,12 +205,9 @@ export const useCurrencyLeg = (mintAddress: string) => {
address: info.assetAddress, address: info.assetAddress,
chain: info.chainID, chain: info.chainID,
}); });
console.log(mint.toBase58());
} }
console.log(info); // console.log({ info });
setInfo(info); setInfo(info);
} }
})(); })();
@ -190,16 +222,13 @@ export const useCurrencyLeg = (mintAddress: string) => {
userAccounts, userAccounts,
]); ]);
return useMemo( return {
() => ({ amount: amount,
amount: amount, setAmount: setAmount,
setAmount: setAmount, chain: chain,
chain: chain, setChain: setChain,
setChain: setChain, info,
info, };
}),
[amount, setAmount, chain, setChain],
);
}; };
export function TokenChainPairProvider({ children = null as any }) { export function TokenChainPairProvider({ children = null as any }) {

View File

@ -5,6 +5,7 @@ import assert from 'assert';
// @ts-ignore // @ts-ignore
import * as BufferLayout from 'buffer-layout'; import * as BufferLayout from 'buffer-layout';
import * as bs58 from 'bs58'; import * as bs58 from 'bs58';
import { AssetMeta } from '../models/bridge';
export interface Lockup { export interface Lockup {
lockupAddress: PublicKey; lockupAddress: PublicKey;
@ -71,7 +72,40 @@ class SolanaBridge {
data, 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 // fetchSignatureStatus fetches the signatures for a VAA
async fetchSignatureStatus(signatureStatus: PublicKey): Promise<Signature[]> { async fetchSignatureStatus(signatureStatus: PublicKey): Promise<Signature[]> {
let signatureInfo = await this.connection.getAccountInfo( let signatureInfo = await this.connection.getAccountInfo(

View File

@ -4,13 +4,17 @@ import {
useConnectionConfig, useConnectionConfig,
programIds, programIds,
notify, notify,
useWallet,
} from '@oyster/common'; } 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 { ASSET_CHAIN } from '../utils/assets';
import { useEthereum } from '../contexts'; import { useEthereum } from '../contexts';
import { import {
Connection, Connection,
ParsedInstruction,
PartiallyDecodedInstruction, PartiallyDecodedInstruction,
PublicKey, PublicKey,
} from '@solana/web3.js'; } from '@solana/web3.js';
@ -31,11 +35,6 @@ import { ethers } from 'ethers';
import { useBridge } from '../contexts/bridge'; import { useBridge } from '../contexts/bridge';
import { SolanaBridge } from '../core'; import { SolanaBridge } from '../core';
interface ParsedData {
info: any;
type: string;
}
type WrappedTransferMeta = { type WrappedTransferMeta = {
chain: number; chain: number;
decimals: number; decimals: number;
@ -53,6 +52,7 @@ type WrappedTransferMeta = {
txhash?: string; txhash?: string;
date: number; // timestamp date: number; // timestamp
status?: string; status?: string;
owner?: string;
lockup?: any; lockup?: any;
vaa?: any; vaa?: any;
}; };
@ -105,7 +105,9 @@ const queryWrappedMetaTransactions = async (
const dec = new BN(10).pow(new BN(metaTransfer.assetDecimals)); const dec = new BN(10).pow(new BN(metaTransfer.assetDecimals));
const rawAmount = new BN(metaTransfer.amount, 2, 'le'); 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(); const txhash = acc.publicKey.toBase58();
transfers.set(assetAddress, { transfers.set(assetAddress, {
@ -142,6 +144,14 @@ const queryWrappedMetaTransactions = async (
if (filteredInstructions && filteredInstructions?.length > 0) { if (filteredInstructions && filteredInstructions?.length > 0) {
for (const ins of filteredInstructions) { for (const ins of filteredInstructions) {
const data = bs58.decode((ins as PartiallyDecodedInstruction).data); 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 ( if (
data[0] === POSTVAA_INSTRUCTION && data[0] === POSTVAA_INSTRUCTION &&
@ -207,10 +217,12 @@ export const useWormholeTransactions = () => {
const { tokenMap: ethTokens } = useEthereum(); const { tokenMap: ethTokens } = useEthereum();
const { tokenMap } = useConnectionConfig(); const { tokenMap } = useConnectionConfig();
const { coinList } = useCoingecko(); const { coinList } = useCoingecko();
const { wallet, connected: walletConnected } = useWallet();
const bridge = useBridge(); const bridge = useBridge();
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [transfers, setTransfers] = useState<WrappedTransferMeta[]>([]); const [transfers, setTransfers] = useState<WrappedTransferMeta[]>([]);
const [userTransfers, setUserTransfers] = useState<WrappedTransferMeta[]>([]);
const [amountInUSD, setAmountInUSD] = useState<number>(0); const [amountInUSD, setAmountInUSD] = useState<number>(0);
useEffect(() => { useEffect(() => {
@ -241,6 +253,16 @@ export const useWormholeTransactions = () => {
})(); })();
}, [connection, setTransfers]); }, [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 coingeckoTimer = useRef<number>(0);
const dataSourcePriceQuery = useCallback(async () => { const dataSourcePriceQuery = useCallback(async () => {
if (transfers.length === 0) return; if (transfers.length === 0) return;
@ -306,6 +328,7 @@ export const useWormholeTransactions = () => {
return { return {
loading, loading,
transfers, transfers,
userTransfers,
totalInUSD: amountInUSD, totalInUSD: amountInUSD,
}; };
}; };

View File

@ -9,6 +9,7 @@ import { ProgressUpdate, TransferRequest } from './interface';
import BN from 'bn.js'; import BN from 'bn.js';
import { createLockAssetInstruction } from '../lock'; import { createLockAssetInstruction } from '../lock';
import { TransferOutProposalLayout } from '../transferOutProposal'; import { TransferOutProposalLayout } from '../transferOutProposal';
import { SolanaBridge } from '../../../core';
export const fromSolana = async ( export const fromSolana = async (
connection: Connection, connection: Connection,
@ -16,17 +17,17 @@ export const fromSolana = async (
request: TransferRequest, request: TransferRequest,
provider: ethers.providers.Web3Provider, provider: ethers.providers.Web3Provider,
setProgress: (update: ProgressUpdate) => void, setProgress: (update: ProgressUpdate) => void,
bridge?: SolanaBridge,
) => { ) => {
if ( if (
!request.asset || !request.asset ||
!request.amount || !request.amount ||
!request.recipient ||
!request.to || !request.to ||
!request.info !request.info ||
!bridge
) { ) {
return; return;
} }
const walletName = 'MetaMask';
const signer = provider?.getSigner(); const signer = provider?.getSigner();
request.recipient = Buffer.from((await signer.getAddress()).slice(2), 'hex'); request.recipient = Buffer.from((await signer.getAddress()).slice(2), 'hex');
const nonce = await provider.getTransactionCount( const nonce = await provider.getTransactionCount(
@ -34,11 +35,6 @@ export const fromSolana = async (
'pending', 'pending',
); );
const amountBN = ethers.utils.parseUnits(
request.amount.toString(),
request.info.decimals,
);
let counter = 0; let counter = 0;
// check difference between lock/approve (invoke lock if allowance < amount) // check difference between lock/approve (invoke lock if allowance < amount)
const steps = { const steps = {
@ -66,7 +62,7 @@ export const fromSolana = async (
return; return;
} }
let group = 'Lock assets'; let group = 'Initiate transfer';
const programs = programIds(); const programs = programIds();
const bridgeId = programs.wormhole.pubkey; const bridgeId = programs.wormhole.pubkey;
const authorityKey = await bridgeAuthorityKey(bridgeId); const authorityKey = await bridgeAuthorityKey(bridgeId);
@ -79,7 +75,7 @@ export const fromSolana = async (
wallet.publicKey, wallet.publicKey,
new PublicKey(request.info.address), new PublicKey(request.info.address),
new PublicKey(request.info.mint), new PublicKey(request.info.mint),
new BN(request.amount.toString()), new BN(amount),
request.to, request.to,
request.recipient, request.recipient,
{ {
@ -100,6 +96,12 @@ export const fromSolana = async (
amount, amount,
); );
setProgress({
message: 'Waiting for Solana approval...',
type: 'user',
group,
step: counter++,
});
let fee_ix = SystemProgram.transfer({ let fee_ix = SystemProgram.transfer({
fromPubkey: wallet.publicKey, fromPubkey: wallet.publicKey,
toPubkey: authorityKey, toPubkey: authorityKey,
@ -126,29 +128,28 @@ export const fromSolana = async (
let startSlot = slot; let startSlot = slot;
let group = 'Lock assets'; let group = 'Lock assets';
const solConfirmationMessage = (current: number) =>
`Awaiting ETH confirmations: ${current} out of 32`;
let slotUpdateListener = connection.onSlotChange(slot => { let slotUpdateListener = connection.onSlotChange(slot => {
if (completed) return; if (completed) return;
const passedSlots = slot.slot - startSlot; const passedSlots = slot.slot - startSlot;
const isLast = passedSlots - 1 === 31; const isLast = passedSlots - 1 === 31;
if (passedSlots < 32) { if (passedSlots < 32) {
// setLoading({ setProgress({
// loading: true, message: solConfirmationMessage(passedSlots),
// message: "Awaiting confirmations", type: isLast ? 'done' : 'wait',
// progress: { step: counter++,
// completion: (slot.slot - startSlot) / 32 * 100, group,
// content: `${slot.slot - startSlot}/${32}` replace: passedSlots > 0,
// } });
// }) if (isLast) {
// setProgress({ setProgress({
// message: ethConfirmationMessage(passedBlocks), message: 'Awaiting guardian confirmation',
// type: isLast ? 'done' : 'wait', type: 'wait',
// step: counter++, step: counter++,
// group, group,
// replace: passedBlocks > 0, });
// }); }
} else {
//setLoading({loading: true, message: "Awaiting guardian confirmation"})
} }
}); });
@ -176,27 +177,27 @@ export const fromSolana = async (
connection.removeAccountChangeListener(accountChangeListener); connection.removeAccountChangeListener(accountChangeListener);
connection.removeSlotChangeListener(slotUpdateListener); connection.removeSlotChangeListener(slotUpdateListener);
// let signatures = await bridge.fetchSignatureStatus( let signatures = await bridge.fetchSignatureStatus(
// lockup.signatureAccount, lockup.signatureAccount,
// ); );
// let sigData = Buffer.of( let sigData = Buffer.of(
// ...signatures.reduce((previousValue, currentValue) => { ...signatures.reduce((previousValue, currentValue) => {
// previousValue.push(currentValue.index); previousValue.push(currentValue.index);
// previousValue.push(...currentValue.signature); previousValue.push(...currentValue.signature);
// return previousValue; return previousValue;
// }, new Array<number>()), }, 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 { try {
await steps.postVAA(request); await steps.postVAA(request, vaa);
resolve(); resolve();
} catch { } catch {
reject(); 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); let wh = WormholeFactory.connect(programIds().wormhole.bridge, signer);
let group = 'Finalizing transfer';
// setLoading({ setProgress({
// ...loading, message: 'Sign the claim...',
// loading: true, type: 'wait',
// message: "Sign the claim...", group,
// }) step: counter++,
// let tx = await wh.submitVAA(vaa); });
// setLoading({ let tx = await wh.submitVAA(vaa);
// ...loading, setProgress({
// loading: true, message: 'Waiting for tokens unlock to be mined...',
// message: "Waiting for tokens unlock to be mined...", type: 'wait',
// }) group,
// await tx.wait(1); step: counter++,
// message.success({content: "Execution of VAA succeeded", key: "eth_tx"}) });
await tx.wait(1);
setProgress({
message: 'Execution of VAA succeeded',
type: 'done',
group,
step: counter++,
});
//message.success({content: "", key: "eth_tx"})
}, },
}; };

View File

@ -37,12 +37,10 @@ export interface TransferRequest {
export const displayBalance = (info?: TransferRequestInfo) => { export const displayBalance = (info?: TransferRequestInfo) => {
try { try {
return ( const balance = info?.balance || new BigNumber(0);
new BN(info?.balance?.toString() || 0) const precision = new BigNumber(10).pow(info?.decimals || new BigNumber(0));
.div(new BN(10).pow(new BN(Math.min((info?.decimals || 0) - 2, 0)))) return balance.div(precision).toNumber();
.toNumber() / 100 } catch (e) {
);
} catch {
return 0; return 0;
} }
}; };

View File

@ -4,11 +4,12 @@ export const WORMHOLE_PROGRAM_ID = new PublicKey(
'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC', 'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC',
); );
export const TRANSFER_ASSETS_OUT_INSTRUCTION: number = 1;
export const POSTVAA_INSTRUCTION: number = 2; export const POSTVAA_INSTRUCTION: number = 2;
const INSTRUCTION_LOOKUP: { [key: number]: string } = { const INSTRUCTION_LOOKUP: { [key: number]: string } = {
0: 'Initialize Bridge', 0: 'Initialize Bridge',
1: 'Transfer Assets Out', [TRANSFER_ASSETS_OUT_INSTRUCTION]: 'Transfer Assets Out',
[POSTVAA_INSTRUCTION]: 'Post VAA', [POSTVAA_INSTRUCTION]: 'Post VAA',
3: 'Evict Transfer Proposal', 3: 'Evict Transfer Proposal',
4: 'Evict Claimed VAA', 4: 'Evict Claimed VAA',