mirror of https://github.com/certusone/oyster.git
fix: transfer
This commit is contained in:
parent
39ae89ca8b
commit
730777f732
|
@ -1,32 +1,24 @@
|
|||
import React, { useState } from 'react';
|
||||
import { NumericInput, programIds } from '@oyster/common';
|
||||
import { NumericInput } from '@oyster/common';
|
||||
import { Card, Select } from 'antd';
|
||||
import './style.less';
|
||||
import { useEthereum } from '../../contexts';
|
||||
import { WrappedAssetFactory } from '../../contracts/WrappedAssetFactory';
|
||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
||||
import { TransferRequestInfo } from '../../models/bridge';
|
||||
import { TokenDisplay } from '../TokenDisplay';
|
||||
import BN from 'bn.js';
|
||||
import { ASSET_CHAIN } from '../../models/bridge/constants';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
export function EthereumInput(props: {
|
||||
title: string;
|
||||
hideBalance?: boolean;
|
||||
|
||||
balance?: number;
|
||||
asset?: string;
|
||||
chain?: ASSET_CHAIN;
|
||||
setAsset: (asset: string) => void;
|
||||
|
||||
setInfo: (info: TransferRequestInfo) => void;
|
||||
amount?: number | null;
|
||||
onInputChange: (value: number | null) => void;
|
||||
onInputChange: (value: number | undefined) => void;
|
||||
}) {
|
||||
const [balance, setBalance] = useState<number>(0);
|
||||
const [lastAmount, setLastAmount] = useState<string>('');
|
||||
const { tokens, provider } = useEthereum();
|
||||
const { tokens } = useEthereum();
|
||||
|
||||
const renderReserveAccounts = tokens
|
||||
.filter(t => (t.tags?.indexOf('longList') || -1) < 0)
|
||||
|
@ -54,54 +46,6 @@ export function EthereumInput(props: {
|
|||
);
|
||||
});
|
||||
|
||||
const updateBalance = async (fromAddress: string) => {
|
||||
props.setAsset(fromAddress);
|
||||
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bridgeAddress = programIds().wormhole.bridge;
|
||||
|
||||
let signer = provider.getSigner();
|
||||
let e = WrappedAssetFactory.connect(fromAddress, provider);
|
||||
let addr = await signer.getAddress();
|
||||
let balance = await e.balanceOf(addr);
|
||||
let decimals = await e.decimals();
|
||||
let symbol = await e.symbol();
|
||||
|
||||
let allowance = await e.allowance(addr, bridgeAddress);
|
||||
|
||||
let info = {
|
||||
address: fromAddress,
|
||||
name: symbol,
|
||||
balance: balance,
|
||||
allowance: allowance,
|
||||
decimals: decimals,
|
||||
isWrapped: false,
|
||||
chainID: ASSET_CHAIN.Ethereum,
|
||||
assetAddress: Buffer.from(fromAddress.slice(2), 'hex'),
|
||||
mint: '',
|
||||
};
|
||||
|
||||
setBalance(
|
||||
new BN(info.balance.toString())
|
||||
.div(new BN(10).pow(new BN(info.decimals)))
|
||||
.toNumber(),
|
||||
);
|
||||
|
||||
let b = WormholeFactory.connect(bridgeAddress, provider);
|
||||
|
||||
let isWrapped = await b.isWrappedAsset(fromAddress);
|
||||
if (isWrapped) {
|
||||
info.chainID = await e.assetChain();
|
||||
info.assetAddress = Buffer.from((await e.assetAddress()).slice(2), 'hex');
|
||||
info.isWrapped = true;
|
||||
}
|
||||
|
||||
props.setInfo(info);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="ccy-input from-input"
|
||||
|
@ -111,12 +55,12 @@ export function EthereumInput(props: {
|
|||
<div className="ccy-input-header">
|
||||
<div className="ccy-input-header-left">{props.title}</div>
|
||||
|
||||
{!props.hideBalance && (
|
||||
{!!props.balance && (
|
||||
<div
|
||||
className="ccy-input-header-right"
|
||||
onClick={e => props.onInputChange && props.onInputChange(balance)}
|
||||
onClick={() => props.onInputChange && props.onInputChange(props.balance)}
|
||||
>
|
||||
Balance: {balance.toFixed(6)}
|
||||
Balance: {props.balance.toFixed(6)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -132,7 +76,7 @@ export function EthereumInput(props: {
|
|||
}
|
||||
onChange={(val: string) => {
|
||||
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
||||
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||
if (!val || !parseFloat(val)) props.onInputChange(undefined);
|
||||
else props.onInputChange(parseFloat(val));
|
||||
}
|
||||
setLastAmount(val);
|
||||
|
@ -153,7 +97,7 @@ export function EthereumInput(props: {
|
|||
placeholder="CCY"
|
||||
value={props.asset}
|
||||
onChange={(item: string) => {
|
||||
updateBalance(item);
|
||||
props.setAsset(item);
|
||||
}}
|
||||
filterOption={(input, option) =>
|
||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Card, notification, Spin, Button } from 'antd';
|
||||
import { TokenInfo } from '@uniswap/token-lists';
|
||||
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||
import { LABELS } from '../../constants';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { notification, Spin, Button } from 'antd';
|
||||
import {
|
||||
contexts,
|
||||
utils,
|
||||
ConnectButton,
|
||||
programIds,
|
||||
formatAmount,
|
||||
} from '@oyster/common';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { EthereumInput } from './../Input';
|
||||
|
||||
import './style.less';
|
||||
import { ethers } from 'ethers';
|
||||
import { ASSET_CHAIN, chainToName } from '../../utils/assets';
|
||||
import { BigNumber } from 'ethers/utils';
|
||||
import { Erc20Factory } from '../../contracts/Erc20Factory';
|
||||
import { ProgressUpdate, transfer, TransferRequest } from '../../models/bridge';
|
||||
import { ProgressUpdate, toSolana, TransferRequest } from '../../models/bridge';
|
||||
import { useEthereum } from '../../contexts';
|
||||
import { TokenDisplay } from './../TokenDisplay';
|
||||
import { WrappedAssetFactory } from '../../contracts/WrappedAssetFactory';
|
||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
||||
import BN from 'bn.js';
|
||||
|
||||
const { useConnection } = contexts.Connection;
|
||||
const { useWallet } = contexts.Wallet;
|
||||
|
@ -51,23 +46,75 @@ export const Transfer = () => {
|
|||
toChain: ASSET_CHAIN.Solana,
|
||||
});
|
||||
|
||||
const setAssetInformation = (asset: string) => {
|
||||
const setAssetInformation = async (asset: string) => {
|
||||
setRequest({
|
||||
...request,
|
||||
asset,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const asset = request.asset;
|
||||
if(!asset || asset === request?.info?.address) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bridgeAddress = programIds().wormhole.bridge;
|
||||
|
||||
let signer = provider.getSigner();
|
||||
let e = WrappedAssetFactory.connect(asset, provider);
|
||||
let addr = await signer.getAddress();
|
||||
let balance = await e.balanceOf(addr);
|
||||
let decimals = await e.decimals();
|
||||
let symbol = await e.symbol();
|
||||
|
||||
let allowance = await e.allowance(addr, bridgeAddress);
|
||||
|
||||
let info = {
|
||||
address: asset,
|
||||
name: symbol,
|
||||
balance: balance,
|
||||
balanceAsNumber: (new BN(balance.toString())
|
||||
.div(new BN(10).pow(new BN(decimals - 2)))
|
||||
.toNumber()) / 100,
|
||||
allowance: allowance,
|
||||
decimals: decimals,
|
||||
isWrapped: false,
|
||||
chainID: ASSET_CHAIN.Ethereum,
|
||||
assetAddress: Buffer.from(asset.slice(2), 'hex'),
|
||||
mint: '',
|
||||
};
|
||||
|
||||
let b = WormholeFactory.connect(bridgeAddress, provider);
|
||||
|
||||
let isWrapped = await b.isWrappedAsset(asset);
|
||||
if (isWrapped) {
|
||||
info.chainID = await e.assetChain();
|
||||
info.assetAddress = Buffer.from((await e.assetAddress()).slice(2), 'hex');
|
||||
info.isWrapped = true;
|
||||
}
|
||||
|
||||
setRequest({
|
||||
...request,
|
||||
asset,
|
||||
info,
|
||||
});
|
||||
})();
|
||||
}, [request, provider])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="exchange-card">
|
||||
<EthereumInput
|
||||
title="From Ethereum"
|
||||
setInfo={info => {
|
||||
request.info = info;
|
||||
}}
|
||||
title={`From ${chainToName(request.from)}`}
|
||||
asset={request.asset}
|
||||
chain={request.from}
|
||||
balance={request.info?.balanceAsNumber || 0 }
|
||||
setAsset={asset => setAssetInformation(asset)}
|
||||
amount={request.amount}
|
||||
onInputChange={amount => {
|
||||
|
@ -94,13 +141,9 @@ export const Transfer = () => {
|
|||
⇅
|
||||
</Button>
|
||||
<EthereumInput
|
||||
title="To Solana"
|
||||
setInfo={info => {
|
||||
request.info = info;
|
||||
}}
|
||||
title={`To ${chainToName(request.toChain)}`}
|
||||
asset={request.asset}
|
||||
chain={request.toChain}
|
||||
hideBalance={true}
|
||||
setAsset={asset => setAssetInformation(asset)}
|
||||
amount={request.amount}
|
||||
onInputChange={amount => {
|
||||
|
@ -126,22 +169,24 @@ export const Transfer = () => {
|
|||
(async () => {
|
||||
let steps: ProgressUpdate[] = [];
|
||||
try {
|
||||
await transfer(
|
||||
connection,
|
||||
wallet,
|
||||
request,
|
||||
provider,
|
||||
update => {
|
||||
if (update.replace) {
|
||||
steps.pop();
|
||||
steps = [...steps, update];
|
||||
} else {
|
||||
steps = [...steps, update];
|
||||
}
|
||||
if(request.toChain === ASSET_CHAIN.Solana) {
|
||||
await toSolana(
|
||||
connection,
|
||||
wallet,
|
||||
request,
|
||||
provider,
|
||||
update => {
|
||||
if (update.replace) {
|
||||
steps.pop();
|
||||
steps = [...steps, update];
|
||||
} else {
|
||||
steps = [...steps, update];
|
||||
}
|
||||
|
||||
setActiveSteps(steps);
|
||||
},
|
||||
);
|
||||
setActiveSteps(steps);
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// TODO...
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import {
|
||||
contexts,
|
||||
utils,
|
||||
programIds,
|
||||
WalletAdapter,
|
||||
getMultipleAccounts,
|
||||
|
@ -8,66 +6,26 @@ import {
|
|||
cache,
|
||||
TokenAccountParser,
|
||||
ParsedAccount,
|
||||
formatNumber,
|
||||
formatAmount,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
} from '@oyster/common';
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
import { ASSET_CHAIN } from '../../utils/assets';
|
||||
import { ASSET_CHAIN } from '../../../utils/assets';
|
||||
import { BigNumber } from 'ethers/utils';
|
||||
import { Erc20Factory } from '../../contracts/Erc20Factory';
|
||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
||||
import { AssetMeta, createWrappedAssetInstruction } from './meta';
|
||||
import { bridgeAuthorityKey, wrappedAssetMintKey } from './helpers';
|
||||
import { Erc20Factory } from '../../../contracts/Erc20Factory';
|
||||
import { WormholeFactory } from '../../../contracts/WormholeFactory';
|
||||
import { AssetMeta, createWrappedAssetInstruction } from './../meta';
|
||||
import { bridgeAuthorityKey, wrappedAssetMintKey } from './../helpers';
|
||||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import { AccountInfo, AccountLayout } from '@solana/spl-token';
|
||||
import { AccountInfo } from '@solana/spl-token';
|
||||
import { ProgressUpdate, TransferRequest } from './interface';
|
||||
|
||||
export interface ProgressUpdate {
|
||||
message: string;
|
||||
type: string;
|
||||
step: number;
|
||||
group: string;
|
||||
replace?: boolean;
|
||||
}
|
||||
|
||||
export interface TransferRequestInfo {
|
||||
name: string;
|
||||
balance: BigNumber;
|
||||
decimals: number;
|
||||
allowance: BigNumber;
|
||||
isWrapped: boolean;
|
||||
chainID: number;
|
||||
assetAddress: Buffer;
|
||||
mint: string;
|
||||
}
|
||||
|
||||
export interface TransferRequest {
|
||||
nonce?: number;
|
||||
signer?: ethers.Signer;
|
||||
asset?: string;
|
||||
amount?: number;
|
||||
amountBN?: BigNumber;
|
||||
|
||||
recipient?: Buffer;
|
||||
|
||||
info?: TransferRequestInfo;
|
||||
|
||||
from?: ASSET_CHAIN;
|
||||
toChain?: ASSET_CHAIN;
|
||||
}
|
||||
|
||||
// type of updates
|
||||
// 1. info
|
||||
// 2. user
|
||||
// 3. wait (progress bar)
|
||||
|
||||
export const transfer = async (
|
||||
export const fromSolana = async (
|
||||
connection: Connection,
|
||||
wallet: WalletAdapter,
|
||||
request: TransferRequest,
|
||||
|
@ -77,7 +35,6 @@ export const transfer = async (
|
|||
if (!request.asset) {
|
||||
return;
|
||||
}
|
||||
|
||||
const walletName = 'MetaMask';
|
||||
request.signer = provider?.getSigner();
|
||||
|
||||
|
@ -384,8 +341,6 @@ export const transfer = async (
|
|||
);
|
||||
});
|
||||
},
|
||||
//
|
||||
vaa: async (request: TransferRequest) => {},
|
||||
};
|
||||
|
||||
return steps.transfer(request);
|
|
@ -0,0 +1,3 @@
|
|||
export * from './toSolana';
|
||||
export * from './fromSolana';
|
||||
export * from './interface';
|
|
@ -0,0 +1,39 @@
|
|||
import { ethers } from 'ethers';
|
||||
import { BigNumber } from 'ethers/utils';
|
||||
import { ASSET_CHAIN } from '../constants';
|
||||
|
||||
export interface ProgressUpdate {
|
||||
message: string;
|
||||
type: string;
|
||||
step: number;
|
||||
group: string;
|
||||
replace?: boolean;
|
||||
}
|
||||
|
||||
export interface TransferRequestInfo {
|
||||
address: string;
|
||||
name: string;
|
||||
balance: BigNumber;
|
||||
balanceAsNumber: number;
|
||||
decimals: number;
|
||||
allowance: BigNumber;
|
||||
isWrapped: boolean;
|
||||
chainID: number;
|
||||
assetAddress: Buffer;
|
||||
mint: string;
|
||||
}
|
||||
|
||||
export interface TransferRequest {
|
||||
nonce?: number;
|
||||
signer?: ethers.Signer;
|
||||
asset?: string;
|
||||
amount?: number;
|
||||
amountBN?: BigNumber;
|
||||
|
||||
recipient?: Buffer;
|
||||
|
||||
info?: TransferRequestInfo;
|
||||
|
||||
from?: ASSET_CHAIN;
|
||||
toChain?: ASSET_CHAIN;
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
import {
|
||||
programIds,
|
||||
WalletAdapter,
|
||||
getMultipleAccounts,
|
||||
sendTransaction,
|
||||
cache,
|
||||
TokenAccountParser,
|
||||
ParsedAccount,
|
||||
formatAmount,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
} from '@oyster/common';
|
||||
import { ethers } from 'ethers';
|
||||
import { Erc20Factory } from '../../../contracts/Erc20Factory';
|
||||
import { WormholeFactory } from '../../../contracts/WormholeFactory';
|
||||
import { AssetMeta, createWrappedAssetInstruction } from './../meta';
|
||||
import { bridgeAuthorityKey, wrappedAssetMintKey } from './../helpers';
|
||||
import {
|
||||
Account,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import { AccountInfo } from '@solana/spl-token';
|
||||
import { TransferRequest, ProgressUpdate } from './interface';
|
||||
|
||||
export const toSolana = async (
|
||||
connection: Connection,
|
||||
wallet: WalletAdapter,
|
||||
request: TransferRequest,
|
||||
provider: ethers.providers.Web3Provider,
|
||||
setProgress: (update: ProgressUpdate) => void,
|
||||
) => {
|
||||
if (!request.asset) {
|
||||
return;
|
||||
}
|
||||
const walletName = 'MetaMask';
|
||||
request.signer = provider?.getSigner();
|
||||
|
||||
request.nonce = await provider.getTransactionCount(
|
||||
request.signer.getAddress(),
|
||||
'pending',
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
// check difference between lock/approve (invoke lock if allowance < amount)
|
||||
const steps = {
|
||||
transfer: async (request: TransferRequest) => {
|
||||
if (!request.info || !request.amount) {
|
||||
return;
|
||||
}
|
||||
|
||||
request.amountBN = ethers.utils.parseUnits(
|
||||
formatAmount(request.amount, 9),
|
||||
request.info.decimals,
|
||||
);
|
||||
|
||||
return steps.prepare(request);
|
||||
},
|
||||
|
||||
// creates wrapped account on solana
|
||||
prepare: async (request: TransferRequest) => {
|
||||
if (!request.info || !request.from || !wallet.publicKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = 'Initiate transfer';
|
||||
try {
|
||||
const bridgeId = programIds().wormhole.pubkey;
|
||||
const authority = await bridgeAuthorityKey(bridgeId);
|
||||
const meta: AssetMeta = {
|
||||
decimals: Math.min(request.info?.decimals, 9),
|
||||
address: request.info?.assetAddress,
|
||||
chain: request.from,
|
||||
};
|
||||
const mintKey = await wrappedAssetMintKey(bridgeId, authority, meta);
|
||||
|
||||
const recipientKey =
|
||||
cache
|
||||
.byParser(TokenAccountParser)
|
||||
.map(key => {
|
||||
let account = cache.get(key) as ParsedAccount<AccountInfo>;
|
||||
if (account?.info.mint.toBase58() === mintKey.toBase58()) {
|
||||
return key;
|
||||
}
|
||||
|
||||
return;
|
||||
})
|
||||
.find(_ => _) || '';
|
||||
const recipient: PublicKey = recipientKey
|
||||
? new PublicKey(recipientKey)
|
||||
: (
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
wallet.publicKey.toBuffer(),
|
||||
programIds().token.toBuffer(),
|
||||
mintKey.toBuffer(),
|
||||
],
|
||||
programIds().associatedToken,
|
||||
)
|
||||
)[0];
|
||||
|
||||
request.recipient = recipient.toBuffer();
|
||||
|
||||
const accounts = await getMultipleAccounts(
|
||||
connection,
|
||||
[mintKey.toBase58(), recipient.toBase58()],
|
||||
'single',
|
||||
);
|
||||
const instructions: TransactionInstruction[] = [];
|
||||
const signers: Account[] = [];
|
||||
|
||||
if (!accounts.array[0]) {
|
||||
// create mint using wormhole instruction
|
||||
instructions.push(
|
||||
await createWrappedAssetInstruction(
|
||||
meta,
|
||||
bridgeId,
|
||||
authority,
|
||||
mintKey,
|
||||
wallet.publicKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!accounts.array[1]) {
|
||||
createAssociatedTokenAccountInstruction(
|
||||
instructions,
|
||||
recipient,
|
||||
wallet.publicKey,
|
||||
wallet.publicKey,
|
||||
mintKey,
|
||||
);
|
||||
}
|
||||
|
||||
if (instructions.length > 0) {
|
||||
setProgress({
|
||||
message: 'Waiting for Solana approval...',
|
||||
type: 'user',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
|
||||
const tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions,
|
||||
signers,
|
||||
true,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
setProgress({
|
||||
message: `Couldn't create Solana account!`,
|
||||
type: 'error',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
|
||||
return steps.approve(request);
|
||||
},
|
||||
// approves assets for transfer
|
||||
approve: async (request: TransferRequest) => {
|
||||
if (!request.amountBN || !request.asset || !request.signer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = 'Approve assets';
|
||||
try {
|
||||
if (request.info?.allowance.lt(request.amountBN)) {
|
||||
let e = Erc20Factory.connect(request.asset, request.signer);
|
||||
setProgress({
|
||||
message: `Waiting for ${walletName} approval`,
|
||||
type: 'user',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
let res = await e.approve(
|
||||
programIds().wormhole.bridge,
|
||||
request.amountBN,
|
||||
);
|
||||
setProgress({
|
||||
message: 'Waiting for ETH transaction to be minted...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
await res.wait(1);
|
||||
setProgress({
|
||||
message: 'Approval on ETH succeeded!',
|
||||
type: 'done',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
} else {
|
||||
setProgress({
|
||||
message: 'Already approved on ETH!',
|
||||
type: 'done',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
setProgress({
|
||||
message: 'Approval failed!',
|
||||
type: 'error',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
|
||||
return steps.lock(request);
|
||||
},
|
||||
// locks assets in the bridge
|
||||
lock: async (request: TransferRequest) => {
|
||||
if (
|
||||
!request.amountBN ||
|
||||
!request.asset ||
|
||||
!request.signer ||
|
||||
!request.recipient ||
|
||||
!request.toChain ||
|
||||
!request.info ||
|
||||
!request.nonce
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let group = 'Lock assets';
|
||||
|
||||
try {
|
||||
let wh = WormholeFactory.connect(
|
||||
programIds().wormhole.bridge,
|
||||
request.signer,
|
||||
);
|
||||
setProgress({
|
||||
message: `Waiting for ${walletName} transfer approval`,
|
||||
type: 'user',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
let res = await wh.lockAssets(
|
||||
request.asset,
|
||||
request.amountBN,
|
||||
request.recipient,
|
||||
request.toChain,
|
||||
request.nonce,
|
||||
false,
|
||||
);
|
||||
setProgress({
|
||||
message: 'Waiting for ETH transaction to be minted...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
await res.wait(1);
|
||||
setProgress({
|
||||
message: 'Transfer on ETH succeeded!',
|
||||
type: 'done',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
} catch (err) {
|
||||
setProgress({
|
||||
message: 'Transfer failed!',
|
||||
type: 'error',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
|
||||
return steps.wait(request);
|
||||
},
|
||||
wait: async (request: TransferRequest) => {
|
||||
let startBlock = provider.blockNumber;
|
||||
let completed = false;
|
||||
let group = 'Finalizing transfer';
|
||||
|
||||
const ethConfirmationMessage = (current: number) =>
|
||||
`Awaiting ETH confirmations: ${current} out of 15`;
|
||||
|
||||
setProgress({
|
||||
message: ethConfirmationMessage(0),
|
||||
type: 'wait',
|
||||
step: counter++,
|
||||
group,
|
||||
});
|
||||
|
||||
let blockHandler = (blockNumber: number) => {
|
||||
let passedBlocks = blockNumber - startBlock;
|
||||
const isLast = passedBlocks === 14;
|
||||
if (passedBlocks < 15) {
|
||||
setProgress({
|
||||
message: ethConfirmationMessage(passedBlocks),
|
||||
type: isLast ? 'done' : 'wait',
|
||||
step: counter++,
|
||||
group,
|
||||
replace: passedBlocks > 0,
|
||||
});
|
||||
|
||||
if (isLast) {
|
||||
setProgress({
|
||||
message: 'Awaiting completion on Solana...',
|
||||
type: 'wait',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
}
|
||||
} else if (!completed) {
|
||||
provider.removeListener('block', blockHandler);
|
||||
}
|
||||
};
|
||||
provider.on('block', blockHandler);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!request.recipient) {
|
||||
return;
|
||||
}
|
||||
|
||||
let accountChangeListener = connection.onAccountChange(
|
||||
new PublicKey(request.recipient),
|
||||
() => {
|
||||
if (completed) return;
|
||||
|
||||
completed = true;
|
||||
provider.removeListener('block', blockHandler);
|
||||
connection.removeAccountChangeListener(accountChangeListener);
|
||||
setProgress({
|
||||
message: 'Transfer completed on Solana',
|
||||
type: 'info',
|
||||
group,
|
||||
step: counter++,
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
'single',
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return steps.transfer(request);
|
||||
};
|
Loading…
Reference in New Issue