mirror of https://github.com/certusone/oyster.git
identify wrapped assets from eth (#105)
This commit is contained in:
parent
9f2ceaafb5
commit
3e142b3ef1
|
@ -22,6 +22,7 @@
|
|||
"@solana/wallet-base": "0.0.1",
|
||||
"@solana/wallet-ledger": "0.0.1",
|
||||
"@solana/web3.js": "^1.5.0",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"bn.js": "^5.1.3",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer-layout": "^1.2.1",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { BigNumber } from 'ethers/utils';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import {ethers} from "ethers";
|
||||
import { ASSET_CHAIN } from '../constants';
|
||||
|
||||
export interface ProgressUpdate {
|
||||
|
@ -14,7 +15,7 @@ export interface TransferRequestInfo {
|
|||
name: string;
|
||||
balance: BigNumber;
|
||||
decimals: number;
|
||||
allowance: BigNumber;
|
||||
allowance: ethers.utils.BigNumber;
|
||||
isWrapped: boolean;
|
||||
chainID: number;
|
||||
assetAddress: Buffer;
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
import { AccountInfo } from '@solana/spl-token';
|
||||
import { TransferRequest, ProgressUpdate } from './interface';
|
||||
import { WalletAdapter } from '@solana/wallet-base';
|
||||
import { BigNumber } from "bignumber.js";
|
||||
|
||||
export const toSolana = async (
|
||||
connection: Connection,
|
||||
|
@ -39,6 +40,9 @@ export const toSolana = async (
|
|||
signer.getAddress(),
|
||||
'pending',
|
||||
);
|
||||
const amountBigNumber = new BigNumber(request.amount.toString()).toFormat(
|
||||
request.info.decimals,
|
||||
);
|
||||
|
||||
const amountBN = ethers.utils.parseUnits(
|
||||
request.amount.toString(),
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
"@web3-react/walletlink-connector": "^6.0.9",
|
||||
"@welldone-software/why-did-you-render": "^6.0.5",
|
||||
"animejs": "^3.2.1",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"bn.js": "^5.1.3",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer-layout": "^1.2.0",
|
||||
|
|
|
@ -40,7 +40,7 @@ export function Input(props: {
|
|||
props.onInputChange && props.onInputChange(props.balance)
|
||||
}
|
||||
>
|
||||
{props.balance}
|
||||
{props.balance?.toFixed(6)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-container">
|
||||
|
|
|
@ -20,8 +20,8 @@ import {
|
|||
filterModalSolTokens,
|
||||
} from '../utils/assets';
|
||||
import { useEthereum } from './ethereum';
|
||||
import { BigNumber } from 'ethers/utils';
|
||||
import { WrappedAssetFactory } from '@solana/bridge-sdk';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { AssetMeta, WrappedAssetFactory } from '@solana/bridge-sdk';
|
||||
import { WormholeFactory } from '@solana/bridge-sdk';
|
||||
import {
|
||||
bridgeAuthorityKey,
|
||||
|
@ -30,6 +30,7 @@ import {
|
|||
} from '@solana/bridge-sdk';
|
||||
import { useBridge } from './bridge';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
export interface TokenChainContextState {
|
||||
info?: TransferRequestInfo;
|
||||
|
@ -108,7 +109,7 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
name: '',
|
||||
balance: new BigNumber(0),
|
||||
decimals: 0,
|
||||
allowance: new BigNumber(0),
|
||||
allowance: new ethers.utils.BigNumber(0),
|
||||
isWrapped: false,
|
||||
chainID: 0,
|
||||
assetAddress: new Buffer(0),
|
||||
|
@ -121,43 +122,69 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
|
||||
(async () => {
|
||||
const ethToken = ethTokens.find(t => t.address === mintAddress);
|
||||
const solToken = solTokens.find(t => t.address === mintAddress);
|
||||
let solToken = solTokens.find(t => t.address === mintAddress);
|
||||
let mintKeyAddress = '';
|
||||
let symbol = '';
|
||||
let decimals = 0;
|
||||
|
||||
// eth assets on eth chain
|
||||
// eth asset on sol chain
|
||||
// sol asset on eth chain
|
||||
// sol asset on sol chain
|
||||
|
||||
//let ethAddress: string = '';
|
||||
// console.log({ chain, solToken, ethToken });
|
||||
//console.log({ chain, solToken, ethToken });
|
||||
if (chain === ASSET_CHAIN.Solana) {
|
||||
if (!solToken) {
|
||||
if (!solToken && ethToken) {
|
||||
try {
|
||||
const bridgeId = programIds().wormhole.pubkey;
|
||||
const authority = await bridgeAuthorityKey(bridgeId);
|
||||
const assetAddress = Buffer.from(ethToken.address.slice(2), 'hex');
|
||||
const meta: AssetMeta = {
|
||||
decimals: Math.min(ethToken.decimals, 9),
|
||||
address: assetAddress,
|
||||
chain: ASSET_CHAIN.Ethereum,
|
||||
};
|
||||
const mintKey = await wrappedAssetMintKey(
|
||||
bridgeId,
|
||||
authority,
|
||||
meta,
|
||||
);
|
||||
if (mintKey) {
|
||||
mintKeyAddress = mintKey.toBase58();
|
||||
solToken = solTokens.find(t => t.address === mintKeyAddress);
|
||||
if (!solToken) {
|
||||
symbol = ethToken.symbol;
|
||||
decimals = ethToken.decimals;
|
||||
}
|
||||
} else {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!solToken && (!symbol || !mintKeyAddress || !decimals)) {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
// TODO: checked if mint is wrapped mint from eth...
|
||||
const currentAccount = userAccounts?.find(
|
||||
a => a.info.mint.toBase58() === solToken.address,
|
||||
a => a.info.mint.toBase58() === (solToken?.address || mintKeyAddress),
|
||||
);
|
||||
const assetMeta = await bridge?.fetchAssetMeta(
|
||||
new PublicKey(solToken.address),
|
||||
new PublicKey(solToken?.address || mintKeyAddress),
|
||||
);
|
||||
|
||||
if (!assetMeta || !currentAccount) {
|
||||
setInfo(defaultCoinInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
let info = {
|
||||
address: currentAccount.pubkey.toBase58(),
|
||||
name: solToken.symbol,
|
||||
name: solToken?.symbol || symbol,
|
||||
balance: new BigNumber(currentAccount?.info.amount.toNumber() || 0),
|
||||
allowance: new BigNumber(0),
|
||||
decimals: solToken.decimals,
|
||||
allowance: new ethers.utils.BigNumber(0),
|
||||
decimals: solToken?.decimals || decimals,
|
||||
isWrapped: assetMeta.chain != ASSET_CHAIN.Solana,
|
||||
chainID: assetMeta.chain,
|
||||
assetAddress: assetMeta.address,
|
||||
mint: solToken.address,
|
||||
mint: solToken?.address || mintKeyAddress,
|
||||
};
|
||||
setInfo(info);
|
||||
}
|
||||
|
@ -197,7 +224,9 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
}
|
||||
|
||||
if (chain === ASSET_CHAIN.Ethereum) {
|
||||
info.balance = await e.balanceOf(addr);
|
||||
info.balance = new BigNumber(
|
||||
new ethers.utils.BigNumber(await e.balanceOf(addr)).toString(),
|
||||
);
|
||||
} else {
|
||||
// TODO: get balance on other chains for assets that came from eth
|
||||
|
||||
|
@ -211,7 +240,7 @@ export const useCurrencyLeg = (mintAddress: string) => {
|
|||
});
|
||||
}
|
||||
|
||||
// console.log({ info });
|
||||
//console.log({ info });
|
||||
setInfo(info);
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
programIds,
|
||||
notify,
|
||||
useWallet,
|
||||
ParsedAccountBase,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
WORMHOLE_PROGRAM_ID,
|
||||
|
@ -29,11 +30,12 @@ import {
|
|||
COINGECKO_POOL_INTERVAL,
|
||||
useCoingecko,
|
||||
} from '../contexts/coingecko';
|
||||
import { BN } from 'bn.js';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { WormholeFactory } from '@solana/bridge-sdk';
|
||||
import { ethers } from 'ethers';
|
||||
import { useBridge } from '../contexts/bridge';
|
||||
import { SolanaBridge } from '@solana/bridge-sdk';
|
||||
import BN from 'bn.js';
|
||||
|
||||
type WrappedTransferMeta = {
|
||||
chain: number;
|
||||
|
@ -47,7 +49,7 @@ type WrappedTransferMeta = {
|
|||
symbol?: string;
|
||||
amount: number;
|
||||
value?: number | string;
|
||||
txhash?: string;
|
||||
txhash: string;
|
||||
date: number; // timestamp
|
||||
status?: string;
|
||||
owner?: string;
|
||||
|
@ -55,6 +57,8 @@ type WrappedTransferMeta = {
|
|||
vaa?: any;
|
||||
};
|
||||
|
||||
const transferCache = new Map<string, WrappedTransferMeta>();
|
||||
|
||||
const queryWrappedMetaTransactions = async (
|
||||
authorityKey: PublicKey,
|
||||
connection: Connection,
|
||||
|
@ -101,14 +105,16 @@ const queryWrappedMetaTransactions = async (
|
|||
assetAddress = new PublicKey(metaTransfer.assetAddress).toBase58();
|
||||
}
|
||||
|
||||
const dec = new BN(10).pow(new BN(metaTransfer.assetDecimals));
|
||||
const rawAmount = new BN(metaTransfer.amount, 2, 'le');
|
||||
const div = rawAmount.div(dec).toNumber();
|
||||
const mod = rawAmount.mod(dec).toNumber();
|
||||
const amount = parseFloat(div + '.' + mod.toString());
|
||||
const dec = new BigNumber(10).pow(
|
||||
new BigNumber(metaTransfer.assetDecimals),
|
||||
);
|
||||
const rawAmount = new BigNumber(
|
||||
new BN(metaTransfer.amount, 2, 'le').toString(),
|
||||
);
|
||||
const amount = rawAmount.div(dec).toNumber();
|
||||
const txhash = acc.publicKey.toBase58();
|
||||
|
||||
transfers.set(assetAddress, {
|
||||
transfers.set(txhash, {
|
||||
publicKey: acc.publicKey,
|
||||
amount,
|
||||
date: metaTransfer.vaaTime,
|
||||
|
@ -125,81 +131,97 @@ const queryWrappedMetaTransactions = async (
|
|||
|
||||
await Promise.all(
|
||||
[...transfers.values()].map(async transfer => {
|
||||
const resp = await (connection as any)._rpcRequest(
|
||||
'getConfirmedSignaturesForAddress2',
|
||||
[transfer.publicKey.toBase58()],
|
||||
);
|
||||
|
||||
for (const sig of resp.result) {
|
||||
const confirmedTx = await connection.getParsedConfirmedTransaction(
|
||||
sig.signature,
|
||||
'finalized',
|
||||
const cachedTransfer = transferCache.get(transfer.txhash);
|
||||
if (cachedTransfer && cachedTransfer.status === 'Completed') {
|
||||
transfer.vaa = cachedTransfer.vaa;
|
||||
transfer.status = cachedTransfer.status;
|
||||
transfer.owner = cachedTransfer.owner;
|
||||
} else {
|
||||
const resp = await (connection as any)._rpcRequest(
|
||||
'getConfirmedSignaturesForAddress2',
|
||||
[transfer.publicKey.toBase58()],
|
||||
);
|
||||
if (!confirmedTx) continue;
|
||||
const instructions = confirmedTx.transaction?.message?.instructions;
|
||||
const filteredInstructions = instructions?.filter(ins => {
|
||||
return ins.programId.toBase58() === WORMHOLE_PROGRAM_ID.toBase58();
|
||||
});
|
||||
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 &&
|
||||
confirmedTx.meta?.err == null &&
|
||||
bridge
|
||||
) {
|
||||
const lockup = transfer.lockup;
|
||||
let vaa = lockup.vaa;
|
||||
for (let i = vaa.length; i > 0; i--) {
|
||||
if (vaa[i] == 0xff) {
|
||||
vaa = vaa.slice(0, i);
|
||||
break;
|
||||
for (const sig of resp.result) {
|
||||
const confirmedTx = await connection.getParsedConfirmedTransaction(
|
||||
sig.signature,
|
||||
'finalized',
|
||||
);
|
||||
if (!confirmedTx) continue;
|
||||
const instructions = confirmedTx.transaction?.message?.instructions;
|
||||
const filteredInstructions = instructions?.filter(ins => {
|
||||
return ins.programId.toBase58() === WORMHOLE_PROGRAM_ID.toBase58();
|
||||
});
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
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>()),
|
||||
);
|
||||
if (
|
||||
data[0] === POSTVAA_INSTRUCTION &&
|
||||
confirmedTx.meta?.err == null &&
|
||||
bridge
|
||||
) {
|
||||
const lockup = transfer.lockup;
|
||||
let vaa = lockup.vaa;
|
||||
for (let i = vaa.length; i > 0; i--) {
|
||||
if (vaa[i] == 0xff) {
|
||||
vaa = vaa.slice(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
let signatures = await bridge.fetchSignatureStatus(
|
||||
lockup.signatureAccount,
|
||||
);
|
||||
let sigData = Buffer.of(
|
||||
...signatures.reduce((previousValue, currentValue) => {
|
||||
previousValue.push(currentValue.index);
|
||||
previousValue.push(...currentValue.signature);
|
||||
|
||||
vaa = Buffer.concat([
|
||||
vaa.slice(0, 5),
|
||||
Buffer.of(signatures.length),
|
||||
sigData,
|
||||
vaa.slice(6),
|
||||
]);
|
||||
try {
|
||||
if (vaa?.length) {
|
||||
const _ = await wh.parseAndVerifyVAA(vaa);
|
||||
transfer.status = 'Failed';
|
||||
transfer.vaa = vaa;
|
||||
//TODO: handle vaa not posted
|
||||
//console.log({ result });
|
||||
} else {
|
||||
return previousValue;
|
||||
}, new Array<number>()),
|
||||
);
|
||||
|
||||
vaa = Buffer.concat([
|
||||
vaa.slice(0, 5),
|
||||
Buffer.of(signatures.length),
|
||||
sigData,
|
||||
vaa.slice(6),
|
||||
]);
|
||||
try {
|
||||
if (vaa?.length) {
|
||||
const _ = await wh.parseAndVerifyVAA(vaa);
|
||||
transfer.status = 'Failed';
|
||||
transfer.vaa = vaa;
|
||||
//TODO: handle vaa not posted
|
||||
//console.log({ result });
|
||||
} else {
|
||||
transfer.status = 'Error';
|
||||
transfer.vaa = vaa;
|
||||
//TODO: handle empty data
|
||||
//console.log({ vaa });
|
||||
}
|
||||
} catch (e) {
|
||||
//console.log({ error: e });
|
||||
transfer.vaa = vaa;
|
||||
transfer.status = 'Completed';
|
||||
transferCache.set(transfer.txhash, transfer);
|
||||
}
|
||||
} catch (e) {
|
||||
transfer.status = 'Error';
|
||||
transfer.vaa = vaa;
|
||||
//TODO: handle empty data
|
||||
//console.log({ vaa });
|
||||
//TODO: handle error
|
||||
}
|
||||
} catch (e) {
|
||||
//console.log({ error: e });
|
||||
transfer.vaa = vaa;
|
||||
transfer.status = 'Completed';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export const chainToName = (chain?: ASSET_CHAIN) => {
|
|||
};
|
||||
|
||||
const EXCLUDED_COMMON_TOKENS = ['usdt', 'usdc'];
|
||||
const EXCLUDED_SPL_TOKENS = ['sol', ...EXCLUDED_COMMON_TOKENS];
|
||||
const EXCLUDED_SPL_TOKENS = ['sol', 'srm', ...EXCLUDED_COMMON_TOKENS];
|
||||
|
||||
export const filterModalSolTokens = (tokens: TokenInfo[]) => {
|
||||
return tokens.filter(
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"@types/react-router-dom": "^5.1.6",
|
||||
"@welldone-software/why-did-you-render": "^6.0.5",
|
||||
"antd": "^4.6.6",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"bn.js": "^5.1.3",
|
||||
"borsh": "^0.3.1",
|
||||
"bs58": "^4.0.1",
|
||||
|
|
Loading…
Reference in New Issue