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 React, { useState } from 'react';
|
||||||
import { NumericInput, programIds } from '@oyster/common';
|
import { NumericInput } from '@oyster/common';
|
||||||
import { Card, Select } from 'antd';
|
import { Card, Select } from 'antd';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import { useEthereum } from '../../contexts';
|
import { useEthereum } from '../../contexts';
|
||||||
import { WrappedAssetFactory } from '../../contracts/WrappedAssetFactory';
|
|
||||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
|
||||||
import { TransferRequestInfo } from '../../models/bridge';
|
|
||||||
import { TokenDisplay } from '../TokenDisplay';
|
import { TokenDisplay } from '../TokenDisplay';
|
||||||
import BN from 'bn.js';
|
|
||||||
import { ASSET_CHAIN } from '../../models/bridge/constants';
|
import { ASSET_CHAIN } from '../../models/bridge/constants';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
export function EthereumInput(props: {
|
export function EthereumInput(props: {
|
||||||
title: string;
|
title: string;
|
||||||
hideBalance?: boolean;
|
balance?: number;
|
||||||
|
|
||||||
asset?: string;
|
asset?: string;
|
||||||
chain?: ASSET_CHAIN;
|
chain?: ASSET_CHAIN;
|
||||||
setAsset: (asset: string) => void;
|
setAsset: (asset: string) => void;
|
||||||
|
|
||||||
setInfo: (info: TransferRequestInfo) => void;
|
|
||||||
amount?: number | null;
|
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 [lastAmount, setLastAmount] = useState<string>('');
|
||||||
const { tokens, provider } = useEthereum();
|
const { tokens } = useEthereum();
|
||||||
|
|
||||||
const renderReserveAccounts = tokens
|
const renderReserveAccounts = tokens
|
||||||
.filter(t => (t.tags?.indexOf('longList') || -1) < 0)
|
.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 (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="ccy-input from-input"
|
className="ccy-input from-input"
|
||||||
|
@ -111,12 +55,12 @@ export function EthereumInput(props: {
|
||||||
<div className="ccy-input-header">
|
<div className="ccy-input-header">
|
||||||
<div className="ccy-input-header-left">{props.title}</div>
|
<div className="ccy-input-header-left">{props.title}</div>
|
||||||
|
|
||||||
{!props.hideBalance && (
|
{!!props.balance && (
|
||||||
<div
|
<div
|
||||||
className="ccy-input-header-right"
|
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,7 +76,7 @@ export function EthereumInput(props: {
|
||||||
}
|
}
|
||||||
onChange={(val: string) => {
|
onChange={(val: string) => {
|
||||||
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
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));
|
else props.onInputChange(parseFloat(val));
|
||||||
}
|
}
|
||||||
setLastAmount(val);
|
setLastAmount(val);
|
||||||
|
@ -153,7 +97,7 @@ export function EthereumInput(props: {
|
||||||
placeholder="CCY"
|
placeholder="CCY"
|
||||||
value={props.asset}
|
value={props.asset}
|
||||||
onChange={(item: string) => {
|
onChange={(item: string) => {
|
||||||
updateBalance(item);
|
props.setAsset(item);
|
||||||
}}
|
}}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Card, notification, Spin, Button } from 'antd';
|
import { notification, Spin, Button } from 'antd';
|
||||||
import { TokenInfo } from '@uniswap/token-lists';
|
|
||||||
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
||||||
import { LABELS } from '../../constants';
|
|
||||||
import {
|
import {
|
||||||
contexts,
|
contexts,
|
||||||
utils,
|
|
||||||
ConnectButton,
|
ConnectButton,
|
||||||
programIds,
|
programIds,
|
||||||
formatAmount,
|
formatAmount,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
|
||||||
import { EthereumInput } from './../Input';
|
import { EthereumInput } from './../Input';
|
||||||
|
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import { ethers } from 'ethers';
|
|
||||||
import { ASSET_CHAIN, chainToName } from '../../utils/assets';
|
import { ASSET_CHAIN, chainToName } from '../../utils/assets';
|
||||||
import { BigNumber } from 'ethers/utils';
|
import { ProgressUpdate, toSolana, TransferRequest } from '../../models/bridge';
|
||||||
import { Erc20Factory } from '../../contracts/Erc20Factory';
|
|
||||||
import { ProgressUpdate, transfer, TransferRequest } from '../../models/bridge';
|
|
||||||
import { useEthereum } from '../../contexts';
|
import { useEthereum } from '../../contexts';
|
||||||
import { TokenDisplay } from './../TokenDisplay';
|
import { TokenDisplay } from './../TokenDisplay';
|
||||||
|
import { WrappedAssetFactory } from '../../contracts/WrappedAssetFactory';
|
||||||
|
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
|
||||||
const { useConnection } = contexts.Connection;
|
const { useConnection } = contexts.Connection;
|
||||||
const { useWallet } = contexts.Wallet;
|
const { useWallet } = contexts.Wallet;
|
||||||
|
@ -51,23 +46,75 @@ export const Transfer = () => {
|
||||||
toChain: ASSET_CHAIN.Solana,
|
toChain: ASSET_CHAIN.Solana,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setAssetInformation = (asset: string) => {
|
const setAssetInformation = async (asset: string) => {
|
||||||
setRequest({
|
setRequest({
|
||||||
...request,
|
...request,
|
||||||
asset,
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="exchange-card">
|
<div className="exchange-card">
|
||||||
<EthereumInput
|
<EthereumInput
|
||||||
title="From Ethereum"
|
title={`From ${chainToName(request.from)}`}
|
||||||
setInfo={info => {
|
|
||||||
request.info = info;
|
|
||||||
}}
|
|
||||||
asset={request.asset}
|
asset={request.asset}
|
||||||
chain={request.from}
|
chain={request.from}
|
||||||
|
balance={request.info?.balanceAsNumber || 0 }
|
||||||
setAsset={asset => setAssetInformation(asset)}
|
setAsset={asset => setAssetInformation(asset)}
|
||||||
amount={request.amount}
|
amount={request.amount}
|
||||||
onInputChange={amount => {
|
onInputChange={amount => {
|
||||||
|
@ -94,13 +141,9 @@ export const Transfer = () => {
|
||||||
⇅
|
⇅
|
||||||
</Button>
|
</Button>
|
||||||
<EthereumInput
|
<EthereumInput
|
||||||
title="To Solana"
|
title={`To ${chainToName(request.toChain)}`}
|
||||||
setInfo={info => {
|
|
||||||
request.info = info;
|
|
||||||
}}
|
|
||||||
asset={request.asset}
|
asset={request.asset}
|
||||||
chain={request.toChain}
|
chain={request.toChain}
|
||||||
hideBalance={true}
|
|
||||||
setAsset={asset => setAssetInformation(asset)}
|
setAsset={asset => setAssetInformation(asset)}
|
||||||
amount={request.amount}
|
amount={request.amount}
|
||||||
onInputChange={amount => {
|
onInputChange={amount => {
|
||||||
|
@ -126,22 +169,24 @@ export const Transfer = () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
let steps: ProgressUpdate[] = [];
|
let steps: ProgressUpdate[] = [];
|
||||||
try {
|
try {
|
||||||
await transfer(
|
if(request.toChain === ASSET_CHAIN.Solana) {
|
||||||
connection,
|
await toSolana(
|
||||||
wallet,
|
connection,
|
||||||
request,
|
wallet,
|
||||||
provider,
|
request,
|
||||||
update => {
|
provider,
|
||||||
if (update.replace) {
|
update => {
|
||||||
steps.pop();
|
if (update.replace) {
|
||||||
steps = [...steps, update];
|
steps.pop();
|
||||||
} else {
|
steps = [...steps, update];
|
||||||
steps = [...steps, update];
|
} else {
|
||||||
}
|
steps = [...steps, update];
|
||||||
|
}
|
||||||
|
|
||||||
setActiveSteps(steps);
|
setActiveSteps(steps);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// TODO...
|
// TODO...
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import {
|
import {
|
||||||
contexts,
|
|
||||||
utils,
|
|
||||||
programIds,
|
programIds,
|
||||||
WalletAdapter,
|
WalletAdapter,
|
||||||
getMultipleAccounts,
|
getMultipleAccounts,
|
||||||
|
@ -8,66 +6,26 @@ import {
|
||||||
cache,
|
cache,
|
||||||
TokenAccountParser,
|
TokenAccountParser,
|
||||||
ParsedAccount,
|
ParsedAccount,
|
||||||
formatNumber,
|
|
||||||
formatAmount,
|
formatAmount,
|
||||||
createAssociatedTokenAccountInstruction,
|
createAssociatedTokenAccountInstruction,
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
|
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { ASSET_CHAIN } from '../../utils/assets';
|
import { ASSET_CHAIN } from '../../../utils/assets';
|
||||||
import { BigNumber } from 'ethers/utils';
|
import { BigNumber } from 'ethers/utils';
|
||||||
import { Erc20Factory } from '../../contracts/Erc20Factory';
|
import { Erc20Factory } from '../../../contracts/Erc20Factory';
|
||||||
import { WormholeFactory } from '../../contracts/WormholeFactory';
|
import { WormholeFactory } from '../../../contracts/WormholeFactory';
|
||||||
import { AssetMeta, createWrappedAssetInstruction } from './meta';
|
import { AssetMeta, createWrappedAssetInstruction } from './../meta';
|
||||||
import { bridgeAuthorityKey, wrappedAssetMintKey } from './helpers';
|
import { bridgeAuthorityKey, wrappedAssetMintKey } from './../helpers';
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
Connection,
|
Connection,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
} from '@solana/web3.js';
|
} 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 {
|
export const fromSolana = async (
|
||||||
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 (
|
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
wallet: WalletAdapter,
|
wallet: WalletAdapter,
|
||||||
request: TransferRequest,
|
request: TransferRequest,
|
||||||
|
@ -77,7 +35,6 @@ export const transfer = async (
|
||||||
if (!request.asset) {
|
if (!request.asset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const walletName = 'MetaMask';
|
const walletName = 'MetaMask';
|
||||||
request.signer = provider?.getSigner();
|
request.signer = provider?.getSigner();
|
||||||
|
|
||||||
|
@ -384,8 +341,6 @@ export const transfer = async (
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
//
|
|
||||||
vaa: async (request: TransferRequest) => {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return steps.transfer(request);
|
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