diff --git a/apps/ui/.eslintrc.cjs b/apps/ui/.eslintrc.cjs index 9eb2bca13..7d62d6d22 100644 --- a/apps/ui/.eslintrc.cjs +++ b/apps/ui/.eslintrc.cjs @@ -8,7 +8,7 @@ module.exports = { extends: ["@swim-io/eslint-config"], }, { - files: ["**/*.{cts,mts,ts,tsx}"], + files: ["src/**/*.{cts,mts,ts,tsx}"], extends: ["@swim-io/eslint-config/react"], parserOptions: { // Make sure correct `tsconfig.json` is found in monorepo @@ -85,5 +85,13 @@ module.exports = { }, ], }, + { + files: ["scripts/*.{cts,mts,ts,tsx}"], + extends: ["@swim-io/eslint-config"], + parserOptions: { + // Make sure correct `tsconfig.json` is found in monorepo + tsconfigRootDir: `${__dirname}/scripts`, + }, + }, ], }; diff --git a/apps/ui/.gitignore b/apps/ui/.gitignore index 567609b12..34761e9cf 100644 --- a/apps/ui/.gitignore +++ b/apps/ui/.gitignore @@ -1 +1,2 @@ build/ +tmp/ diff --git a/apps/ui/.prettierignore b/apps/ui/.prettierignore index 6a52b36fa..722517a76 100644 --- a/apps/ui/.prettierignore +++ b/apps/ui/.prettierignore @@ -1,4 +1,5 @@ # ignore auto-generated files build/ storybook-static/ +src/config/wormholeTokens.json src/keys/**/*.json diff --git a/apps/ui/package.json b/apps/ui/package.json index a87d9fba5..d23fc67e8 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -3,6 +3,7 @@ "private": true, "version": "0.1.0", "scripts": { + "scrape-wormhole-tokens": "./scripts/scrapeWormholeTokenData.sh", "start": "NODE_ENV=development SKIP_PREFLIGHT_CHECK=true DISABLE_ESLINT_PLUGIN=true craco start", "build": "SKIP_PREFLIGHT_CHECK=true DISABLE_ESLINT_PLUGIN=true craco build", "release": "SENTRY_RELEASE=$(git rev-parse --short HEAD) yarn workspaces foreach --topological-dev --parallel --recursive --from @swim-io/ui run build", @@ -50,16 +51,16 @@ "@sentry/types": "^7.9.0", "@solana/spl-token": "^0.3.5", "@solana/web3.js": "^1.62.0", - "@swim-io/aptos": "^0.40.0", - "@swim-io/core": "^0.40.0", - "@swim-io/evm": "^0.40.0", - "@swim-io/evm-contracts": "^0.40.0", - "@swim-io/pool-math": "^0.40.0", - "@swim-io/solana": "^0.40.0", - "@swim-io/solana-contracts": "^0.40.0", - "@swim-io/token-projects": "^0.40.0", - "@swim-io/utils": "^0.40.0", - "@swim-io/wormhole": "^0.40.0", + "@swim-io/aptos": "workspace:^", + "@swim-io/core": "workspace:^", + "@swim-io/evm": "workspace:^", + "@swim-io/evm-contracts": "workspace:^", + "@swim-io/pool-math": "workspace:^", + "@swim-io/solana": "workspace:^", + "@swim-io/solana-contracts": "workspace:^", + "@swim-io/token-projects": "workspace:^", + "@swim-io/utils": "workspace:^", + "@swim-io/wormhole": "workspace:^", "bn.js": "^5.2.1", "classnames": "^2.3.1", "decimal.js": "^10.3.1", @@ -113,6 +114,7 @@ "@types/react-dom": "^17.0.0", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", + "csv-parse": "^5.3.1", "eslint": "^8.18.0", "eslint-config-prettier": "^8.5.0", "eslint-config-react-app": "^7.0.1", @@ -129,6 +131,7 @@ "jsonc-eslint-parser": "^2.1.0", "prettier": "^2.7.1", "storybook-preset-craco": "^0.0.6", + "ts-node": "^10.9.1", "typescript": "~4.8.4", "webpack": "4.44.2" } diff --git a/apps/ui/scripts/processWormholeTokenData.ts b/apps/ui/scripts/processWormholeTokenData.ts new file mode 100644 index 000000000..36a8c8c4e --- /dev/null +++ b/apps/ui/scripts/processWormholeTokenData.ts @@ -0,0 +1,206 @@ +import fs from "fs"; + +import type { ChainId } from "@certusone/wormhole-sdk"; +import { CHAINS, isEVMChain } from "@certusone/wormhole-sdk"; +import { parse } from "csv-parse"; + +type Source = + | "sol" + | "eth" + | "bsc" + | "terra" + | "matic" + | "avax" + | "oasis" + | "algorand" + | "ftm" + | "aurora" + | "karura" + | "acala" + | "klaytn" + | "celo" + | "near" + | "moonbeam" + | "terra2"; + +interface WrappedDetails { + readonly solAddress: string; + readonly solDecimals: string; + readonly ethAddress: string; + readonly ethDecimals: string; + readonly bscAddress: string; + readonly bscDecimals: string; + readonly terraAddress: string; + readonly terraDecimals: string; + readonly maticAddress: string; + readonly maticDecimals: string; + readonly avaxAddress: string; + readonly avaxDecimals: string; + readonly oasisAddress: string; + readonly oasisDecimals: string; + readonly algorandAddress: string; + readonly algorandDecimals: string; + readonly ftmAddress: string; + readonly ftmDecimals: string; + readonly auroraAddress: string; + readonly auroraDecimals: string; + readonly karuraAddress: string; + readonly karuraDecimals: string; + readonly acalaAddress: string; + readonly acalaDecimals: string; + readonly klaytnAddress: string; + readonly klaytnDecimals: string; + readonly celoAddress: string; + readonly celoDecimals: string; + readonly nearAddress: string; + readonly nearDecimals: string; + readonly moonbeamAddress: string; + readonly moonbeamDecimals: string; + readonly terra2Address: string; + readonly terra2Decimals: string; +} + +interface CsvRecord extends WrappedDetails { + readonly source: Source; + readonly symbol: string; + readonly name: string; + readonly sourceAddress: string; + readonly sourceDecimals: string; + readonly coingeckoId: string; + readonly logo: string; +} + +interface WormholeTokenDetails { + readonly chainId: ChainId; + readonly address: string; + readonly decimals: number; +} + +interface WormholeToken { + readonly symbol: string; + readonly displayName: string; + readonly logo: string; + readonly coinGeckoId: string; + readonly nativeDetails: WormholeTokenDetails; + readonly wrappedDetails: readonly WormholeTokenDetails[]; +} + +const sourceToChainId: Record = { + sol: CHAINS.solana, + eth: CHAINS.ethereum, + bsc: CHAINS.bsc, + terra: CHAINS.terra, + matic: CHAINS.polygon, + avax: CHAINS.avalanche, + oasis: CHAINS.oasis, + algorand: CHAINS.algorand, + ftm: CHAINS.fantom, + aurora: CHAINS.aurora, + karura: CHAINS.karura, + acala: CHAINS.acala, + klaytn: CHAINS.klaytn, + celo: CHAINS.celo, + near: CHAINS.near, + moonbeam: CHAINS.moonbeam, + terra2: CHAINS.terra2, +}; + +const isChainSupported = (chainId: ChainId): boolean => + chainId === CHAINS.solana || isEVMChain(chainId); + +const supportedSourceToChainId = Object.entries(sourceToChainId).reduce< + Partial> +>( + (accumulator, [source, chainId]) => + isChainSupported(chainId) + ? { + ...accumulator, + [source]: chainId, + } + : accumulator, + {}, +); + +const processWrappedDetails = ( + wrappedDetails: WrappedDetails, +): readonly WormholeTokenDetails[] => + Object.entries(supportedSourceToChainId).reduce( + (accumulator: readonly WormholeTokenDetails[], [source, chainId]) => { + const address: string = + wrappedDetails[`${source}Address` as keyof WrappedDetails]; + const decimals: string = + wrappedDetails[`${source}Decimals` as keyof WrappedDetails]; + if (!address || !decimals) { + return accumulator; + } + return [ + ...accumulator, + { + chainId, + address, + decimals: parseInt(decimals, 10), + }, + ]; + }, + [], + ); + +const processRecord = ({ + source, + symbol, + name, + logo, + coingeckoId, + sourceAddress, + sourceDecimals, + ...wrappedDetails +}: CsvRecord): WormholeToken => ({ + symbol, + displayName: name, + logo, + coinGeckoId: coingeckoId, + nativeDetails: { + chainId: sourceToChainId[source], + address: sourceAddress, + decimals: parseInt(sourceDecimals, 10), + }, + wrappedDetails: processWrappedDetails(wrappedDetails), +}); + +const main = async () => { + const parser = fs + .createReadStream(`${__dirname}/../tmp/wormholeTokenData.csv`) + .pipe( + parse({ + bom: true, + columns: true, + }), + ); + + // eslint-disable-next-line functional/prefer-readonly-type + const processedRecords: WormholeToken[] = []; + for await (const record of parser) { + const processedRecord = processRecord(record as CsvRecord); + const supportedChains = [ + processedRecord.nativeDetails, + ...processedRecord.wrappedDetails, + ].filter((details) => isChainSupported(details.chainId)); + if (supportedChains.length >= 2) { + // eslint-disable-next-line functional/immutable-data + processedRecords.push(processedRecord); + } + } + + return new Promise((resolve, reject) => { + fs.writeFile( + `${__dirname}/../src/config/wormholeTokens.json`, + JSON.stringify(processedRecords), + (err) => (err ? reject(err) : resolve()), + ); + }); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/apps/ui/scripts/scrapeWormholeTokenData.sh b/apps/ui/scripts/scrapeWormholeTokenData.sh new file mode 100755 index 000000000..663247a0c --- /dev/null +++ b/apps/ui/scripts/scrapeWormholeTokenData.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +TMP_FILE="./tmp/wormholeTokenData.csv" + +mkdir -p tmp +wget -O "$TMP_FILE" "https://raw.githubusercontent.com/certusone/wormhole-token-list/main/content/by_source.csv" + +yarn ts-node --esm ./scripts/processWormholeTokenData.ts + +rm "$TMP_FILE" diff --git a/apps/ui/scripts/tsconfig.json b/apps/ui/scripts/tsconfig.json new file mode 100644 index 000000000..04746c616 --- /dev/null +++ b/apps/ui/scripts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@swim-io/tsconfig/tsconfig-base.json", + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "baseUrl": "./", + "noEmit": true + }, + "include": ["./"], + "ts-node": { + "compilerOptions": { + "module": "commonjs" + } + } +} diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index 9f4b05fcd..fde766901 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -23,6 +23,7 @@ import StakePage from "./pages/StakePage"; import SwapPage from "./pages/SwapPage"; import SwapPageV2 from "./pages/SwapPageV2"; import TosPage from "./pages/TosPage"; +import WormholePage from "./pages/WormholePage"; function App(): ReactElement { return ( @@ -64,6 +65,7 @@ function App(): ReactElement { {process.env.REACT_APP_ENABLE_POOL_RESTRUCTURE && ( } /> )} + } /> } /> } /> } /> diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx new file mode 100644 index 000000000..ddee5d793 --- /dev/null +++ b/apps/ui/src/components/WormholeForm.tsx @@ -0,0 +1,329 @@ +import type { ChainId, EVMChainId } from "@certusone/wormhole-sdk"; +import { + CHAIN_ID_ACALA, + CHAIN_ID_ARBITRUM, + CHAIN_ID_AURORA, + CHAIN_ID_AVAX, + CHAIN_ID_BSC, + CHAIN_ID_CELO, + CHAIN_ID_ETH, + CHAIN_ID_ETHEREUM_ROPSTEN, + CHAIN_ID_FANTOM, + CHAIN_ID_GNOSIS, + CHAIN_ID_KARURA, + CHAIN_ID_KLAYTN, + CHAIN_ID_MOONBEAM, + CHAIN_ID_NEON, + CHAIN_ID_OASIS, + CHAIN_ID_OPTIMISM, + CHAIN_ID_POLYGON, + CHAIN_ID_SOLANA, + CHAIN_ID_TO_NAME, + isEVMChain, +} from "@certusone/wormhole-sdk"; +import type { EuiSelectOption } from "@elastic/eui"; +import { + EuiButton, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, +} from "@elastic/eui"; +import { ERC20__factory } from "@swim-io/evm-contracts"; +import type { ReadonlyRecord } from "@swim-io/utils"; +import { findOrThrow } from "@swim-io/utils"; +import Decimal from "decimal.js"; +import { utils as ethersUtils } from "ethers"; +import type { FormEvent, ReactElement } from "react"; +import { useEffect, useMemo, useState } from "react"; +import type { UseQueryResult } from "react-query"; +import { useQuery } from "react-query"; + +import { wormholeTokens as rawWormholeTokens } from "../config"; +import { + useEvmWallet, + useUserSolanaTokenBalance, + useWormholeTransfer, +} from "../hooks"; +import type { TxResult, WormholeToken, WormholeTokenDetails } from "../models"; +import { generateId } from "../models"; + +import { EuiFieldIntlNumber } from "./EuiFieldIntlNumber"; + +const EVM_NETWORKS: ReadonlyRecord = { + [CHAIN_ID_ETH]: 1, + [CHAIN_ID_BSC]: 56, + [CHAIN_ID_POLYGON]: 137, + [CHAIN_ID_AVAX]: 43114, + [CHAIN_ID_OASIS]: 42262, + [CHAIN_ID_AURORA]: 1313161554, + [CHAIN_ID_FANTOM]: 250, + [CHAIN_ID_KARURA]: 686, + [CHAIN_ID_ACALA]: 787, + [CHAIN_ID_KLAYTN]: 8217, + [CHAIN_ID_CELO]: 42220, + [CHAIN_ID_MOONBEAM]: 1284, + [CHAIN_ID_NEON]: 245022934, + [CHAIN_ID_ARBITRUM]: 42161, + [CHAIN_ID_OPTIMISM]: 10, + [CHAIN_ID_GNOSIS]: 100, + [CHAIN_ID_ETHEREUM_ROPSTEN]: 3, +}; + +const getDetailsByChainId = ( + token: WormholeToken, + chainId: ChainId, +): WormholeTokenDetails | null => + [token.nativeDetails, ...token.wrappedDetails].find( + (details) => details.chainId === chainId, + ) ?? null; + +const useErc20BalanceQuery = ({ + chainId, + address, + decimals, +}: WormholeTokenDetails): UseQueryResult => { + const { wallet } = useEvmWallet(); + + return useQuery( + ["wormhole", "erc20Balance", chainId, address, wallet?.address], + async () => { + if (!wallet?.address || !isEVMChain(chainId)) { + return null; + } + const evmNetwork = EVM_NETWORKS[chainId]; + await wallet.switchNetwork(evmNetwork); + const { provider } = wallet.signer ?? {}; + if (!provider) { + return null; + } + const erc20Contract = ERC20__factory.connect(address, provider); + try { + const balance = await erc20Contract.balanceOf(wallet.address); + return new Decimal(ethersUtils.formatUnits(balance, decimals)); + } catch { + return new Decimal(0); + } + }, + {}, + ); +}; + +export const WormholeForm = (): ReactElement => { + const wormholeTokens = rawWormholeTokens as readonly WormholeToken[]; + const [currentTokenSymbol, setCurrentTokenSymbol] = useState( + wormholeTokens[0].symbol, + ); + const currentToken = findOrThrow( + wormholeTokens, + (token) => token.symbol === currentTokenSymbol, + ); + const tokenOptions: readonly EuiSelectOption[] = wormholeTokens.map( + (token) => ({ + value: token.symbol, + text: `${token.displayName} (${token.symbol})`, + selected: token.symbol === currentTokenSymbol, + }), + ); + + const sourceChains = useMemo( + () => [ + currentToken.nativeDetails.chainId, + ...currentToken.wrappedDetails.map(({ chainId }) => chainId), + ], + [currentToken], + ); + const [sourceChainId, setSourceChainId] = useState(sourceChains[0]); + const sourceChainOptions = sourceChains.map((chainId) => ({ + value: chainId, + text: CHAIN_ID_TO_NAME[chainId], + selected: chainId === sourceChainId, + })); + + const targetChains = useMemo( + () => sourceChains.filter((option) => option !== sourceChainId), + [sourceChains, sourceChainId], + ); + const [targetChainId, setTargetChainId] = useState(targetChains[0]); + const targetChainOptions = targetChains.map((chainId) => ({ + value: chainId, + text: CHAIN_ID_TO_NAME[chainId], + selected: chainId === targetChainId, + })); + + const [formInputAmount, setFormInputAmount] = useState(""); + const [inputAmount, setInputAmount] = useState(new Decimal(0)); + + const [txResults, setTxResults] = useState([]); + + const { mutateAsync: transfer, isLoading } = useWormholeTransfer(); + + const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); + if (sourceDetails === null) { + throw new Error("Missing source details"); + } + const targetDetails = getDetailsByChainId(currentToken, targetChainId); + const splBalance = useUserSolanaTokenBalance( + sourceChainId === CHAIN_ID_SOLANA ? sourceDetails : null, + { enabled: sourceChainId === CHAIN_ID_SOLANA }, + ); + const { data: erc20Balance = null } = useErc20BalanceQuery(sourceDetails); + + const handleTxResult = (txResult: TxResult): void => { + setTxResults((previousResults) => [...previousResults, txResult]); + }; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + (async (): Promise => { + if (targetDetails === null) { + throw new Error("Missing target details"); + } + setTxResults([]); + await transfer({ + interactionId: generateId(), + value: inputAmount, + sourceDetails, + targetDetails, + nativeDetails: currentToken.nativeDetails, + onTxResult: handleTxResult, + }); + })().catch(console.error); + }; + + useEffect(() => { + setSourceChainId(sourceChains[0]); + }, [sourceChains]); + + useEffect(() => { + if (targetChainId === sourceChainId) { + setTargetChainId(targetChains[0]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [targetChains]); + + return ( + + {/* These tables are only to show what data is available */} + + + + + + + + + + {currentToken.logo && ( + + + + + )} + + + + + + + + + + + + + + + + +
{"Symbol"}{currentToken.symbol}
{"Name"}{currentToken.displayName}
{"Logo"} + {currentToken.displayName} +
{"Source Chain"}{sourceChainId}
{"Balance"}{splBalance?.toString() ?? erc20Balance?.toString() ?? "-"}
{"Target Chain"}{targetChainId}
{"Loading?"}{isLoading.toString()}
+ {txResults.length > 0 && ( + <> +

{"Tx results"}

+ + + + + + {txResults.map(({ chainId, txId }) => ( + + + + + ))} +
{"Chain ID"}{"Tx ID"}
{chainId}{txId}
+ + )} + + + + + { + setCurrentTokenSymbol(event.target.value); + }} + /> + + + + + + { + const newSourceChainId = parseInt( + event.target.value, + 10, + ) as ChainId; + setSourceChainId(newSourceChainId); + }} + /> + + + + + + { + const newTargetChainId = parseInt( + event.target.value, + 10, + ) as ChainId; + setTargetChainId(newTargetChainId); + }} + /> + + + + + + { + setInputAmount(new Decimal(formInputAmount)); + }} + /> + + + + + + + {"Transfer"} + + +
+ ); +}; diff --git a/apps/ui/src/config/index.ts b/apps/ui/src/config/index.ts index 14ddfff90..21eec3032 100644 --- a/apps/ui/src/config/index.ts +++ b/apps/ui/src/config/index.ts @@ -18,6 +18,7 @@ export * from "./pools"; export * from "./tokens"; export * from "./utils"; export * from "./wormhole"; +export { default as wormholeTokens } from "./wormholeTokens.json"; export interface Config { readonly ecosystems: ReadonlyRecord; diff --git a/apps/ui/src/config/wormholeTokens.json b/apps/ui/src/config/wormholeTokens.json new file mode 100644 index 000000000..208961037 --- /dev/null +++ b/apps/ui/src/config/wormholeTokens.json @@ -0,0 +1 @@ +[{"symbol":"RAY","displayName":"Raydium (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R/logo.png","coinGeckoId":"raydium","nativeDetails":{"chainId":1,"address":"4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R","decimals":6},"wrappedDetails":[{"chainId":2,"address":"0xE617dd80c621a5072bD8cBa65E9d76c07327004d","decimals":6},{"chainId":4,"address":"0x13b6A55662f6591f8B8408Af1C73B017E32eEdB8","decimals":6}]},{"symbol":"SBR","displayName":"Saber (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1/logo.svg","coinGeckoId":"saber","nativeDetails":{"chainId":1,"address":"Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1","decimals":6},"wrappedDetails":[{"chainId":4,"address":"0x75344E5693ed5ecAdF4f292fFeb866c2cF8afCF1","decimals":6}]},{"symbol":"SOL","displayName":"SOL (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png","coinGeckoId":"solana","nativeDetails":{"chainId":1,"address":"So11111111111111111111111111111111111111112","decimals":9},"wrappedDetails":[{"chainId":2,"address":"0xD31a59c85aE9D8edEFeC411D448f90841571b89c","decimals":9},{"chainId":4,"address":"0xfA54fF1a158B5189Ebba6ae130CEd6bbd3aEA76e","decimals":9},{"chainId":5,"address":"0xd93f7e271cb87c23aaa73edc008a79646d1f9912","decimals":9},{"chainId":6,"address":"0xFE6B19286885a4F7F55AdAD09C3Cd1f906D2478F","decimals":9},{"chainId":7,"address":"0xd17dDAC91670274F7ba1590a06EcA0f2FD2b12bc","decimals":9},{"chainId":10,"address":"0xd99021C2A33e4Cf243010539c9e9b7c52E0236c1","decimals":9}]},{"symbol":"SRMso","displayName":"Serum (Portal from Solana)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/xnorPhAzWXUczCP3KjU5yDxmKKZi5cSbxytQ1LgE3kG/logo.png","coinGeckoId":"serum","nativeDetails":{"chainId":1,"address":"SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt","decimals":6},"wrappedDetails":[{"chainId":2,"address":"0xE3ADAA4fb7c92AB833Ee08B3561D9c434aA2A3eE","decimals":6},{"chainId":4,"address":"0x12BeffdCEcb547640DC30e1495E4B9cdc21922b4","decimals":6}]},{"symbol":"USDCso","displayName":"USD Coin (Portal from Solana)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png","coinGeckoId":"usd-coin","nativeDetails":{"chainId":1,"address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","decimals":6},"wrappedDetails":[{"chainId":2,"address":"0x41f7B8b9b897276b7AAE926a9016935280b44E97","decimals":6},{"chainId":4,"address":"0x91Ca579B0D47E5cfD5D0862c21D5659d39C8eCf0","decimals":6},{"chainId":5,"address":"0x576cf361711cd940cd9c397bb98c4c896cbd38de","decimals":6},{"chainId":6,"address":"0x0950Fc1AD509358dAeaD5eB8020a3c7d8b43b9DA","decimals":6},{"chainId":7,"address":"0x1d1149a53deB36F2836Ae7877c9176413aDfA4A8","decimals":6},{"chainId":10,"address":"0xb8398DA4FB3BC4306B9D9d9d13d9573e7d0E299f","decimals":6},{"chainId":9,"address":"0xDd1DaFedeBa5F9851C4F4a2876E0f3aF3c774B1A","decimals":6}]},{"symbol":"USDTso","displayName":"Tether USD (Portal from Solana)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg","coinGeckoId":"tether","nativeDetails":{"chainId":1,"address":"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB","decimals":6},"wrappedDetails":[{"chainId":2,"address":"0x1CDD2EaB61112697626F7b4bB0e23Da4FeBF7B7C","decimals":6},{"chainId":4,"address":"0x49d5cC521F75e13fa8eb4E89E9D381352C897c96","decimals":6},{"chainId":5,"address":"0x3553f861dec0257bada9f8ed268bf0d74e45e89c","decimals":6},{"chainId":6,"address":"0xF0FF231e3F1A50F83136717f287ADAB862f89431","decimals":6},{"chainId":7,"address":"0x24285C5232ce3858F00bacb950Cae1f59d1b2704","decimals":6},{"chainId":9,"address":"0xd80890AFDBd7148456D8Ee358eF9127F0F8c7faf","decimals":6}]},{"symbol":"XTAG","displayName":"XTAG Token (Portal)","logo":"https://raw.githubusercontent.com/sudomon/wormhole-token-list/main/src/logogen/base/xtag.png","coinGeckoId":"xhashtag","nativeDetails":{"chainId":1,"address":"5gs8nf4wojB5EXgDUWNLwXpknzgV2YWDhveAeBZpVLbp","decimals":6},"wrappedDetails":[{"chainId":6,"address":"0xa608d79c5f695c0d4c0e773a4938b57e18e0fc57","decimals":6}]},{"symbol":"ZBC","displayName":"Zebec Protocol","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/zebeczgi5fSEtbpfQKVZKCJ3WgYXxjkMUkNNx7fLKAF/logo.png","coinGeckoId":"zebec-protocol","nativeDetails":{"chainId":1,"address":"zebeczgi5fSEtbpfQKVZKCJ3WgYXxjkMUkNNx7fLKAF","decimals":9},"wrappedDetails":[{"chainId":4,"address":"0x6D1054C3102E842314e250b9e9C4Be327b8DaaE2","decimals":9}]},{"symbol":"mSOL","displayName":"Marinade staked SOL (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So/logo.png","coinGeckoId":"marinade-staked-sol","nativeDetails":{"chainId":1,"address":"mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So","decimals":9},"wrappedDetails":[{"chainId":2,"address":"0x756bFb452cFE36A5Bc82e4F5f4261A89a18c242b","decimals":9},{"chainId":7,"address":"0x5E11A4f64D3B9fA042dB9e1AA918F735038FdfD8","decimals":9}]},{"symbol":"1INCH","displayName":"1INCH Token (Portal)","logo":"","coinGeckoId":"1inch","nativeDetails":{"chainId":2,"address":"0x111111111117dC0aa78b770fA6A738034120C302","decimals":18},"wrappedDetails":[{"chainId":1,"address":"AjkPkq3nsyDe1yKcbyZT7N4aK4Evv9om9tzhQD3wsRC","decimals":8}]},{"symbol":"1SOL","displayName":"1sol.io (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/4ThReWAbAVZjNVgs5Ui9Pk3cZ5TYaD9u6Y89fp6EFzoF/logo.png","coinGeckoId":"1sol","nativeDetails":{"chainId":2,"address":"0x009178997aff09a67d4caccfeb897fb79d036214","decimals":18},"wrappedDetails":[{"chainId":1,"address":"4ThReWAbAVZjNVgs5Ui9Pk3cZ5TYaD9u6Y89fp6EFzoF","decimals":8}]},{"symbol":"AAVE","displayName":"Aave Token (Portal)","logo":"","coinGeckoId":"aave","nativeDetails":{"chainId":2,"address":"0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9","decimals":18},"wrappedDetails":[{"chainId":1,"address":"3vAs4D1WE6Na4tCgt4BApgFfENbm8WY7q4cSPD1yM4Cg","decimals":8}]},{"symbol":"AKRO","displayName":"Akropolis (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/G3h8NZgJozk9crme2me6sKDJuSQ12mNCtvC9NbSWqGuk/logo.png","coinGeckoId":"akropolis","nativeDetails":{"chainId":2,"address":"0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7","decimals":18},"wrappedDetails":[{"chainId":1,"address":"12uHjozDVgyGWeLqQ8DMCRbig8amW5VmvZu3FdMMdcaG","decimals":8}]},{"symbol":"ALEPH","displayName":"Aleph.im (Portal)","logo":"","coinGeckoId":"aleph-im","nativeDetails":{"chainId":2,"address":"0x27702a26126e0b3702af63ee09ac4d1a084ef628","decimals":18},"wrappedDetails":[{"chainId":1,"address":"3UCMiSnkcnkPE1pgQ5ggPCBv6dXgVUy16TmMUe1WpG9x","decimals":8}]},{"symbol":"ALICE","displayName":"My Neighbor Alice (Portal)","logo":"","coinGeckoId":"my-neighbor-alice","nativeDetails":{"chainId":2,"address":"0xac51066d7bec65dc4589368da368b212745d63e8","decimals":6},"wrappedDetails":[{"chainId":1,"address":"9ARQsBfAn65q522cEqSJuse3cLhA31jgWDBGQHeiq7Mg","decimals":8}]},{"symbol":"AMP","displayName":"Amp (Portal)","logo":"","coinGeckoId":"amp","nativeDetails":{"chainId":2,"address":"0xff20817765cb7f73d4bde2e66e067e58d11095c2","decimals":18},"wrappedDetails":[{"chainId":1,"address":"D559HwgjYGDYsXpmFUKxhFTEwutvS9sya1kXiyCVogCV","decimals":8}]},{"symbol":"AMPL","displayName":"Ampleforth (Portal)","logo":"","coinGeckoId":"ampleforth","nativeDetails":{"chainId":2,"address":"0xd46ba6d942050d489dbd938a2c909a5d5039a161","decimals":9},"wrappedDetails":[{"chainId":1,"address":"EHKQvJGu48ydKA4d3RivrkNyTJTkSdoS32UafxSX1yak","decimals":8}]},{"symbol":"ANKR","displayName":"Ankr (Portal)","logo":"","coinGeckoId":"ankr","nativeDetails":{"chainId":2,"address":"0x8290333cef9e6d528dd5618fb97a76f268f3edd4","decimals":18},"wrappedDetails":[{"chainId":1,"address":"Gq2norJ1kBemBp3mPfkgAUMhMMmnFmY4zEyi26tRcxFB","decimals":8}]},{"symbol":"AUDIO","displayName":"Audius (Portal)","logo":"","coinGeckoId":"audius","nativeDetails":{"chainId":2,"address":"0x18aaa7115705e8be94bffebde57af9bfc265b998","decimals":18},"wrappedDetails":[{"chainId":1,"address":"9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM","decimals":8}]},{"symbol":"AXSet","displayName":"Axie Infinity Shard (Portal from Ethereum)","logo":"https://etherscan.io/token/images/axieinfinityshard_32.png","coinGeckoId":"axie-infinity","nativeDetails":{"chainId":2,"address":"0xbb0e17ef65f82ab018d8edd776e8dd940327b28b","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HysWcbHiYY9888pHbaqhwLYZQeZrcQMXKQWRqS7zcPK5","decimals":8},{"chainId":4,"address":"0x556b60c53fbC1518Ad17E03d52E47368dD4d81B3","decimals":18}]},{"symbol":"BAT","displayName":"Basic Attention Token (Portal)","logo":"","coinGeckoId":"basic-attention-token","nativeDetails":{"chainId":2,"address":"0x0d8775f648430679a709e98d2b0cb6250d2887ef","decimals":18},"wrappedDetails":[{"chainId":1,"address":"EPeUFDgHRxs9xxEPVaL6kfGQvCon7jmAWKVUHuux1Tpz","decimals":8},{"chainId":4,"address":"0x31C78f583ed0288D67b2b80Dc5C443Bc3b15C661","decimals":18}]},{"symbol":"BNT","displayName":"Bancor Network Token (Portal)","logo":"","coinGeckoId":"bancor-network","nativeDetails":{"chainId":2,"address":"0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c","decimals":18},"wrappedDetails":[{"chainId":1,"address":"EDVVEYW4fPJ6vKw5LZXRGUSPzxoHrv6eWvTqhCr8oShs","decimals":8}]},{"symbol":"BUSDet","displayName":"Binance USD (Portal from Ethereum)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX/logo.png","coinGeckoId":"binance-usd","nativeDetails":{"chainId":2,"address":"0x4fabb145d64652a948d72533023f6e7a623c7c53","decimals":18},"wrappedDetails":[{"chainId":1,"address":"33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX","decimals":8},{"chainId":4,"address":"0x035de3679E692C471072d1A09bEb9298fBB2BD31","decimals":18}]},{"symbol":"CEL","displayName":"Celsius (Portal)","logo":"","coinGeckoId":"celsius-network","nativeDetails":{"chainId":2,"address":"0xaaaebe6fe48e54f431b0c390cfaf0b017d09d42d","decimals":4},"wrappedDetails":[{"chainId":1,"address":"nRtfwU9G82CSHhHGJNxFhtn7FLvWP2rqvQvje1WtL69","decimals":4}]},{"symbol":"CHZ","displayName":"Chiliz (Portal)","logo":"","coinGeckoId":"chiliz","nativeDetails":{"chainId":2,"address":"0x3506424f91fd33084466f402d5d97f05f8e3b4af","decimals":18},"wrappedDetails":[{"chainId":1,"address":"5TtSKAamFq88grN1QGrEaZ1AjjyciqnCya1aiMhAgFvG","decimals":8}]},{"symbol":"COMP","displayName":"Compound (Portal)","logo":"","coinGeckoId":"compound","nativeDetails":{"chainId":2,"address":"0xc00e94cb662c3520282e6f5717214004a7f26888","decimals":18},"wrappedDetails":[{"chainId":1,"address":"AwEauVaTMQRB71WeDnwf1DWSBxaMKjEPuxyLr1uixFom","decimals":8}]},{"symbol":"CREAM","displayName":"Cream (Portal)","logo":"","coinGeckoId":"cream","nativeDetails":{"chainId":2,"address":"0x2ba592f78db6436527729929aaf6c908497cb200","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HihxL2iM6L6P1oqoSeiixdJ3PhPYNxvSKH9A2dDqLVDH","decimals":8}]},{"symbol":"CRO","displayName":"Crypto.com Coin (Portal)","logo":"","coinGeckoId":"crypto-com-coin","nativeDetails":{"chainId":2,"address":"0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b","decimals":8},"wrappedDetails":[{"chainId":1,"address":"DvjMYMVeXgKxaixGKpzQThLoG98nc7HSU7eanzsdCboA","decimals":8}]},{"symbol":"CRV","displayName":"Curve DAO Token (Portal)","logo":"","coinGeckoId":"curve-dao-token","nativeDetails":{"chainId":2,"address":"0xd533a949740bb3306d119cc777fa900ba034cd52","decimals":18},"wrappedDetails":[{"chainId":1,"address":"7gjNiPun3AzEazTZoFEjZgcBMeuaXdpjHq2raZTmTrfs","decimals":8}]},{"symbol":"CVX","displayName":"Convex Finance (Portal)","logo":"","coinGeckoId":"convex-finance","nativeDetails":{"chainId":2,"address":"0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b","decimals":18},"wrappedDetails":[{"chainId":1,"address":"BLvmrccP4g1B6SpiVvmQrLUDya1nZ4B2D1nm9jzKF7sz","decimals":8}]},{"symbol":"DAI","displayName":"DAI (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/xnorPhAzWXUczCP3KjU5yDxmKKZi5cSbxytQ1LgE3kG/logo.png","coinGeckoId":"dai","nativeDetails":{"chainId":2,"address":"0x6b175474e89094c44da98b954eedeac495271d0f","decimals":18},"wrappedDetails":[{"chainId":1,"address":"EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o","decimals":8},{"chainId":4,"address":"0x3413a030EF81a3dD5a302F4B4D11d911e12ed337","decimals":18}]},{"symbol":"DYDX","displayName":"dYdX (Portal)","logo":"https://etherscan.io/token/images/dydx2_32.png","coinGeckoId":"dydx","nativeDetails":{"chainId":2,"address":"0x92d6c1e31e14520e676a687f0a93788b716beff5","decimals":18},"wrappedDetails":[{"chainId":1,"address":"4Hx6Bj56eGyw8EJrrheM6LBQAvVYRikYCWsALeTrwyRU","decimals":8}]},{"symbol":"ELON","displayName":"Dogelon Mars (Portal)","logo":"","coinGeckoId":"dogelon-mars","nativeDetails":{"chainId":2,"address":"0x761d38e5ddf6ccf6cf7c55759d5210750b5d60f3","decimals":18},"wrappedDetails":[{"chainId":1,"address":"6nKUU36URHkewHg5GGGAgxs6szkE4VTioGUT5txQqJFU","decimals":8}]},{"symbol":"ENJ","displayName":"EnjinCoin (Portal)","logo":"","coinGeckoId":"enjin","nativeDetails":{"chainId":2,"address":"0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c","decimals":18},"wrappedDetails":[{"chainId":1,"address":"EXExWvT6VyYxEjFzF5BrUxt5GZMPVZnd48y3iWrRefMq","decimals":8}]},{"symbol":"ENS","displayName":"Ethereum Name Service (Portal)","logo":"","coinGeckoId":"ethereum-name-service","nativeDetails":{"chainId":2,"address":"0xc18360217d8f7ab5e7c516566761ea12ce7f9d72","decimals":18},"wrappedDetails":[{"chainId":1,"address":"CLQsDGoGibdNPnVCFp8BAsN2unvyvb41Jd5USYwAnzAg","decimals":8}]},{"symbol":"ETH","displayName":"Ether (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs/logo.png","coinGeckoId":"ether","nativeDetails":{"chainId":2,"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","decimals":18},"wrappedDetails":[{"chainId":1,"address":"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs","decimals":8},{"chainId":4,"address":"0x4DB5a66E937A9F4473fA95b1cAF1d1E1D62E29EA","decimals":18},{"chainId":5,"address":"0x11CD37bb86F65419713f30673A480EA33c826872","decimals":18},{"chainId":6,"address":"0x8b82A291F83ca07Af22120ABa21632088fC92931","decimals":18},{"chainId":7,"address":"0x3223f17957Ba502cbe71401D55A0DB26E5F7c68F","decimals":18},{"chainId":9,"address":"0x811Cc0d762eA72aC72385d93b98a97263AE37E4C","decimals":18},{"chainId":14,"address":"0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207","decimals":18}]},{"symbol":"ETHIX","displayName":"Ethix (Portal)","logo":"https://raw.githubusercontent.com/ethichub/wormhole-token-list/main/src/logogen/base/ETHIX.png","coinGeckoId":"ethichub","nativeDetails":{"chainId":2,"address":"0xFd09911130e6930Bf87F2B0554c44F400bD80D3e","decimals":18},"wrappedDetails":[{"chainId":14,"address":"0x9995cc8F20Db5896943Afc8eE0ba463259c931ed","decimals":18}]},{"symbol":"FRAX","displayName":"Frax (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp/logo.png","coinGeckoId":"frax","nativeDetails":{"chainId":2,"address":"0x853d955acef822db058eb8505911ed77f175b99e","decimals":18},"wrappedDetails":[{"chainId":1,"address":"FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp","decimals":8}]},{"symbol":"FRONT","displayName":"Frontier (Portal)","logo":"","coinGeckoId":"frontier","nativeDetails":{"chainId":2,"address":"0xf8c3527cc04340b208c854e985240c02f7b7793f","decimals":18},"wrappedDetails":[{"chainId":1,"address":"A9ik2NrpKRRG2snyTjofZQcTuav9yH3mNVHLsLiDQmYt","decimals":8}]},{"symbol":"FTMet","displayName":"Fantom (Portal from Ethereum)","logo":"","coinGeckoId":"fantom","nativeDetails":{"chainId":2,"address":"0x4e15361fd6b4bb609fa63c81a2be19d873717870","decimals":18},"wrappedDetails":[{"chainId":1,"address":"8gC27rQF4NEDYfyf5aS8ZmQJUum5gufowKGYRRba4ENN","decimals":8}]},{"symbol":"FTT","displayName":"FTX Token (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv/logo.png","coinGeckoId":"ftx-token","nativeDetails":{"chainId":2,"address":"0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9","decimals":18},"wrappedDetails":[{"chainId":1,"address":"EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv","decimals":8},{"chainId":4,"address":"0x49BA054B9664e99ac335667a917c63bB94332E84","decimals":18}]},{"symbol":"FXS","displayName":"Frax Share (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/6LX8BhMQ4Sy2otmAWj7Y5sKd9YTVVUgfMsBzT6B9W7ct/logo.png","coinGeckoId":"frax-share","nativeDetails":{"chainId":2,"address":"0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0","decimals":18},"wrappedDetails":[{"chainId":1,"address":"6LX8BhMQ4Sy2otmAWj7Y5sKd9YTVVUgfMsBzT6B9W7ct","decimals":8}]},{"symbol":"GALA","displayName":"Gala (Portal)","logo":"","coinGeckoId":"gala","nativeDetails":{"chainId":2,"address":"0x15d4c048f83bd7e37d49ea4c83a07267ec4203da","decimals":8},"wrappedDetails":[{"chainId":1,"address":"AuGz22orMknxQHTVGwAu7e3dJikTJKgcjFwMNDikEKmF","decimals":8}]},{"symbol":"GRT","displayName":"Graph Token (Portal)","logo":"","coinGeckoId":"the-graph","nativeDetails":{"chainId":2,"address":"0xc944E90C64B2c07662A292be6244BDf05Cda44a7","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HGsLG4PnZ28L8A4R5nPqKgZd86zUUdmfnkTRnuFJ5dAX","decimals":8}]},{"symbol":"GT","displayName":"GateToken (Portal)","logo":"","coinGeckoId":"gatetoken","nativeDetails":{"chainId":2,"address":"0xe66747a101bff2dba3697199dcce5b743b454759","decimals":18},"wrappedDetails":[{"chainId":1,"address":"ABAq2R9gSpDDGguQxBk4u13s4ZYW6zbwKVBx15mCMG8","decimals":8}]},{"symbol":"HBTC","displayName":"Huobi BTC (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7dVH61ChzgmN9BwG4PkzwRP8PbYwPJ7ZPNF2vamKT2H8/logo.png","coinGeckoId":"huobi-btc","nativeDetails":{"chainId":2,"address":"0x0316eb71485b0ab14103307bf65a021042c6d380","decimals":18},"wrappedDetails":[{"chainId":1,"address":"7dVH61ChzgmN9BwG4PkzwRP8PbYwPJ7ZPNF2vamKT2H8","decimals":8}]},{"symbol":"HGET","displayName":"Hedget (Portal)","logo":"","coinGeckoId":"hedget","nativeDetails":{"chainId":2,"address":"0x7968bc6a03017ea2de509aaa816f163db0f35148","decimals":6},"wrappedDetails":[{"chainId":1,"address":"2ueY1bLcPHfuFzEJq7yN1V2Wrpu8nkun9xG2TVCE1mhD","decimals":8}]},{"symbol":"HUSD","displayName":"HUSD Stablecoin (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw/logo.png","coinGeckoId":"husd","nativeDetails":{"chainId":2,"address":"0xdf574c24545e5ffecb9a659c229253d4111d87e1","decimals":8},"wrappedDetails":[{"chainId":1,"address":"7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw","decimals":8}]},{"symbol":"HXRO","displayName":"Hxro (Portal)","logo":"","coinGeckoId":"hxro","nativeDetails":{"chainId":2,"address":"0x4bd70556ae3f8a6ec6c4080a0c327b24325438f3","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK","decimals":8}]},{"symbol":"ICE","displayName":"PopsicleToken (Portal)","logo":"","coinGeckoId":"popsicle-finance","nativeDetails":{"chainId":2,"address":"0xf16e81dce15b08f326220742020379b855b87df9","decimals":18},"wrappedDetails":[{"chainId":1,"address":"DiJut4U3CU8b3bRgwfyqtJMJ4wjzJHaX6hudamjH46Km","decimals":8}]},{"symbol":"ILV","displayName":"Illuvium (Portal)","logo":"","coinGeckoId":"illuvium","nativeDetails":{"chainId":2,"address":"0x767fe9edc9e0df98e07454847909b5e959d7ca0e","decimals":18},"wrappedDetails":[{"chainId":1,"address":"8UJbtpsEubDVkY53rk7d61hNYKkvouicczB2XmuwiG4g","decimals":8}]},{"symbol":"KEEP","displayName":"Keep Network (Portal)","logo":"","coinGeckoId":"keep-network","nativeDetails":{"chainId":2,"address":"0x85eee30c52b0b379b046fb0f85f4f3dc3009afec","decimals":18},"wrappedDetails":[{"chainId":1,"address":"64L6o4G2H7Ln1vN7AHZsUMW4pbFciHyuwn4wUdSbcFxh","decimals":8}]},{"symbol":"KP3R","displayName":"Keep3rV1 (Portal)","logo":"","coinGeckoId":"keep3rv1","nativeDetails":{"chainId":2,"address":"0x1ceb5cb57c4d4e2b2433641b95dd330a33185a44","decimals":18},"wrappedDetails":[{"chainId":1,"address":"3a2VW9t5N6p4baMW3M6yLH1UJ9imMt7VsyUk6ouXPVLq","decimals":8}]},{"symbol":"LDO","displayName":"Lido DAO (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/HZRCwxP2Vq9PCpPXooayhJ2bxTpo5xfpQrwB1svh332p/logo.png","coinGeckoId":"lido-dao","nativeDetails":{"chainId":2,"address":"0x5a98fcbea516cf06857215779fd812ca3bef1b32","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HZRCwxP2Vq9PCpPXooayhJ2bxTpo5xfpQrwB1svh332p","decimals":8},{"chainId":4,"address":"0x986854779804799C1d68867F5E03e601E781e41b","decimals":18}]},{"symbol":"LINK","displayName":"Chainlink (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2wpTofQ8SkACrkZWrZDjXPitYa8AwWgX8AfxdeBRRVLX/logo.png","coinGeckoId":"chainlink","nativeDetails":{"chainId":2,"address":"0x514910771af9ca656af840dff83e8264ecf986ca","decimals":18},"wrappedDetails":[{"chainId":1,"address":"2wpTofQ8SkACrkZWrZDjXPitYa8AwWgX8AfxdeBRRVLX","decimals":8}]},{"symbol":"LRC","displayName":"Loopring (Portal)","logo":"","coinGeckoId":"loopring","nativeDetails":{"chainId":2,"address":"0xbbbbca6a901c926f240b89eacb641d8aec7aeafd","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HCTVFTzHL21a1dPzKxAUeWwqbE8QMUyvgChFDL4XYoi1","decimals":8}]},{"symbol":"LUA","displayName":"LuaSwap (Portal)","logo":"","coinGeckoId":"luaswap","nativeDetails":{"chainId":2,"address":"0xb1f66997a5760428d3a87d68b90bfe0ae64121cc","decimals":18},"wrappedDetails":[{"chainId":1,"address":"5Wc4U1ZoQRzF4tPdqKQzBwRSjYe8vEf3EvZMuXgtKUW6","decimals":8}]},{"symbol":"MANA","displayName":"Decentraland (Portal)","logo":"https://assets.coingecko.com/coins/images/878/thumb/decentraland-mana.png","coinGeckoId":"decentraland","nativeDetails":{"chainId":2,"address":"0x0F5D2fB29fb7d3CFeE444a200298f468908cC942","decimals":18},"wrappedDetails":[{"chainId":1,"address":"7dgHoN8wBZCc5wbnQ2C47TDnBMAxG4Q5L3KjP67z8kNi","decimals":8}]},{"symbol":"MATH","displayName":"MATH Token (Portal)","logo":"","coinGeckoId":"math","nativeDetails":{"chainId":2,"address":"0x08d967bb0134f2d07f7cfb6e246680c53927dd30","decimals":18},"wrappedDetails":[{"chainId":1,"address":"CaGa7pddFXS65Gznqwp42kBhkJQdceoFVT7AQYo8Jr8Q","decimals":8}]},{"symbol":"MATICet","displayName":"MATIC (Portal from Ethereum)","logo":"","coinGeckoId":"polygon","nativeDetails":{"chainId":2,"address":"0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0","decimals":18},"wrappedDetails":[{"chainId":1,"address":"C7NNPWuZCNjZBfW5p6JvGsR8pUdsRpEdP1ZAhnoDwj7h","decimals":8}]},{"symbol":"MIMet","displayName":"Magic Internet Money (Portal from Ethereum)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/HRQke5DKdDo3jV7wnomyiM8AA3EzkVnxMDdo2FQ5XUe1/logo.png","coinGeckoId":"magic-internet-money","nativeDetails":{"chainId":2,"address":"0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HRQke5DKdDo3jV7wnomyiM8AA3EzkVnxMDdo2FQ5XUe1","decimals":8}]},{"symbol":"NXM","displayName":"Nexus Mutual (Portal)","logo":"","coinGeckoId":"nexus-mutual","nativeDetails":{"chainId":2,"address":"0xd7c49cee7e9188cca6ad8ff264c1da2e69d4cf3b","decimals":18},"wrappedDetails":[{"chainId":1,"address":"Aqs5ydqKXEK2cjotDXxHmk8N9PknqQ5q4ZED4ymY1eeh","decimals":8}]},{"symbol":"ORION","displayName":"Orion Money (Portal)","logo":"https://assets.coingecko.com/coins/images/18630/small/YtrqPIWc.png","coinGeckoId":"orion-money","nativeDetails":{"chainId":2,"address":"0x727f064a78dc734d33eec18d5370aef32ffd46e4","decimals":18},"wrappedDetails":[{"chainId":4,"address":"0x3dcB18569425930954feb191122e574b87F66abd","decimals":18},{"chainId":5,"address":"0x5E0294Af1732498C77F8dB015a2d52a76298542B","decimals":18}]},{"symbol":"PAXG","displayName":"Paxos Gold (Portal)","logo":"","coinGeckoId":"pax-gold","nativeDetails":{"chainId":2,"address":"0x45804880de22913dafe09f4980848ece6ecbaf78","decimals":18},"wrappedDetails":[{"chainId":1,"address":"C6oFsE8nXRDThzrMEQ5SxaNFGKoyyfWDDVPw37JKvPTe","decimals":8}]},{"symbol":"PEOPLE","displayName":"ConstitutionDAO (Portal)","logo":"","coinGeckoId":"constitutiondao","nativeDetails":{"chainId":2,"address":"0x7a58c0be72be218b41c608b7fe7c5bb630736c71","decimals":18},"wrappedDetails":[{"chainId":1,"address":"CobcsUrt3p91FwvULYKorQejgsm5HoQdv5T8RUZ6PnLA","decimals":8}]},{"symbol":"PERP","displayName":"Perpetual Protocol (Portal)","logo":"","coinGeckoId":"perpetual-protocol","nativeDetails":{"chainId":2,"address":"0xbc396689893d065f41bc2c6ecbee5e0085233447","decimals":18},"wrappedDetails":[{"chainId":1,"address":"9BsnSWDPfbusseZfnXyZ3un14CyPMZYvsKjWY3Y8Gbqn","decimals":8}]},{"symbol":"RGT","displayName":"Rari Governance Token (Portal)","logo":"","coinGeckoId":"rari-governance-token","nativeDetails":{"chainId":2,"address":"0xd291e7a03283640fdc51b121ac401383a46cc623","decimals":18},"wrappedDetails":[{"chainId":1,"address":"ASk8bss7PoxfFVJfXnSJepj9KupTX15QaRnhdjs6DdYe","decimals":8}]},{"symbol":"RPL","displayName":"Rocket Pool (Portal)","logo":"","coinGeckoId":"rocket-pool","nativeDetails":{"chainId":2,"address":"0xd33526068d116ce69f19a9ee46f0bd304f21a51f","decimals":18},"wrappedDetails":[{"chainId":1,"address":"HUCyuyqESEUV4YWTKFvvB4JiQLqoovscTBpRXfGzW4Wx","decimals":8}]},{"symbol":"RSR","displayName":"Reserve Rights (Portal)","logo":"","coinGeckoId":"reserve-rights-token","nativeDetails":{"chainId":2,"address":"0x8762db106B2c2A0bccB3A80d1Ed41273552616E8","decimals":18},"wrappedDetails":[{"chainId":1,"address":"DkbE8U4gSRuGHcVMA1LwyZPYUjYbfEbjW8DMR3iSXBzr","decimals":8}]},{"symbol":"SAND","displayName":"The Sandbox (Portal)","logo":"https://gemini.com/images/currencies/icons/default/sand.svg","coinGeckoId":"the-sandbox","nativeDetails":{"chainId":2,"address":"0x3845badAde8e6dFF049820680d1F14bD3903a5d0","decimals":18},"wrappedDetails":[{"chainId":1,"address":"49c7WuCZkQgc3M4qH8WuEUNXfgwupZf1xqWkDQ7gjRGt","decimals":8}]},{"symbol":"SHIB","displayName":"Shiba Inu (Portal)","logo":"https://etherscan.io/token/images/shibatoken_32.png","coinGeckoId":"shiba-inu","nativeDetails":{"chainId":2,"address":"0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce","decimals":18},"wrappedDetails":[{"chainId":1,"address":"CiKu4eHsVrc1eueVQeHn7qhXTcVu95gSQmBpX4utjL9z","decimals":8},{"chainId":4,"address":"0xb1547683DA678f2e1F003A780143EC10Af8a832B","decimals":18}]},{"symbol":"SLP","displayName":"Smooth Love Potion (Portal)","logo":"","coinGeckoId":"smooth-love-potion","nativeDetails":{"chainId":2,"address":"0xcc8fa225d80b9c7d42f96e9570156c65d6caaa25","decimals":0},"wrappedDetails":[{"chainId":1,"address":"4hpngEp1v3CXpeKB81Gw4sv7YvwUVRKvY3SGag9ND8Q4","decimals":8}]},{"symbol":"SNX","displayName":"Synthetix Network Token (Portal)","logo":"","coinGeckoId":"synthetix-network-token","nativeDetails":{"chainId":2,"address":"0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f","decimals":18},"wrappedDetails":[{"chainId":1,"address":"8cTNUtcV2ueC3royJ642uRnvTxorJAWLZc58gxAo7y56","decimals":8}]},{"symbol":"SOS","displayName":"OpenDAO (Portal)","logo":"","coinGeckoId":"opendao","nativeDetails":{"chainId":2,"address":"0x3b484b82567a09e2588a13d54d032153f0c0aee0","decimals":18},"wrappedDetails":[{"chainId":1,"address":"6Q5fvsJ6kgAFmisgDqqyaFd9FURYzHf8MCUbpAUaGZnE","decimals":8}]},{"symbol":"SPELL","displayName":"Spell Token (Portal)","logo":"","coinGeckoId":"spell-token","nativeDetails":{"chainId":2,"address":"0x090185f2135308bad17527004364ebcc2d37e5f6","decimals":18},"wrappedDetails":[{"chainId":1,"address":"BCsFXYm81iqXyYmrLKgAp3AePcgLHnirb8FjTs6sjM7U","decimals":8}]},{"symbol":"SRMet","displayName":"Serum (Portal from Ethereum)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/xnorPhAzWXUczCP3KjU5yDxmKKZi5cSbxytQ1LgE3kG/logo.png","coinGeckoId":"serum","nativeDetails":{"chainId":2,"address":"0x476c5e26a75bd202a9683ffd34359c0cc15be0ff","decimals":6},"wrappedDetails":[{"chainId":1,"address":"xnorPhAzWXUczCP3KjU5yDxmKKZi5cSbxytQ1LgE3kG","decimals":6},{"chainId":4,"address":"0xd63CDf02853D759831550fAe7dF8FFfE0B317b39","decimals":6}]},{"symbol":"SUSHI","displayName":"SushiToken (Portal)","logo":"https://etherscan.io/token/images/sushitoken_32.png","coinGeckoId":"sushi","nativeDetails":{"chainId":2,"address":"0x6b3595068778dd592e39a122f4f5a5cf09c90fe2","decimals":18},"wrappedDetails":[{"chainId":1,"address":"ChVzxWRmrTeSgwd3Ui3UumcN8KX7VK3WaD4KGeSKpypj","decimals":8},{"chainId":4,"address":"0x3524fd7488fdb1F4723BBc22C9cbD1Bf89f46E3B","decimals":18}]},{"symbol":"SWAG","displayName":"SWAG Finance (Portal)","logo":"","coinGeckoId":"swag-finance","nativeDetails":{"chainId":2,"address":"0x87edffde3e14c7a66c9b9724747a1c5696b742e6","decimals":18},"wrappedDetails":[{"chainId":1,"address":"5hcdG6NjQwiNhVa9bcyaaDsCyA1muPQ6WRzQwHfgeeKo","decimals":8}]},{"symbol":"SXP","displayName":"Swipe (Portal)","logo":"","coinGeckoId":"swipe","nativeDetails":{"chainId":2,"address":"0x8ce9137d39326ad0cd6491fb5cc0cba0e089b6a9","decimals":18},"wrappedDetails":[{"chainId":1,"address":"3CyiEDRehaGufzkpXJitCP5tvh7cNhRqd9rPBxZrgK5z","decimals":8}]},{"symbol":"TOKE","displayName":"Tokemak (Portal)","logo":"","coinGeckoId":"tokemak","nativeDetails":{"chainId":2,"address":"0x2e9d63788249371f1dfc918a52f8d799f4a38c94","decimals":18},"wrappedDetails":[{"chainId":1,"address":"3EQ6LqLkiFcoxTeGEsHMFpSLWNVPe9yT7XPX2HYSFyxX","decimals":8}]},{"symbol":"TRIBE","displayName":"Tribe (Portal)","logo":"","coinGeckoId":"tribe","nativeDetails":{"chainId":2,"address":"0xc7283b66eb1eb5fb86327f08e1b5816b0720212b","decimals":18},"wrappedDetails":[{"chainId":1,"address":"DPgNKZJAG2w1S6vfYHDBT62R4qrWWH5f45CnxtbQduZE","decimals":8}]},{"symbol":"UBXT","displayName":"UpBots (Portal)","logo":"","coinGeckoId":"upbots","nativeDetails":{"chainId":2,"address":"0x8564653879a18c560e7c0ea0e084c516c62f5653","decimals":18},"wrappedDetails":[{"chainId":1,"address":"FTtXEUosNn6EKG2SQtfbGuYB4rBttreQQcoWn1YDsuTq","decimals":8}]},{"symbol":"UFO","displayName":"UFO Gaming (Portal)","logo":"","coinGeckoId":"ufo-gaming","nativeDetails":{"chainId":2,"address":"0x249e38ea4102d0cf8264d3701f1a0e39c4f2dc3b","decimals":18},"wrappedDetails":[{"chainId":1,"address":"GWdkYFnXnSJAsCBvmsqFLiPPe2tpvXynZcJdxf11Fu3U","decimals":8}]},{"symbol":"UNI","displayName":"Uniswap (Portal)","logo":"https://etherscan.io/token/images/uniswap_32.png","coinGeckoId":"uniswap","nativeDetails":{"chainId":2,"address":"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984","decimals":18},"wrappedDetails":[{"chainId":1,"address":"8FU95xFJhUUkyyCLU13HSzDLs7oC4QZdXQHL6SCeab36","decimals":8}]},{"symbol":"USDCet","displayName":"USD Coin (Portal from Ethereum)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png","coinGeckoId":"usd-coin","nativeDetails":{"chainId":2,"address":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","decimals":6},"wrappedDetails":[{"chainId":1,"address":"A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM","decimals":6},{"chainId":4,"address":"0xB04906e95AB5D797aDA81508115611fee694c2b3","decimals":6},{"chainId":5,"address":"0x4318cb63a2b8edf2de971e2f17f77097e499459d","decimals":6},{"chainId":6,"address":"0xB24CA28D4e2742907115fECda335b40dbda07a4C","decimals":6},{"chainId":7,"address":"0xE8A638b3B7565Ee7c5eb9755E58552aFc87b94DD","decimals":6},{"chainId":10,"address":"0x2Ec752329c3EB419136ca5e4432Aa2CDb1eA23e6","decimals":6},{"chainId":14,"address":"0x37f750B7cC259A2f741AF45294f6a16572CF5cAd","decimals":6}]},{"symbol":"USDK","displayName":"USDK (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F/logo.png","coinGeckoId":"usdk","nativeDetails":{"chainId":2,"address":"0x1c48f86ae57291f7686349f12601910bd8d470bb","decimals":18},"wrappedDetails":[{"chainId":1,"address":"43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F","decimals":8}]},{"symbol":"USDTet","displayName":"Tether USD (Portal from Ethereum)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg","coinGeckoId":"tether","nativeDetails":{"chainId":2,"address":"0xdac17f958d2ee523a2206206994597c13d831ec7","decimals":6},"wrappedDetails":[{"chainId":1,"address":"Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1","decimals":6},{"chainId":4,"address":"0x524bC91Dc82d6b90EF29F76A3ECAaBAffFD490Bc","decimals":6},{"chainId":5,"address":"0x9417669fBF23357D2774e9D421307bd5eA1006d2","decimals":6},{"chainId":7,"address":"0xdC19A122e268128B5eE20366299fc7b5b199C8e3","decimals":6}]},{"symbol":"WBTC","displayName":"Wrapped BTC (Portal)","logo":"https://etherscan.io/token/images/wbtc_28.png?v=1","coinGeckoId":"wrapped-bitcoin","nativeDetails":{"chainId":2,"address":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","decimals":8},"wrappedDetails":[{"chainId":1,"address":"3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh","decimals":8},{"chainId":5,"address":"0x5D49c278340655B56609FdF8976eb0612aF3a0C3","decimals":8},{"chainId":7,"address":"0xd43ce0aa2a29DCb75bDb83085703dc589DE6C7eb","decimals":8}]},{"symbol":"YFI","displayName":"yearn.finance (Portal)","logo":"","coinGeckoId":"yearn-finance","nativeDetails":{"chainId":2,"address":"0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e","decimals":18},"wrappedDetails":[{"chainId":1,"address":"BXZX2JRJFjvKazM1ibeDFxgAngKExb74MRXzXKvgikxX","decimals":8}]},{"symbol":"YGG","displayName":"Yield Guild Games (Portal)","logo":"","coinGeckoId":"yield-guild-games","nativeDetails":{"chainId":2,"address":"0x25f8087ead173b73d6e8b84329989a8eea16cf73","decimals":18},"wrappedDetails":[{"chainId":1,"address":"EzZp7LRN1xwu3QsB2RJRrWwEGjJGsuWzuMCeQDB3NSPK","decimals":8}]},{"symbol":"ZRX","displayName":"0x (Portal)","logo":"","coinGeckoId":"0x","nativeDetails":{"chainId":2,"address":"0xe41d2489571d322189246dafa5ebde1f4699f498","decimals":18},"wrappedDetails":[{"chainId":1,"address":"GJa1VeEYLTRoHbaeqcxfzHmjGCGtZGF3CUqxv9znZZAY","decimals":8}]},{"symbol":"agEUR","displayName":"agEUR (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/CbNYA9n3927uXUukee2Hf4tm3xxkffJPPZvGazc2EAH1/logo.svg","coinGeckoId":"ageur","nativeDetails":{"chainId":2,"address":"0x1a7e4e63778B4f12a199C062f3eFdD288afCBce8","decimals":18},"wrappedDetails":[{"chainId":1,"address":"CbNYA9n3927uXUukee2Hf4tm3xxkffJPPZvGazc2EAH1","decimals":8}]},{"symbol":"gOHM","displayName":"Governance OHM (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/FUGsN8H74WjRBBMfQWcf9Kk32gebA9VnNaGxqwcVvUW7/logo.png","coinGeckoId":"governance-ohm","nativeDetails":{"chainId":2,"address":"0x0ab87046fbb341d058f17cbc4c1133f25a20a52f","decimals":18},"wrappedDetails":[{"chainId":1,"address":"FUGsN8H74WjRBBMfQWcf9Kk32gebA9VnNaGxqwcVvUW7","decimals":8}]},{"symbol":"ibBTC","displayName":"Interest Bearing Bitcoin (Portal)","logo":"https://etherscan.io/token/images/badgeribtc_32.png","coinGeckoId":"interest-bearing-bitcoin","nativeDetails":{"chainId":2,"address":"0xc4e15973e6ff2a35cc804c2cf9d2a1b817a8b40f","decimals":18},"wrappedDetails":[{"chainId":1,"address":"Bzq68gAVedKqQkQbsM28yQ4LYpc2VComDUD9wJBywdTi","decimals":8}]},{"symbol":"stETH","displayName":"Lido Staked Ether (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/H2mf9QNdU2Niq6QR7367Ua2trBsvscLyX5bz7R3Pw5sE/logo.png","coinGeckoId":"lido-staked-ether","nativeDetails":{"chainId":2,"address":"0xae7ab96520de3a18e5e111b5eaab095312d7fe84","decimals":18},"wrappedDetails":[{"chainId":1,"address":"H2mf9QNdU2Niq6QR7367Ua2trBsvscLyX5bz7R3Pw5sE","decimals":8}]},{"symbol":"BNB","displayName":"Binance Coin (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/9gP2kCy3wA1ctvYWQk75guqXuHfrEomqydHLtcTCqiLa/logo.png","coinGeckoId":"binance-coin","nativeDetails":{"chainId":4,"address":"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c","decimals":18},"wrappedDetails":[{"chainId":1,"address":"9gP2kCy3wA1ctvYWQk75guqXuHfrEomqydHLtcTCqiLa","decimals":8},{"chainId":2,"address":"0x418D75f65a02b3D53B2418FB8E1fe493759c7605","decimals":18},{"chainId":5,"address":"0xecdcb5b88f8e3c15f95c720c51c71c9e2080525d","decimals":18},{"chainId":6,"address":"0x442F7f22b1EE2c842bEAFf52880d4573E9201158","decimals":18},{"chainId":7,"address":"0xd79Ef9A91b56c690C7b80570a3c060678667f469","decimals":18}]},{"symbol":"BUSDbs","displayName":"Binance USD (Portal from BSC)","logo":"https://etherscan.io/token/images/binanceusd_32.png","coinGeckoId":"binance-usd","nativeDetails":{"chainId":4,"address":"0xe9e7cea3dedca5984780bafc599bd69add087d56","decimals":18},"wrappedDetails":[{"chainId":1,"address":"5RpUwQ8wtdPCZHhu6MERp2RGrpobsbZ6MH5dDHkUjs2","decimals":8},{"chainId":2,"address":"0x7B4B0B9b024109D182dCF3831222fbdA81369423","decimals":18},{"chainId":5,"address":"0xa8d394fe7380b8ce6145d5f85e6ac22d4e91acde","decimals":18},{"chainId":6,"address":"0xA41a6c7E25DdD361343e8Cb8cFa579bbE5eEdb7a","decimals":18},{"chainId":7,"address":"0xf6568FD76f9fcD1f60f73b730F142853c5eF627E","decimals":18}]},{"symbol":"CAKE","displayName":"PancakeSwap Token (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/J8LKx7pr9Zxh9nMhhT7X3EBmj5RzuhFrHKyJAe2F2i9S/logo.png","coinGeckoId":"pancakeswap","nativeDetails":{"chainId":4,"address":"0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82","decimals":18},"wrappedDetails":[{"chainId":1,"address":"J8LKx7pr9Zxh9nMhhT7X3EBmj5RzuhFrHKyJAe2F2i9S","decimals":8},{"chainId":2,"address":"0x7c8161545717a334f3196e765d9713f8042EF338","decimals":18},{"chainId":6,"address":"0x98a4d09036Cc5337810096b1D004109686E56Afc","decimals":18}]},{"symbol":"USDCbs","displayName":"USD Coin (Portal from BSC)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png","coinGeckoId":"usd-coin","nativeDetails":{"chainId":4,"address":"0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d","decimals":18},"wrappedDetails":[{"chainId":1,"address":"FCqfQSujuPxy6V42UvafBhsysWtEq1vhjfMN1PUbgaxA","decimals":8},{"chainId":2,"address":"0x7cd167B101D2808Cfd2C45d17b2E7EA9F46b74B6","decimals":18},{"chainId":6,"address":"0x6145E8a910aE937913426BF32De2b26039728ACF","decimals":18},{"chainId":7,"address":"0x4cA2A3De42eabC8fd8b0AC46127E64DB08b9150e","decimals":18}]},{"symbol":"USDTbs","displayName":"Tether USD (Portal from BSC)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg","coinGeckoId":"tether","nativeDetails":{"chainId":4,"address":"0x55d398326f99059fF775485246999027B3197955","decimals":18},"wrappedDetails":[{"chainId":1,"address":"8qJSyQprMC57TWKaYEmetUR3UUiTP2M3hXdcvFhkZdmv","decimals":8},{"chainId":2,"address":"0xDe60aDfDdAAbaAAC3dAFa57B26AcC91Cb63728c4","decimals":18},{"chainId":6,"address":"0xA67BCC0D06d7d13A13A2AE30bF30f1B434f5a28B","decimals":18},{"chainId":7,"address":"0x366EF31C8dc715cbeff5fA54Ad106dC9c25C6153","decimals":18}]},{"symbol":"LUNA","displayName":"LUNA (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W/logo.png","coinGeckoId":"terra-luna","nativeDetails":{"chainId":3,"address":"uluna","decimals":6},"wrappedDetails":[{"chainId":1,"address":"F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W","decimals":6},{"chainId":2,"address":"0xbd31ea8212119f94a611fa969881cba3ea06fa3d","decimals":6},{"chainId":4,"address":"0x156ab3346823B651294766e23e6Cf87254d68962","decimals":6},{"chainId":5,"address":"0x9cd6746665D9557e1B9a775819625711d0693439","decimals":6},{"chainId":6,"address":"0x70928E5B188def72817b7775F0BF6325968e563B","decimals":6},{"chainId":7,"address":"0x4F43717B20ae319Aa50BC5B2349B93af5f7Ac823","decimals":6},{"chainId":10,"address":"0x593AE1d34c8BD7587C11D539E4F42BFf242c82Af","decimals":6},{"chainId":9,"address":"0x12302fbE05a7e833f87d4B7843F58d19BE4FdE3B","decimals":6}]},{"symbol":"UST","displayName":"UST (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i/logo.png","coinGeckoId":"terra-usd","nativeDetails":{"chainId":3,"address":"uusd","decimals":6},"wrappedDetails":[{"chainId":1,"address":"9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i","decimals":6},{"chainId":2,"address":"0xa693B19d2931d498c5B318dF961919BB4aee87a5","decimals":6},{"chainId":4,"address":"0x3d4350cD54aeF9f9b2C29435e0fa809957B3F30a","decimals":6},{"chainId":5,"address":"0xE6469Ba6D2fD6130788E0eA9C0a0515900563b59","decimals":6},{"chainId":6,"address":"0xb599c3590F42f8F995ECfa0f85D2980B76862fc1","decimals":6},{"chainId":7,"address":"0xa1E73c01E0cF7930F5e91CB291031739FE5Ad6C2","decimals":6},{"chainId":10,"address":"0x846e4D51d7E2043C1a87E0Ab7490B93FB940357b","decimals":6},{"chainId":9,"address":"0x8D07bBb478B84f7E940e97C8e9cF7B3645166b03","decimals":6}]},{"symbol":"aUST","displayName":"AnchorUST (Portal)","logo":"","coinGeckoId":"anchorust","nativeDetails":{"chainId":3,"address":"terra1hzh9vpxhsk8253se0vv5jj6etdvxu3nv8z07zu","decimals":6},"wrappedDetails":[{"chainId":1,"address":"4CsZsUCoKFiaGyU7DEVDayqeVtG8iqgGDR6RjzQmzQao","decimals":6},{"chainId":4,"address":"0x8b04E56A8cd5f4D465b784ccf564899F30Aaf88C","decimals":6}]},{"symbol":"DAIpo","displayName":"DAI (Portal from Polygon)","logo":"","coinGeckoId":"dai","nativeDetails":{"chainId":5,"address":"0x8f3cf7ad23cd3cadbd9735aff958023239c6a063","decimals":18},"wrappedDetails":[{"chainId":1,"address":"4Fo67MYQpVhZj9R7jQTd63FPAnWbPpaafAUxsMGX2geP","decimals":8}]},{"symbol":"MATICpo","displayName":"MATIC (Portal from Polygon)","logo":"","coinGeckoId":"polygon","nativeDetails":{"chainId":5,"address":"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270","decimals":18},"wrappedDetails":[{"chainId":1,"address":"Gz7VkD4MacbEB6yC5XD3HcumEiYx2EtDYYrfikGsvopG","decimals":8},{"chainId":2,"address":"0x7c9f4C87d911613Fe9ca58b579f737911AAD2D43","decimals":18},{"chainId":4,"address":"0xc836d8dC361E44DbE64c4862D55BA041F88Ddd39","decimals":18},{"chainId":6,"address":"0xf2f13f0B7008ab2FA4A2418F4ccC3684E49D20Eb","decimals":18}]},{"symbol":"QUICK","displayName":"Quickswap (Portal)","logo":"","coinGeckoId":"quickswap","nativeDetails":{"chainId":5,"address":"0x831753dd7087cac61ab5644b308642cc1c33dc13","decimals":18},"wrappedDetails":[{"chainId":1,"address":"5njTmK53Ss5jkiHHZvzabVzZj6ztu6WYWpAPYgbVnbjs","decimals":8}]},{"symbol":"USDCpo","displayName":"USD Coin (PoS) (Portal from Polygon)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png","coinGeckoId":"usd-coin","nativeDetails":{"chainId":5,"address":"0x2791bca1f2de4661ed88a30c99a7a9449aa84174","decimals":6},"wrappedDetails":[{"chainId":1,"address":"E2VmbootbVCBkMNNxKQgCLMS1X3NoGMaYAsufaAsf7M","decimals":6},{"chainId":2,"address":"0x566957eF80F9fd5526CD2BEF8BE67035C0b81130","decimals":6},{"chainId":4,"address":"0x672147dD47674757C457eB155BAA382cc10705Dd","decimals":6},{"chainId":6,"address":"0x543672E9CBEC728CBBa9C3Ccd99ed80aC3607FA8","decimals":6},{"chainId":7,"address":"0x3E62a9c3aF8b810dE79645C4579acC8f0d06a241","decimals":6}]},{"symbol":"USDTpo","displayName":"Tether USD (PoS) (Portal from Polygon)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1/logo.png","coinGeckoId":"tether","nativeDetails":{"chainId":5,"address":"0xc2132d05d31c914a87c6611c10748aeb04b58e8f","decimals":6},"wrappedDetails":[{"chainId":1,"address":"5goWRao6a3yNC4d6UjMdQxonkCMvKBwdpubU3qhfcdf1","decimals":6},{"chainId":7,"address":"0xFffD69E757d8220CEA60dc80B9Fe1a30b58c94F3","decimals":6}]},{"symbol":"AVAX","displayName":"AVAX (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/KgV1GvrHQmRBY8sHQQeUKwTm2r2h8t4C8qt12Cw1HVE/logo.png","coinGeckoId":"avalanche","nativeDetails":{"chainId":6,"address":"0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7","decimals":18},"wrappedDetails":[{"chainId":1,"address":"KgV1GvrHQmRBY8sHQQeUKwTm2r2h8t4C8qt12Cw1HVE","decimals":8},{"chainId":2,"address":"0x85f138bfEE4ef8e540890CFb48F620571d67Eda3","decimals":18},{"chainId":4,"address":"0x96412902aa9aFf61E13f085e70D3152C6ef2a817","decimals":18},{"chainId":5,"address":"0x7Bb11E7f8b10E9e571E5d8Eace04735fDFB2358a","decimals":18},{"chainId":7,"address":"0x32847e63E99D3a044908763056e25694490082F8","decimals":18}]},{"symbol":"JOE","displayName":"JoeToken (Portal)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/CriXdFS9iRAYbGEQiTcUqbWwG9RBmYt5B6LwTnoJ61Sm/logo.png","coinGeckoId":"joe","nativeDetails":{"chainId":6,"address":"0x6e84a6216ea6dacc71ee8e6b0a5b7322eebc0fdd","decimals":18},"wrappedDetails":[{"chainId":1,"address":"CriXdFS9iRAYbGEQiTcUqbWwG9RBmYt5B6LwTnoJ61Sm","decimals":8}]},{"symbol":"USDCav","displayName":"USD Coin (Portal from Avalanche)","logo":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png","coinGeckoId":"usd-coin","nativeDetails":{"chainId":6,"address":"0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664","decimals":6},"wrappedDetails":[{"chainId":1,"address":"AGqKX7F4mqJ8x2mUQVangJb5pWQJApaKoUfe5gXM53CV","decimals":8},{"chainId":4,"address":"0xc1F47175d96Fe7c4cD5370552e5954f384E3C791","decimals":6},{"chainId":7,"address":"0x05CbE6319Dcc937BdbDf0931466F4fFd0d392B47","decimals":6}]},{"symbol":"USDTav","displayName":"Tether USD (Portal from Avalanche)","logo":"","coinGeckoId":"tether","nativeDetails":{"chainId":6,"address":"0xc7198437980c041c805a1edcba50c1ce5db95118","decimals":6},"wrappedDetails":[{"chainId":1,"address":"B2wfeYz5VtBnQVrX4M8F6FeDrprVrzKPws5qg1in8bzR","decimals":8},{"chainId":4,"address":"0x2B90E061a517dB2BbD7E39Ef7F733Fd234B494CA","decimals":6},{"chainId":7,"address":"0x05832a0905E516f29344ADBa1c2052a788B10129","decimals":6}]},{"symbol":"ROSE","displayName":"ROSE (Portal)","logo":"https://assets.coingecko.com/coins/images/13162/small/rose.png","coinGeckoId":"oasis-network","nativeDetails":{"chainId":7,"address":"0x21C718C22D52d0F3a789b752D4c2fD5908a8A733","decimals":18},"wrappedDetails":[{"chainId":1,"address":"S3SQfD6RheMXQ3EEYn1Z5sJsbtwfXdt7tSAVXPQFtYo","decimals":8},{"chainId":2,"address":"0x26B80FBfC01b71495f477d5237071242e0d959d7","decimals":18},{"chainId":4,"address":"0x6c6D604D3f07aBE287C1A3dF0281e999A83495C0","decimals":18},{"chainId":6,"address":"0x12AF5C1a232675f62F405b5812A80e7a6F75D746","decimals":18}]},{"symbol":"SWEAT","displayName":"Sweat Economy","logo":"https://assets.coingecko.com/coins/images/25057/small/fhD9Xs16_400x400.jpg?1649947000","coinGeckoId":"sweatcoin","nativeDetails":{"chainId":15,"address":"token.sweat","decimals":18},"wrappedDetails":[{"chainId":2,"address":"0xB4b9DC1C77bdbb135eA907fd5a08094d98883A35","decimals":18},{"chainId":4,"address":"0x510Ad22d8C956dCC20f68932861f54A591001283","decimals":18}]}] \ No newline at end of file diff --git a/apps/ui/src/hooks/index.ts b/apps/ui/src/hooks/index.ts index 7059127ad..eeb156d4a 100644 --- a/apps/ui/src/hooks/index.ts +++ b/apps/ui/src/hooks/index.ts @@ -7,3 +7,4 @@ export * from "./solana"; export * from "./swim"; export * from "./utils"; export * from "./wallets"; +export * from "./wormhole"; diff --git a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts index 89f8d5370..b24edddd8 100644 --- a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts @@ -13,7 +13,6 @@ import { useMutation } from "react-query"; import { ECOSYSTEMS, Protocol, - getSolanaTokenDetails, getTokenDetailsForEcosystem, getWormholeRetries, } from "../../config"; @@ -128,7 +127,6 @@ export const useFromSolanaTransferMutation = () => { } const amount = Amount.fromHuman(token, value); const evmEcosystem = ECOSYSTEMS[toEcosystem]; - const solanaTokenDetails = getSolanaTokenDetails(token); const evmWallet = wallets[toEcosystem].wallet; if (!evmWallet) { throw new Error("No EVM wallet"); @@ -142,7 +140,7 @@ export const useFromSolanaTransferMutation = () => { throw new Error("No token details"); } const splTokenAccount = findTokenAccountForMint( - solanaTokenDetails.address, + token.nativeDetails.address, solanaWalletAddress, splTokenAccounts, ); @@ -157,12 +155,12 @@ export const useFromSolanaTransferMutation = () => { solanaClient.generateInitiatePortalTransferTxs({ atomicAmount: amount.toAtomicString(SOLANA_ECOSYSTEM_ID), interactionId, + sourceAddress: token.nativeDetails.address, targetAddress: formatWormholeAddress( evmEcosystem.protocol, evmWalletAddress, ), targetChainId: evmEcosystem.wormholeChainId, - tokenProjectId: token.projectId, wallet: solanaWallet, auxiliarySigner, wrappedTokenInfo: getWrappedTokenInfo(token, SOLANA_ECOSYSTEM_ID), diff --git a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts index 1662c04e3..1f91397cc 100644 --- a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts @@ -105,12 +105,12 @@ export const useToSolanaTransferMutation = () => { ].generateInitiatePortalTransferTxs({ atomicAmount: humanDecimalToAtomicString(value, token, fromEcosystem), interactionId, + sourceAddress: token.nativeDetails.address, targetAddress: formatWormholeAddress( Protocol.Solana, splTokenAccount.address.toBase58(), ), targetChainId: WormholeChainId.Solana, - tokenProjectId: token.projectId, wallet: evmWallet, wrappedTokenInfo: getWrappedTokenInfo(token, fromEcosystem), }); diff --git a/apps/ui/src/hooks/wormhole/index.ts b/apps/ui/src/hooks/wormhole/index.ts new file mode 100644 index 000000000..aec868412 --- /dev/null +++ b/apps/ui/src/hooks/wormhole/index.ts @@ -0,0 +1 @@ +export * from "./useWormholeTransfer"; diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts new file mode 100644 index 000000000..1e2765be8 --- /dev/null +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts @@ -0,0 +1,137 @@ +import { + getEmitterAddressEth, + parseSequenceFromLogEth, +} from "@certusone/wormhole-sdk"; +import type { EvmTx } from "@swim-io/evm"; +import { EvmTxType, isEvmEcosystemId } from "@swim-io/evm"; +import { findOrThrow, humanToAtomic } from "@swim-io/utils"; +import { useMutation } from "react-query"; +import shallow from "zustand/shallow.js"; + +import { ECOSYSTEM_LIST, Protocol, getWormholeRetries } from "../../config"; +import { selectConfig } from "../../core/selectors"; +import { useEnvironment } from "../../core/store"; +import type { WormholeTransfer } from "../../models"; +import { + formatWormholeAddress, + getSignedVaaWithRetry, + getWrappedTokenInfoFromNativeDetails, +} from "../../models"; +import { useWallets } from "../crossEcosystem"; +import { useGetEvmClient } from "../evm"; + +export const useTransferEvmToEvmMutation = () => { + const { chains, wormhole } = useEnvironment(selectConfig, shallow); + const getEvmClient = useGetEvmClient(); + const wallets = useWallets(); + + return useMutation( + async ({ + interactionId, + value, + sourceDetails, + targetDetails, + nativeDetails, + onTxResult, + }: WormholeTransfer) => { + if (!wormhole) { + throw new Error("No Wormhole RPC configured"); + } + + const [sourceEcosystem, targetEcosystem] = [ + sourceDetails.chainId, + targetDetails.chainId, + ].map((chainId) => + findOrThrow( + ECOSYSTEM_LIST, + (ecosystem) => ecosystem.wormholeChainId === chainId, + ), + ); + const sourceEcosystemId = sourceEcosystem.id; + if (!isEvmEcosystemId(sourceEcosystemId)) { + throw new Error("Invalid source chain"); + } + const targetEcosystemId = targetEcosystem.id; + if (!isEvmEcosystemId(targetEcosystemId)) { + throw new Error("Invalid target chain"); + } + const sourceChain = findOrThrow( + chains[Protocol.Evm], + (chain) => chain.ecosystem === sourceEcosystemId, + ); + const targetChain = findOrThrow( + chains[Protocol.Evm], + (chain) => chain.ecosystem === targetEcosystemId, + ); + + const evmWallet = wallets[sourceEcosystemId].wallet; + if (evmWallet === null) { + throw new Error("Missing EVM wallet"); + } + const evmWalletAddress = evmWallet.address; + if (evmWalletAddress === null) { + throw new Error("Missing EVM wallet address"); + } + + const sourceClient = getEvmClient(sourceEcosystemId); + const targetClient = getEvmClient(targetEcosystemId); + + await evmWallet.switchNetwork(sourceChain.chainId); + // Process transfer if transfer txId does not exist + const sourceTxGenerator = sourceClient.generateInitiatePortalTransferTxs({ + atomicAmount: humanToAtomic(value, sourceDetails.decimals).toString(), + interactionId, + sourceAddress: sourceDetails.address, + targetAddress: formatWormholeAddress(Protocol.Evm, evmWalletAddress), + targetChainId: targetDetails.chainId, + wallet: evmWallet, + wrappedTokenInfo: getWrappedTokenInfoFromNativeDetails( + sourceDetails.chainId, + nativeDetails, + ), + }); + + let transferTx: EvmTx | null = null; + for await (const result of sourceTxGenerator) { + if (result.type === EvmTxType.PortalTransferTokens) { + transferTx = result.tx; + } + onTxResult({ + chainId: sourceDetails.chainId, + txId: result.tx.id, + }); + } + + if (transferTx === null) { + throw new Error("Missing source transaction"); + } + const sequence = parseSequenceFromLogEth( + transferTx.original, + sourceChain.wormhole.bridge, + ); + const retries = getWormholeRetries(sourceDetails.chainId); + const { vaaBytes: vaa } = await getSignedVaaWithRetry( + [...wormhole.rpcUrls], + sourceDetails.chainId, + getEmitterAddressEth(sourceChain.wormhole.portal), + sequence, + undefined, + undefined, + retries, + ); + + await evmWallet.switchNetwork(targetChain.chainId); + const targetTxGenerator = targetClient.generateCompletePortalTransferTxs({ + interactionId, + vaa, + wallet: evmWallet, + }); + for await (const result of targetTxGenerator) { + onTxResult({ + chainId: targetDetails.chainId, + txId: result.tx.id, + }); + } + }, + ); +}; diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts new file mode 100644 index 000000000..4a7c88514 --- /dev/null +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts @@ -0,0 +1,155 @@ +import { + getEmitterAddressEth, + parseSequenceFromLogEth, +} from "@certusone/wormhole-sdk"; +import { Keypair } from "@solana/web3.js"; +import type { EvmTx } from "@swim-io/evm"; +import { EvmTxType, isEvmEcosystemId } from "@swim-io/evm"; +import { SOLANA_ECOSYSTEM_ID, solana } from "@swim-io/solana"; +import { findOrThrow, humanToAtomic } from "@swim-io/utils"; +import { WormholeChainId } from "@swim-io/wormhole"; +import { useMutation, useQueryClient } from "react-query"; +import shallow from "zustand/shallow.js"; + +import { ECOSYSTEM_LIST, Protocol, getWormholeRetries } from "../../config"; +import { selectConfig } from "../../core/selectors"; +import { useEnvironment } from "../../core/store"; +import type { WormholeTransfer } from "../../models"; +import { + findOrCreateSplTokenAccount, + formatWormholeAddress, + getSignedVaaWithRetry, + getWrappedTokenInfoFromNativeDetails, +} from "../../models"; +import { useWallets } from "../crossEcosystem"; +import { useGetEvmClient } from "../evm"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; + +export const useTransferEvmToSolanaMutation = () => { + const queryClient = useQueryClient(); + const { env } = useEnvironment(); + const { chains, wormhole } = useEnvironment(selectConfig, shallow); + const getEvmClient = useGetEvmClient(); + const solanaClient = useSolanaClient(); + const wallets = useWallets(); + const solanaWallet = wallets[SOLANA_ECOSYSTEM_ID].wallet; + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); + + return useMutation( + async ({ + interactionId, + value, + sourceDetails, + targetDetails, + nativeDetails, + onTxResult, + }: WormholeTransfer) => { + if (!wormhole) { + throw new Error("No Wormhole RPC configured"); + } + if (!solanaWallet) { + throw new Error("No Solana wallet"); + } + if (targetDetails.chainId !== WormholeChainId.Solana) { + throw new Error("Invalid target chain"); + } + + const evmEcosystem = findOrThrow( + ECOSYSTEM_LIST, + (ecosystem) => ecosystem.wormholeChainId === sourceDetails.chainId, + ); + const evmEcosystemId = evmEcosystem.id; + if (!isEvmEcosystemId(evmEcosystemId)) { + throw new Error("Invalid EVM chain"); + } + const evmChain = findOrThrow( + chains[Protocol.Evm], + ({ ecosystem }) => ecosystem === evmEcosystemId, + ); + const evmClient = getEvmClient(evmEcosystemId); + const evmWallet = wallets[evmEcosystemId].wallet; + if (!evmWallet) { + throw new Error("Missing EVM wallet"); + } + + const { tokenAccount, creationTxId } = await findOrCreateSplTokenAccount({ + env, + solanaClient, + wallet: solanaWallet, + queryClient, + splTokenMintAddress: targetDetails.address, + splTokenAccounts, + }); + const splTokenAccountAddress = tokenAccount.address.toBase58(); + if (creationTxId) { + onTxResult({ + chainId: targetDetails.chainId, + txId: creationTxId, + }); + } + + await evmWallet.switchNetwork(evmChain.chainId); + // Process transfer if transfer txId does not exist + const evmTxGenerator = evmClient.generateInitiatePortalTransferTxs({ + atomicAmount: humanToAtomic(value, sourceDetails.decimals).toString(), + interactionId, + sourceAddress: sourceDetails.address, + targetAddress: formatWormholeAddress( + Protocol.Solana, + splTokenAccountAddress, + ), + targetChainId: solana.wormholeChainId, + wallet: evmWallet, + wrappedTokenInfo: getWrappedTokenInfoFromNativeDetails( + sourceDetails.chainId, + nativeDetails, + ), + }); + + let evmTransferTx: EvmTx | null = null; + for await (const result of evmTxGenerator) { + if (result.type === EvmTxType.PortalTransferTokens) { + evmTransferTx = result.tx; + } + onTxResult({ + chainId: sourceDetails.chainId, + txId: result.tx.id, + }); + } + + if (evmTransferTx === null) { + throw new Error("Missing EVM transaction"); + } + + const sequence = parseSequenceFromLogEth( + evmTransferTx.original, + evmChain.wormhole.bridge, + ); + + const auxiliarySigner = Keypair.generate(); + const retries = getWormholeRetries(evmEcosystem.wormholeChainId); + const { vaaBytes: vaa } = await getSignedVaaWithRetry( + [...wormhole.rpcUrls], + evmEcosystem.wormholeChainId, + getEmitterAddressEth(evmChain.wormhole.portal), + sequence, + undefined, + undefined, + retries, + ); + const solanaTxGenerator = solanaClient.generateCompletePortalTransferTxs({ + interactionId, + vaa, + wallet: solanaWallet, + auxiliarySigner, + }); + + for await (const result of solanaTxGenerator) { + onTxResult({ + chainId: targetDetails.chainId, + txId: result.tx.id, + }); + } + }, + ); +}; diff --git a/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts new file mode 100644 index 000000000..06d7c222b --- /dev/null +++ b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts @@ -0,0 +1,133 @@ +import { getEmitterAddressSolana } from "@certusone/wormhole-sdk"; +import { Keypair } from "@solana/web3.js"; +import { isEvmEcosystemId } from "@swim-io/evm"; +import type { SolanaTx } from "@swim-io/solana"; +import { + SOLANA_ECOSYSTEM_ID, + parseSequenceFromLogSolana, + solana, +} from "@swim-io/solana"; +import { findOrThrow, humanToAtomic } from "@swim-io/utils"; +import { useMutation } from "react-query"; +import shallow from "zustand/shallow.js"; + +import { ECOSYSTEM_LIST, Protocol, getWormholeRetries } from "../../config"; +import { selectConfig } from "../../core/selectors"; +import { useEnvironment } from "../../core/store"; +import type { WormholeTransfer } from "../../models"; +import { + formatWormholeAddress, + getSignedVaaWithRetry, + getWrappedTokenInfoFromNativeDetails, +} from "../../models"; +import { useWallets } from "../crossEcosystem"; +import { useGetEvmClient } from "../evm"; +import { useSolanaClient } from "../solana"; + +export const useTransferSolanaToEvmMutation = () => { + const { chains, wormhole } = useEnvironment(selectConfig, shallow); + const [solanaChain] = chains[Protocol.Solana]; + const getEvmClient = useGetEvmClient(); + const solanaClient = useSolanaClient(); + const wallets = useWallets(); + const solanaWallet = wallets[SOLANA_ECOSYSTEM_ID].wallet; + + return useMutation( + async ({ + interactionId, + value, + sourceDetails, + targetDetails, + nativeDetails, + onTxResult, + }: WormholeTransfer) => { + if (!wormhole) { + throw new Error("No Wormhole RPC configured"); + } + if (!solanaWallet) { + throw new Error("No Solana wallet"); + } + const solanaWalletAddress = solanaWallet.address ?? null; + if (!solanaWalletAddress) { + throw new Error("No Solana wallet address"); + } + + const evmEcosystem = findOrThrow( + ECOSYSTEM_LIST, + (ecosystem) => ecosystem.wormholeChainId === targetDetails.chainId, + ); + const evmEcosystemId = evmEcosystem.id; + if (!isEvmEcosystemId(evmEcosystemId)) { + throw new Error("Invalid EVM chain"); + } + const evmChain = findOrThrow( + chains[Protocol.Evm], + ({ ecosystem }) => ecosystem === evmEcosystemId, + ); + const evmClient = getEvmClient(evmEcosystemId); + const evmWallet = wallets[evmEcosystemId].wallet; + if (!evmWallet) { + throw new Error("No EVM wallet"); + } + const evmWalletAddress = evmWallet.address; + if (evmWalletAddress === null) { + throw new Error("No EVM wallet address"); + } + + const auxiliarySigner = Keypair.generate(); + const solanaTxGenerator = solanaClient.generateInitiatePortalTransferTxs({ + atomicAmount: humanToAtomic(value, sourceDetails.decimals).toString(), + interactionId, + sourceAddress: sourceDetails.address, + targetAddress: formatWormholeAddress(Protocol.Evm, evmWalletAddress), + targetChainId: evmEcosystem.wormholeChainId, + wallet: solanaWallet, + auxiliarySigner, + wrappedTokenInfo: getWrappedTokenInfoFromNativeDetails( + sourceDetails.chainId, + nativeDetails, + ), + }); + + let solanaTx: SolanaTx | null = null; + for await (const result of solanaTxGenerator) { + solanaTx = result.tx; + onTxResult({ + chainId: sourceDetails.chainId, + txId: result.tx.id, + }); + } + if (solanaTx === null) { + throw new Error("Missing Solana transaction"); + } + + const sequence = parseSequenceFromLogSolana(solanaTx.original); + const emitterAddress = await getEmitterAddressSolana( + solanaChain.wormhole.portal, + ); + const retries = getWormholeRetries(solana.wormholeChainId); + const { vaaBytes: vaa } = await getSignedVaaWithRetry( + [...wormhole.rpcUrls], + sourceDetails.chainId, + emitterAddress, + sequence, + undefined, + undefined, + retries, + ); + await evmWallet.switchNetwork(evmChain.chainId); + const evmTxGenerator = evmClient.generateCompletePortalTransferTxs({ + interactionId, + vaa, + wallet: evmWallet, + }); + + for await (const result of evmTxGenerator) { + onTxResult({ + chainId: targetDetails.chainId, + txId: result.tx.id, + }); + } + }, + ); +}; diff --git a/apps/ui/src/hooks/wormhole/useWormholeTransfer.ts b/apps/ui/src/hooks/wormhole/useWormholeTransfer.ts new file mode 100644 index 000000000..adedceaa3 --- /dev/null +++ b/apps/ui/src/hooks/wormhole/useWormholeTransfer.ts @@ -0,0 +1,42 @@ +import { CHAIN_ID_SOLANA, isEVMChain } from "@certusone/wormhole-sdk"; +import { useMutation } from "react-query"; + +import type { WormholeTransfer } from "../../models"; + +import { useTransferEvmToEvmMutation } from "./useTransferEvmToEvmMutation"; +import { useTransferEvmToSolanaMutation } from "./useTransferEvmToSolanaMutation"; +import { useTransferSolanaToEvmMutation } from "./useTransferSolanaToEvmMutation"; + +export const useWormholeTransfer = () => { + const { mutateAsync: transferEvmToEvm } = useTransferEvmToEvmMutation(); + const { mutateAsync: transferEvmToSolana } = useTransferEvmToSolanaMutation(); + const { mutateAsync: transferSolanaToEvm } = useTransferSolanaToEvmMutation(); + + return useMutation(async (transfer: WormholeTransfer) => { + const { + sourceDetails: { chainId: sourceChainId }, + targetDetails: { chainId: targetChainId }, + } = transfer; + if (sourceChainId === targetChainId) { + throw new Error("Source and target chains are the same"); + } + + if (sourceChainId === CHAIN_ID_SOLANA) { + if (isEVMChain(targetChainId)) { + return await transferSolanaToEvm(transfer); + } + throw new Error(`Transfer from Solana to ${targetChainId} not supported`); + } + if (isEVMChain(sourceChainId)) { + if (targetChainId === CHAIN_ID_SOLANA) { + return await transferEvmToSolana(transfer); + } + if (isEVMChain(targetChainId)) { + return await transferEvmToEvm(transfer); + } + throw new Error( + `Transfer from ${sourceChainId} to ${targetChainId} not supported`, + ); + } + }); +}; diff --git a/apps/ui/src/models/wormhole/getWrappedTokenInfo.ts b/apps/ui/src/models/wormhole/getWrappedTokenInfo.ts index d13d88667..275b51360 100644 --- a/apps/ui/src/models/wormhole/getWrappedTokenInfo.ts +++ b/apps/ui/src/models/wormhole/getWrappedTokenInfo.ts @@ -1,7 +1,7 @@ import type { WrappedTokenInfo } from "@swim-io/core"; import type { EcosystemId, TokenConfig } from "../../config"; -import { ECOSYSTEMS, getTokenDetailsForEcosystem } from "../../config"; +import { ECOSYSTEMS } from "../../config"; import { formatWormholeAddress } from "./formatWormholeAddress"; @@ -14,19 +14,11 @@ export const getWrappedTokenInfo = ( } const nativeAddress = tokenConfig.nativeDetails.address; const nativeEcosystem = ECOSYSTEMS[tokenConfig.nativeEcosystemId]; - const sourceTokenDetails = getTokenDetailsForEcosystem( - tokenConfig, - sourceEcosystemId, - ); - if (sourceTokenDetails === null) { - throw new Error("No source token details"); - } return { originAddress: formatWormholeAddress( nativeEcosystem.protocol, nativeAddress, ), originChainId: nativeEcosystem.wormholeChainId, - wrappedAddress: sourceTokenDetails.address, }; }; diff --git a/apps/ui/src/models/wormhole/index.ts b/apps/ui/src/models/wormhole/index.ts index 6ca9cc732..9b78077c0 100644 --- a/apps/ui/src/models/wormhole/index.ts +++ b/apps/ui/src/models/wormhole/index.ts @@ -3,3 +3,4 @@ export * from "./formatWormholeAddress"; export * from "./getWrappedTokenInfo"; export * from "./guardiansRpc"; export * from "./solana"; +export * from "./transfer"; diff --git a/apps/ui/src/models/wormhole/transfer.ts b/apps/ui/src/models/wormhole/transfer.ts new file mode 100644 index 000000000..007e40f76 --- /dev/null +++ b/apps/ui/src/models/wormhole/transfer.ts @@ -0,0 +1,57 @@ +import type { ChainId } from "@certusone/wormhole-sdk"; +import type { WrappedTokenInfo } from "@swim-io/core"; +import { findOrThrow } from "@swim-io/utils"; +import type Decimal from "decimal.js"; + +import { ECOSYSTEM_LIST } from "../../config"; + +import { formatWormholeAddress } from "./formatWormholeAddress"; + +export interface TxResult { + readonly chainId: ChainId; + readonly txId: string; +} + +export interface WormholeTokenDetails { + readonly chainId: ChainId; + readonly address: string; + readonly decimals: number; +} + +export interface WormholeToken { + readonly symbol: string; + readonly displayName: string; + readonly logo: string; + readonly coinGeckoId: string; + readonly nativeDetails: WormholeTokenDetails; + readonly wrappedDetails: readonly WormholeTokenDetails[]; +} + +export interface WormholeTransfer { + readonly interactionId: string; + readonly value: Decimal; + readonly sourceDetails: WormholeTokenDetails; + readonly targetDetails: WormholeTokenDetails; + readonly nativeDetails: WormholeTokenDetails; + readonly onTxResult: (txResult: TxResult) => any; +} + +export const getWrappedTokenInfoFromNativeDetails = ( + sourceChainId: ChainId, + nativeDetails: WormholeTokenDetails, +): WrappedTokenInfo | undefined => { + if (sourceChainId === nativeDetails.chainId) { + return undefined; + } + const nativeEcosystem = findOrThrow( + ECOSYSTEM_LIST, + (ecosystem) => ecosystem.wormholeChainId === nativeDetails.chainId, + ); + return { + originAddress: formatWormholeAddress( + nativeEcosystem.protocol, + nativeDetails.address, + ), + originChainId: nativeDetails.chainId, + }; +}; diff --git a/apps/ui/src/pages/WormholePage.scss b/apps/ui/src/pages/WormholePage.scss new file mode 100644 index 000000000..010e88b94 --- /dev/null +++ b/apps/ui/src/pages/WormholePage.scss @@ -0,0 +1,10 @@ +.wormholeForm { + width: 500px; + transition: all 0.5s ease; +} + +@media (min-width: 768px) { + .wormholeForm { + min-width: 460px; + } +} diff --git a/apps/ui/src/pages/WormholePage.tsx b/apps/ui/src/pages/WormholePage.tsx new file mode 100644 index 000000000..3d12a9f16 --- /dev/null +++ b/apps/ui/src/pages/WormholePage.tsx @@ -0,0 +1,38 @@ +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, + EuiTitle, +} from "@elastic/eui"; +import type { ReactElement } from "react"; +import { useTranslation } from "react-i18next"; + +import { WormholeForm } from "../components/WormholeForm"; +import { useTitle } from "../hooks"; + +import "./WormholePage.scss"; + +const WormholePage = (): ReactElement => { + const { t } = useTranslation(); + useTitle(t("nav.wormhole")); + + return ( + + + + + +

{"Wormhole"}

+
+ + +
+
+
+
+ ); +}; + +export default WormholePage; diff --git a/apps/ui/tsconfig.json b/apps/ui/tsconfig.json index 155e517d6..4fccb0f50 100644 --- a/apps/ui/tsconfig.json +++ b/apps/ui/tsconfig.json @@ -8,5 +8,10 @@ "noEmit": true, "typeRoots": ["./node_modules/@types", "./src/types"] }, - "include": ["src/"] + "include": ["src/"], + "ts-node": { + "compilerOptions": { + "module": "commonjs" + } + } } diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 7e8d35efc..9934fe184 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1,5 +1,4 @@ import type { ChainId } from "@certusone/wormhole-sdk"; -import type { TokenProjectId } from "@swim-io/token-projects"; import type Decimal from "decimal.js"; import type { ChainConfig } from "./chain"; @@ -10,17 +9,16 @@ export interface WrappedTokenInfo { readonly originChainId: ChainId; /** Standardized Wormhole format, ie 32 bytes */ readonly originAddress: Uint8Array; - /** Ecosystem-specific format */ - readonly wrappedAddress: string; } export interface InitiatePortalTransferParams { readonly atomicAmount: string; readonly interactionId: string; + /** Ecosystem-specific address format */ + readonly sourceAddress: string; /** Standardized Wormhole format, ie 32 bytes */ readonly targetAddress: Uint8Array; readonly targetChainId: ChainId; - readonly tokenProjectId: TokenProjectId; readonly wallet: Wallet; readonly wrappedTokenInfo?: WrappedTokenInfo; } diff --git a/packages/evm/src/client.ts b/packages/evm/src/client.ts index 1addf126c..036c2bfed 100644 --- a/packages/evm/src/client.ts +++ b/packages/evm/src/client.ts @@ -4,7 +4,7 @@ import { createNonce, getAllowanceEth, } from "@certusone/wormhole-sdk"; -import { Client, getTokenDetails } from "@swim-io/core"; +import { Client } from "@swim-io/core"; import type { CompletePortalTransferParams, InitiatePortalTransferParams, @@ -154,11 +154,10 @@ export class EvmClient extends Client< public async *generateInitiatePortalTransferTxs({ atomicAmount, interactionId, + sourceAddress, targetAddress, targetChainId, - tokenProjectId, wallet, - wrappedTokenInfo, }: InitiatePortalTransferParams): AsyncGenerator< TxGeneratorResult< TransactionReceipt, @@ -166,15 +165,11 @@ export class EvmClient extends Client< EvmTxType.Erc20Approve | EvmTxType.PortalTransferTokens > > { - const mintAddress = - wrappedTokenInfo?.wrappedAddress ?? - getTokenDetails(this.chainConfig, tokenProjectId).address; - await wallet.switchNetwork(this.chainConfig.chainId); const approvalGenerator = this.generateErc20ApproveTxs({ atomicAmount, - mintAddress, + mintAddress: sourceAddress, spenderAddress: this.chainConfig.wormhole.portal, wallet, }); @@ -185,7 +180,7 @@ export class EvmClient extends Client< const tx = await this.transferToken({ interactionId, - mintAddress, + mintAddress: sourceAddress, atomicAmount, targetChainId, targetAddress, diff --git a/packages/solana/src/client.ts b/packages/solana/src/client.ts index 7974a65d6..e85352107 100644 --- a/packages/solana/src/client.ts +++ b/packages/solana/src/client.ts @@ -25,7 +25,7 @@ import type { TokenDetails, TxGeneratorResult, } from "@swim-io/core"; -import { Client, getTokenDetails } from "@swim-io/core"; +import { Client } from "@swim-io/core"; import { atomicToHuman, chunks, sleep } from "@swim-io/utils"; import Decimal from "decimal.js"; @@ -177,10 +177,10 @@ export class SolanaClient extends Client< public async *generateInitiatePortalTransferTxs({ atomicAmount, - targetChainId, + sourceAddress, targetAddress, + targetChainId, interactionId, - tokenProjectId, wallet, wrappedTokenInfo, auxiliarySigner = Keypair.generate(), @@ -197,11 +197,8 @@ export class SolanaClient extends Client< if (solanaWalletAddress === null) { throw new Error("No Solana wallet address"); } - const mintAddress = - wrappedTokenInfo?.wrappedAddress ?? - getTokenDetails(this.chainConfig, tokenProjectId).address; const associatedTokenAccountAddress = getAssociatedTokenAddressSync( - new PublicKey(mintAddress), + new PublicKey(sourceAddress), new PublicKey(solanaWalletAddress), ).toBase58(); const txRequest = await createTransferFromSolanaTx({ @@ -212,7 +209,7 @@ export class SolanaClient extends Client< payerAddress: solanaWalletAddress, auxiliarySignerAddress: auxiliarySigner.publicKey.toString(), fromAddress: associatedTokenAccountAddress, - mintAddress, + mintAddress: sourceAddress, amount: BigInt(atomicAmount), targetAddress, targetChainId, diff --git a/yarn.lock b/yarn.lock index 7f1ecd27d..84e47f862 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7156,21 +7156,7 @@ __metadata: languageName: node linkType: hard -"@swim-io/aptos@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/aptos@npm:0.40.0" - dependencies: - "@swim-io/core": ^0.40.0 - "@swim-io/utils": ^0.40.0 - aptos: ^1.3.13 - peerDependencies: - decimal.js: ^10.3.1 - eventemitter3: ^4.0.7 - checksum: c20ed5033c04f12909ba46605ea40a5866db25cfe5ae08a8fe2e99c1a99fab2c8488e11d5737c74b5cc6ae282f2819097fd71e8a9af2a7dc507f08785d3a8496 - languageName: node - linkType: hard - -"@swim-io/aptos@workspace:packages/aptos": +"@swim-io/aptos@workspace:^, @swim-io/aptos@workspace:packages/aptos": version: 0.0.0-use.local resolution: "@swim-io/aptos@workspace:packages/aptos" dependencies: @@ -7261,19 +7247,6 @@ __metadata: languageName: node linkType: hard -"@swim-io/core@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/core@npm:0.40.0" - dependencies: - "@swim-io/token-projects": ^0.40.0 - "@swim-io/utils": ^0.40.0 - decimal.js: ^10.3.1 - peerDependencies: - "@certusone/wormhole-sdk": ^0.6.2 - checksum: 8f71a7c1708ae099d70fa6f39c3eac475702c948ed670924a66053ed8def4578358365a8e48d1fb31f253fd6df515b1827977f6072561159d7e604cfa8207afc - languageName: node - linkType: hard - "@swim-io/core@workspace:^, @swim-io/core@workspace:packages/core": version: 0.0.0-use.local resolution: "@swim-io/core@workspace:packages/core" @@ -7365,13 +7338,6 @@ __metadata: languageName: node linkType: hard -"@swim-io/evm-contracts@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/evm-contracts@npm:0.40.0" - checksum: 5061e0d2594e4912f271613f2cbbfb819238a809cb3c01a086339e325890607fd67efe8f5b47d6d434b016d8b92c6968331458a49ed4da9333753cc2fcf19145 - languageName: node - linkType: hard - "@swim-io/evm-contracts@workspace:^, @swim-io/evm-contracts@workspace:packages/evm-contracts": version: 0.0.0-use.local resolution: "@swim-io/evm-contracts@workspace:packages/evm-contracts" @@ -7426,27 +7392,7 @@ __metadata: languageName: node linkType: hard -"@swim-io/evm@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/evm@npm:0.40.0" - dependencies: - "@swim-io/core": ^0.40.0 - "@swim-io/evm-contracts": ^0.40.0 - "@swim-io/token-projects": ^0.40.0 - "@swim-io/utils": ^0.40.0 - graphql: ^16.6.0 - graphql-request: ^4.3.0 - moralis: ^1.8.0 - peerDependencies: - "@certusone/wormhole-sdk": ^0.6.2 - decimal.js: ^10.3.1 - ethers: ^5.6.9 - eventemitter3: ^4.0.7 - checksum: ba65ee3dcf51c9a4c1e329a7597b41c4226028f2fc76afd57c9e8185b20e04ef5563d47cdf017ec9aa122e148162408b16d544a167c6953b251a77fc83cebdfb - languageName: node - linkType: hard - -"@swim-io/evm@workspace:packages/evm": +"@swim-io/evm@workspace:^, @swim-io/evm@workspace:packages/evm": version: 0.0.0-use.local resolution: "@swim-io/evm@workspace:packages/evm" dependencies: @@ -7505,15 +7451,6 @@ __metadata: languageName: unknown linkType: soft -"@swim-io/pool-math@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/pool-math@npm:0.40.0" - peerDependencies: - decimal.js: ^10.3.1 - checksum: 6b3de0916194bcab630111aa1bc34d46d046c7d1960437a734b354a5f376e23e89ccfa228a077332ff33989373a9de6d883ff437849bd2cf61526d6db52d5843 - languageName: node - linkType: hard - "@swim-io/pool-math@workspace:^, @swim-io/pool-math@workspace:packages/pool-math": version: 0.0.0-use.local resolution: "@swim-io/pool-math@workspace:packages/pool-math" @@ -7640,19 +7577,7 @@ __metadata: languageName: unknown linkType: soft -"@swim-io/solana-contracts@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/solana-contracts@npm:0.40.0" - peerDependencies: - "@project-serum/anchor": ^0.25.0 - "@project-serum/borsh": ^0.2.5 - "@solana/spl-token": ^0.2.0 - "@solana/web3.js": ^1.50.1 - checksum: 5f78da938ff92fa660024ed2ad63345db68bee8b0f83f7e26cb86aa7e959a6e35d8afece1d11374274037b0821b4d55e1597f1ba40e1814a65e8faf75e75c041 - languageName: node - linkType: hard - -"@swim-io/solana-contracts@workspace:packages/solana-contracts": +"@swim-io/solana-contracts@workspace:^, @swim-io/solana-contracts@workspace:packages/solana-contracts": version: 0.0.0-use.local resolution: "@swim-io/solana-contracts@workspace:packages/solana-contracts" dependencies: @@ -7779,28 +7704,6 @@ __metadata: languageName: node linkType: hard -"@swim-io/solana@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/solana@npm:0.40.0" - dependencies: - "@ledgerhq/hw-transport": ^6.27.1 - "@ledgerhq/hw-transport-webusb": ^6.0.2 - "@project-serum/sol-wallet-adapter": 0.2.2 - "@swim-io/core": ^0.40.0 - "@swim-io/token-projects": ^0.40.0 - "@swim-io/utils": ^0.40.0 - peerDependencies: - "@certusone/wormhole-sdk": ^0.6.2 - "@project-serum/borsh": ^0.2.5 - "@solana/spl-token": ^0.3.4 - "@solana/web3.js": ^1.62.0 - bn.js: ^5.2.1 - decimal.js: ^10.3.1 - ethers: ^5.7.0 - checksum: 207c35b40b87f6e2bdbec0a50dcbe0cb5bb437d5170e2691790b56c1c91fff508ea2cb5ec9cb8abbf52dcd23a1ca1e1663e843abee9e10c57eec1996584c186b - languageName: node - linkType: hard - "@swim-io/solana@workspace:^, @swim-io/solana@workspace:packages/solana": version: 0.0.0-use.local resolution: "@swim-io/solana@workspace:packages/solana" @@ -7891,15 +7794,6 @@ __metadata: languageName: node linkType: hard -"@swim-io/token-projects@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/token-projects@npm:0.40.0" - dependencies: - "@swim-io/utils": ^0.40.0 - checksum: 48958357bcb6d30978833321f9e6db875581102416327f50b345515166505a5ce5e6e5f1b0d3d458ac440503f3cc9cb9436384e59da86da703f1e95e15518733 - languageName: node - linkType: hard - "@swim-io/token-projects@workspace:^, @swim-io/token-projects@workspace:packages/token-projects": version: 0.0.0-use.local resolution: "@swim-io/token-projects@workspace:packages/token-projects" @@ -7966,18 +7860,18 @@ __metadata: "@storybook/manager-webpack4": ^6.5.10 "@storybook/node-logger": ^6.5.10 "@storybook/react": ^6.5.10 - "@swim-io/aptos": ^0.40.0 - "@swim-io/core": ^0.40.0 + "@swim-io/aptos": "workspace:^" + "@swim-io/core": "workspace:^" "@swim-io/eslint-config": "workspace:^" - "@swim-io/evm": ^0.40.0 - "@swim-io/evm-contracts": ^0.40.0 - "@swim-io/pool-math": ^0.40.0 - "@swim-io/solana": ^0.40.0 - "@swim-io/solana-contracts": ^0.40.0 - "@swim-io/token-projects": ^0.40.0 + "@swim-io/evm": "workspace:^" + "@swim-io/evm-contracts": "workspace:^" + "@swim-io/pool-math": "workspace:^" + "@swim-io/solana": "workspace:^" + "@swim-io/solana-contracts": "workspace:^" + "@swim-io/token-projects": "workspace:^" "@swim-io/tsconfig": "workspace:^" - "@swim-io/utils": ^0.40.0 - "@swim-io/wormhole": ^0.40.0 + "@swim-io/utils": "workspace:^" + "@swim-io/wormhole": "workspace:^" "@testing-library/jest-dom": ^5.16.4 "@testing-library/react": ^12.1.5 "@testing-library/react-hooks": ^7.0.2 @@ -7991,6 +7885,7 @@ __metadata: "@typescript-eslint/parser": ^5.38.1 bn.js: ^5.2.1 classnames: ^2.3.1 + csv-parse: ^5.3.1 decimal.js: ^10.3.1 dexie: ^3.2.2 escape-string-regexp: ^5.0.0 @@ -8025,6 +7920,7 @@ __metadata: react-router-dom: ^6.3.0 react-scripts: 4.0.3 storybook-preset-craco: ^0.0.6 + ts-node: ^10.9.1 typescript: ~4.8.4 use-deep-compare: ^1.1.0 wasm-loader: ^1.3.0 @@ -8050,15 +7946,6 @@ __metadata: languageName: node linkType: hard -"@swim-io/utils@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/utils@npm:0.40.0" - peerDependencies: - decimal.js: ^10.3.1 - checksum: 0a9c84c556c57015aa118ddb4b72a00e01ac837c1c7870e475897b85a0cfdc1583f9e90b6615a804a90e8185fa80bc05e06e8f15275dda0dcd8475bbbb219bb3 - languageName: node - linkType: hard - "@swim-io/utils@workspace:^, @swim-io/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@swim-io/utils@workspace:packages/utils" @@ -8086,24 +7973,7 @@ __metadata: languageName: unknown linkType: soft -"@swim-io/wormhole@npm:^0.40.0": - version: 0.40.0 - resolution: "@swim-io/wormhole@npm:0.40.0" - dependencies: - "@swim-io/core": ^0.40.0 - "@swim-io/solana": ^0.40.0 - "@swim-io/utils": ^0.40.0 - grpc-web: ^1.3.1 - peerDependencies: - "@certusone/wormhole-sdk": ^0.6.2 - "@solana/spl-token": ^0.3.4 - "@solana/web3.js": ^1.62.0 - ethers: ^5.6.9 - checksum: 12af304e4184bb1db601df1bc8a3c40f38f76ef64d18de5bfcc783b55680adc3090fb7beb08bcee15c9a22313a724fb6418713eaa628f0fad1be449865455f9e - languageName: node - linkType: hard - -"@swim-io/wormhole@workspace:packages/wormhole": +"@swim-io/wormhole@workspace:^, @swim-io/wormhole@workspace:packages/wormhole": version: 0.0.0-use.local resolution: "@swim-io/wormhole@workspace:packages/wormhole" dependencies: @@ -14515,6 +14385,13 @@ __metadata: languageName: node linkType: hard +"csv-parse@npm:^5.3.1": + version: 5.3.1 + resolution: "csv-parse@npm:5.3.1" + checksum: 18acfe5295d13632ab4bcebf285b9cb4e1450d8843946882b42f313d8ae3b606e508594d29bb17a1ca4864ff7da7d1b8ad060caa0495266c433bace1c663b745 + languageName: node + linkType: hard + "csv-stringify@npm:^6.2.0": version: 6.2.0 resolution: "csv-stringify@npm:6.2.0"