From bf47ab40b661c53a9238fff9c7240e63eb5cb3c1 Mon Sep 17 00:00:00 2001 From: Thor <109517311+swimthor@users.noreply.github.com> Date: Wed, 19 Oct 2022 20:29:13 +0100 Subject: [PATCH 01/36] Revert "perf(ui): use websocket for fetching user solana balance (#423)" This reverts commit 7d68589c95ef64c4dc2894a51b1ade697dd828fb. --- .../hooks/solana/useSolBalanceQuery.test.ts | 6 -- .../ui/src/hooks/solana/useSolBalanceQuery.ts | 59 ++----------------- 2 files changed, 6 insertions(+), 59 deletions(-) diff --git a/apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts b/apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts index bcda26b1f..bbf1fefe1 100644 --- a/apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts +++ b/apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts @@ -35,8 +35,6 @@ describe("useSolBalanceQuery", () => { connection: { // eslint-disable-next-line @typescript-eslint/require-await getBalance: async () => 999, - onAccountChange: jest.fn(), - removeAccountChangeListener: jest.fn(async () => {}), } as Partial as unknown as CustomConnection, }); const { result, waitFor } = renderHookWithAppContext(() => @@ -54,8 +52,6 @@ describe("useSolBalanceQuery", () => { connection: { // eslint-disable-next-line @typescript-eslint/require-await getBalance: async () => 123 * LAMPORTS_PER_SOL, - onAccountChange: jest.fn(), - removeAccountChangeListener: jest.fn(async () => {}), } as Partial as unknown as CustomConnection, }); const { result, waitFor } = renderHookWithAppContext(() => @@ -75,8 +71,6 @@ describe("useSolBalanceQuery", () => { getBalance: async () => { throw new Error("Something went wrong"); }, - onAccountChange: jest.fn(), - removeAccountChangeListener: jest.fn(async () => {}), } as Partial as unknown as CustomConnection, }); const { result, waitFor } = renderHookWithAppContext(() => diff --git a/apps/ui/src/hooks/solana/useSolBalanceQuery.ts b/apps/ui/src/hooks/solana/useSolBalanceQuery.ts index 4a274c501..8d4dcd6a8 100644 --- a/apps/ui/src/hooks/solana/useSolBalanceQuery.ts +++ b/apps/ui/src/hooks/solana/useSolBalanceQuery.ts @@ -1,66 +1,22 @@ import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; import Decimal from "decimal.js"; -import { useEffect, useMemo } from "react"; import type { UseQueryOptions, UseQueryResult } from "react-query"; -import { useQuery, useQueryClient } from "react-query"; +import { useQuery } from "react-query"; import { useEnvironment } from "../../core/store"; import { useSolanaClient } from "./useSolanaClient"; import { useSolanaWallet } from "./useSolanaWallet"; -const lamportsToSol = (balance: Decimal.Value): Decimal => { - return new Decimal(balance).dividedBy(LAMPORTS_PER_SOL); -}; - // Returns user's Solana balance in SOL. export const useSolBalanceQuery = ( - options?: Omit, "staleTime">, + options?: UseQueryOptions, ): UseQueryResult => { const { env } = useEnvironment(); const solanaClient = useSolanaClient(); const { address: walletAddress } = useSolanaWallet(); - - const queryClient = useQueryClient(); - const queryKey = useMemo( - () => [env, "solBalance", walletAddress], - [env, walletAddress], - ); - - useEffect(() => { - if (!walletAddress || !options?.enabled) { - return; - } - - // Make sure all network requests are ignored after exit, so the state is not mixed, e.g. state between different wallet addresses - let isExited = false; - - const clientSubscriptionId = solanaClient.connection.onAccountChange( - new PublicKey(walletAddress), - (accountInfo) => { - if (isExited) return; - - queryClient.setQueryData(queryKey, lamportsToSol(accountInfo.lamports)); - }, - ); - return () => { - isExited = true; - - solanaClient.connection - .removeAccountChangeListener(clientSubscriptionId) - .catch(console.error); - }; - }, [ - options?.enabled, - queryClient, - queryKey, - // make sure we are depending on `solanaClient.connection` not `solanaClient` because the reference of `solanaClient` won't change when we rotate the connection - solanaClient.connection, - walletAddress, - ]); - return useQuery( - queryKey, + [env, "solBalance", walletAddress], async () => { if (!walletAddress) { return new Decimal(0); @@ -69,15 +25,12 @@ export const useSolBalanceQuery = ( const balance = await solanaClient.connection.getBalance( new PublicKey(walletAddress), ); - return lamportsToSol(balance); + // Convert lamports to SOL. + return new Decimal(balance).dividedBy(LAMPORTS_PER_SOL); } catch { return new Decimal(0); } }, - { - ...options, - // rely on websocket to update outdated data - staleTime: Infinity, - }, + options, ); }; From 0e51aa6678b14c5f1e0fe1699205c371f3e329aa Mon Sep 17 00:00:00 2001 From: Thor <109517311+swimthor@users.noreply.github.com> Date: Wed, 12 Oct 2022 19:32:40 +0100 Subject: [PATCH 02/36] fix(solana): wrong address in responses of SolanaClient.getMultipleTokenAccounts --- packages/solana/src/client.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/solana/src/client.ts b/packages/solana/src/client.ts index 8337acf84..6a8f53124 100644 --- a/packages/solana/src/client.ts +++ b/packages/solana/src/client.ts @@ -415,13 +415,11 @@ export class SolanaClient extends Client< ), ), ); - return results.flatMap((accounts, i) => - accounts.map((account) => - account === null - ? null - : deserializeTokenAccount(new PublicKey(keys[i]), account.data), - ), - ); + return results.flat().map((account, i) => { + return account === null + ? null + : deserializeTokenAccount(new PublicKey(keys[i]), account.data); + }); } private incrementRpcProvider() { From 08bad24578a01b4dba0f90b943acbb56daceab7d Mon Sep 17 00:00:00 2001 From: Thor <109517311+swimthor@users.noreply.github.com> Date: Fri, 14 Oct 2022 20:34:19 +0100 Subject: [PATCH 03/36] refactor(ui): rename solana react query hook/key names --- apps/ui/src/components/AddForm.tsx | 4 +-- apps/ui/src/components/RecentInteractions.tsx | 5 ++-- apps/ui/src/components/RemoveForm.tsx | 4 +-- .../src/components/SwapForm/SwapForm.test.tsx | 26 ++++++++++--------- apps/ui/src/components/SwapForm/SwapForm.tsx | 4 +-- .../crossEcosystem/useMultipleUserBalances.ts | 4 +-- .../hooks/crossEcosystem/useUserBalances.ts | 4 +-- .../crossEcosystem/useUserNativeBalances.ts | 4 +-- .../interaction/useAddInteractionMutation.ts | 5 ++-- .../useCreateInteractionState.test.ts | 16 +++++++----- .../interaction/useCreateInteractionState.ts | 4 +-- .../useCreateInteractionStateV2.test.ts | 12 ++++++--- .../useCreateInteractionStateV2.ts | 4 +-- .../useFromSolanaTransferMutation.ts | 4 +-- .../usePrepareSplTokenAccountMutation.ts | 8 +++--- .../useReloadInteractionStateMutation.test.ts | 10 ++++--- .../useReloadInteractionStateMutation.ts | 4 +-- .../useRemoveInteractionMutation.ts | 5 ++-- ...ingleChainSolanaSwapInteractionMutation.ts | 5 ++-- .../useSolanaPoolOperationsMutation.ts | 4 +-- .../useToSolanaTransferMutation.test.ts | 10 ++++--- .../useToSolanaTransferMutation.ts | 4 +-- apps/ui/src/hooks/solana/index.ts | 8 +++--- .../useCreateSplTokenAccountsMutation.ts | 11 +++++--- ...st.ts => useSolanaGasBalanceQuery.test.ts} | 10 +++---- ...ceQuery.ts => useSolanaGasBalanceQuery.ts} | 5 ++-- ...ery.ts => useSolanaTokenAccountQueries.ts} | 24 +++-------------- ....ts => useUserSolanaTokenAccountsQuery.ts} | 8 +++--- ...alance.ts => useUserSolanaTokenBalance.ts} | 6 ++--- apps/ui/src/hooks/swim/usePoolBalances.ts | 4 +-- apps/ui/src/hooks/swim/usePools.ts | 8 +++--- .../solana/findOrCreateSplTokenAccount.ts | 6 ++++- 32 files changed, 125 insertions(+), 115 deletions(-) rename apps/ui/src/hooks/solana/{useSolBalanceQuery.test.ts => useSolanaGasBalanceQuery.test.ts} (92%) rename apps/ui/src/hooks/solana/{useSolBalanceQuery.ts => useSolanaGasBalanceQuery.ts} (92%) rename apps/ui/src/hooks/solana/{useSolanaLiquidityQuery.ts => useSolanaTokenAccountQueries.ts} (57%) rename apps/ui/src/hooks/solana/{useSplTokenAccountsQuery.ts => useUserSolanaTokenAccountsQuery.ts} (86%) rename apps/ui/src/hooks/solana/{useSplUserBalance.ts => useUserSolanaTokenBalance.ts} (83%) diff --git a/apps/ui/src/components/AddForm.tsx b/apps/ui/src/components/AddForm.tsx index dc530bf98..5d1287552 100644 --- a/apps/ui/src/components/AddForm.tsx +++ b/apps/ui/src/components/AddForm.tsx @@ -37,9 +37,9 @@ import { useMultipleUserBalances, usePool, usePoolMath, - useSplTokenAccountsQuery, useUserBalanceAmount, useUserNativeBalances, + useUserSolanaTokenAccountsQuery, useWallets, } from "../hooks"; import { @@ -206,7 +206,7 @@ export const AddForm = ({ poolTokens, poolSpec.isLegacyPool ? undefined : poolSpec.ecosystem, ); - const { data: splTokenAccounts = null } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = null } = useUserSolanaTokenAccountsQuery(); const startNewInteraction = useStartNewInteraction(() => { setFormInputAmounts(poolTokens.map(() => "0")); }); diff --git a/apps/ui/src/components/RecentInteractions.tsx b/apps/ui/src/components/RecentInteractions.tsx index 93557a68d..cb5d2c6e3 100644 --- a/apps/ui/src/components/RecentInteractions.tsx +++ b/apps/ui/src/components/RecentInteractions.tsx @@ -9,7 +9,7 @@ import { Fragment, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useEnvironment, useInteractionState } from "../core/store"; -import { useSplTokenAccountsQuery, useWallets } from "../hooks"; +import { useUserSolanaTokenAccountsQuery, useWallets } from "../hooks"; import { isEveryAddressConnected } from "../models"; import type { InteractionType } from "../models"; @@ -34,7 +34,8 @@ export const RecentInteractions = ({ loadInteractionStatesFromIdb(env).catch(console.error); }, [env, loadInteractionStatesFromIdb]); - const { isSuccess: didLoadSplTokenAccounts } = useSplTokenAccountsQuery(); + const { isSuccess: didLoadSplTokenAccounts } = + useUserSolanaTokenAccountsQuery(); const wallets = useWallets(); // Don’t display current interaction const { recentInteractionId, interactionStates } = useInteractionState(); diff --git a/apps/ui/src/components/RemoveForm.tsx b/apps/ui/src/components/RemoveForm.tsx index ae4962d20..0a4ac7775 100644 --- a/apps/ui/src/components/RemoveForm.tsx +++ b/apps/ui/src/components/RemoveForm.tsx @@ -40,9 +40,9 @@ import { usePoolMath, useRemoveFeesEstimationQuery, useRemoveFeesEstimationQueryV2, - useSplTokenAccountsQuery, useUserLpBalances, useUserNativeBalances, + useUserSolanaTokenAccountsQuery, useWallets, } from "../hooks"; import { @@ -93,7 +93,7 @@ export const RemoveForm = ({ state: poolState, } = usePool(poolSpec.id); const poolMath = usePoolMath(poolSpec.id); - const { data: splTokenAccounts = null } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = null } = useUserSolanaTokenAccountsQuery(); const startNewInteractionV1 = useStartNewInteraction(() => { if (method === RemoveMethod.ExactOutput) { setFormOutputAmounts(formOutputAmounts.map(() => "0")); diff --git a/apps/ui/src/components/SwapForm/SwapForm.test.tsx b/apps/ui/src/components/SwapForm/SwapForm.test.tsx index 586d1d72c..773e865bf 100644 --- a/apps/ui/src/components/SwapForm/SwapForm.test.tsx +++ b/apps/ui/src/components/SwapForm/SwapForm.test.tsx @@ -14,12 +14,12 @@ import { useErc20BalanceQuery, useGetSwapFormErrors, useSolanaClient, - useSolanaLiquidityQueries, - useSplTokenAccountsQuery, - useSplUserBalance, + useSolanaTokenAccountQueries, useStartNewInteraction, useSwapFeesEstimationQuery, useUserNativeBalances, + useUserSolanaTokenAccountsQuery, + useUserSolanaTokenBalance, } from "../../hooks"; import { mockOf, renderWithAppContext } from "../../testUtils"; @@ -37,9 +37,9 @@ jest.mock( jest.mock("../../hooks/solana", () => ({ ...jest.requireActual("../../hooks/solana"), - useSplTokenAccountsQuery: jest.fn(), - useSplUserBalance: jest.fn(), - useSolanaLiquidityQueries: jest.fn(), + useUserSolanaTokenAccountsQuery: jest.fn(), + useUserSolanaTokenBalance: jest.fn(), + useSolanaTokenAccountQueries: jest.fn(), })); jest.mock("../../hooks/solana/useSolanaClient", () => ({ @@ -66,13 +66,15 @@ jest.mock("../../hooks", () => ({ const useGetSwapFormErrorsMock = mockOf(useGetSwapFormErrors); const useSolanaClientMock = mockOf(useSolanaClient); -const useSplTokenAccountsQueryMock = mockOf(useSplTokenAccountsQuery); +const useUserSolanaTokenAccountsQueryMock = mockOf( + useUserSolanaTokenAccountsQuery, +); const useStartNewInteractionMock = mockOf(useStartNewInteraction); const useSwapFeesEstimationQueryMock = mockOf(useSwapFeesEstimationQuery); const useErc20BalanceQueryMock = mockOf(useErc20BalanceQuery); const useUserNativeBalancesMock = mockOf(useUserNativeBalances); -const useSplUserBalanceMock = mockOf(useSplUserBalance); -const useSolanaLiquidityQueriesMock = mockOf(useSolanaLiquidityQueries); +const useUserSolanaTokenBalanceMock = mockOf(useUserSolanaTokenBalance); +const useSolanaTokenAccountQueriesMock = mockOf(useSolanaTokenAccountQueries); const findFromTokenButton = () => screen.queryAllByRole("button")[0]; const findToTokenButton = () => screen.queryAllByRole("button")[3]; @@ -86,7 +88,7 @@ describe("SwapForm", () => { }, } as Partial as unknown as CustomConnection, }); - useSplTokenAccountsQueryMock.mockReturnValue({ + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ data: [], }); @@ -107,8 +109,8 @@ describe("SwapForm", () => { useSwapFeesEstimationQueryMock.mockReturnValue(null); useGetSwapFormErrorsMock.mockReturnValue(() => []); useErc20BalanceQueryMock.mockReturnValue({ data: zero }); - useSplUserBalanceMock.mockReturnValue(zero); - useSolanaLiquidityQueriesMock.mockReturnValue([ + useUserSolanaTokenBalanceMock.mockReturnValue(zero); + useSolanaTokenAccountQueriesMock.mockReturnValue([ { data: [] }, ] as unknown as readonly UseQueryResult[]); }); diff --git a/apps/ui/src/components/SwapForm/SwapForm.tsx b/apps/ui/src/components/SwapForm/SwapForm.tsx index 1c79c92d8..e71d75a38 100644 --- a/apps/ui/src/components/SwapForm/SwapForm.tsx +++ b/apps/ui/src/components/SwapForm/SwapForm.tsx @@ -24,12 +24,12 @@ import { useIsLargeSwap, usePoolMaths, usePools, - useSplTokenAccountsQuery, useSwapFeesEstimationQuery, useSwapOutputAmountEstimate, useSwapTokensContext, useUserBalanceAmount, useUserNativeBalances, + useUserSolanaTokenAccountsQuery, } from "../../hooks"; import { useHasActiveInteraction, @@ -59,7 +59,7 @@ export const SwapForm = ({ maxSlippageFraction }: Props): ReactElement => { const { t } = useTranslation(); const config = useEnvironment(selectConfig, shallow); const { notify } = useNotification(); - const { data: splTokenAccounts = null } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = null } = useUserSolanaTokenAccountsQuery(); const startNewInteraction = useStartNewInteraction(() => { setFormInputAmount(""); }); diff --git a/apps/ui/src/hooks/crossEcosystem/useMultipleUserBalances.ts b/apps/ui/src/hooks/crossEcosystem/useMultipleUserBalances.ts index 8a0a229a0..8748a494f 100644 --- a/apps/ui/src/hooks/crossEcosystem/useMultipleUserBalances.ts +++ b/apps/ui/src/hooks/crossEcosystem/useMultipleUserBalances.ts @@ -11,7 +11,7 @@ import { getTokenDetailsForEcosystem } from "../../config"; import { Amount } from "../../models"; import { useAptosTokenBalancesQuery } from "../aptos"; import { useErc20BalancesQuery } from "../evm"; -import { useSolanaWallet, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaWallet, useUserSolanaTokenAccountsQuery } from "../solana"; const getTokenDetailsByEcosystem = ( tokenConfigs: readonly TokenConfig[], @@ -134,7 +134,7 @@ export const useMultipleUserBalances = ( acala, } = getTokenDetailsByEcosystem(tokenConfigs); const { address: solanaWalletAddress } = useSolanaWallet(); - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const solanaTokenAccounts = solana.map((tokenDetails) => solanaWalletAddress !== null ? findTokenAccountForMint( diff --git a/apps/ui/src/hooks/crossEcosystem/useUserBalances.ts b/apps/ui/src/hooks/crossEcosystem/useUserBalances.ts index 332ed6ce2..1e65bb0df 100644 --- a/apps/ui/src/hooks/crossEcosystem/useUserBalances.ts +++ b/apps/ui/src/hooks/crossEcosystem/useUserBalances.ts @@ -8,7 +8,7 @@ import { getTokenDetailsForEcosystem } from "../../config"; import { Amount } from "../../models"; import { useAptosTokenBalanceQuery } from "../aptos"; import { useErc20BalanceQuery } from "../evm"; -import { useSplUserBalance } from "../solana"; +import { useUserSolanaTokenBalance } from "../solana"; const useUserBalance = ( tokenConfig: TokenConfig | null, @@ -20,7 +20,7 @@ const useUserBalance = ( (getTokenDetailsForEcosystem(tokenConfig, APTOS_ECOSYSTEM_ID) ?? null), { enabled: ecosystemId === APTOS_ECOSYSTEM_ID }, ); - const splBalance = useSplUserBalance( + const splBalance = useUserSolanaTokenBalance( tokenConfig && (getTokenDetailsForEcosystem(tokenConfig, SOLANA_ECOSYSTEM_ID) ?? null), { enabled: ecosystemId === SOLANA_ECOSYSTEM_ID }, diff --git a/apps/ui/src/hooks/crossEcosystem/useUserNativeBalances.ts b/apps/ui/src/hooks/crossEcosystem/useUserNativeBalances.ts index c4e9dab8b..a066c0f89 100644 --- a/apps/ui/src/hooks/crossEcosystem/useUserNativeBalances.ts +++ b/apps/ui/src/hooks/crossEcosystem/useUserNativeBalances.ts @@ -8,7 +8,7 @@ import type { EcosystemId } from "../../config"; import { ECOSYSTEM_IDS } from "../../config"; import { useAptosGasBalanceQuery } from "../aptos"; import { useEvmUserNativeBalanceQuery } from "../evm"; -import { useSolBalanceQuery } from "../solana"; +import { useSolanaGasBalanceQuery } from "../solana"; export const useUserNativeBalances = ( /** only fetch the ecosystems specified to reduce network calls */ @@ -17,7 +17,7 @@ export const useUserNativeBalances = ( const { data: aptBalance = new Decimal(0) } = useAptosGasBalanceQuery({ enabled: ecosystemIds.includes(APTOS_ECOSYSTEM_ID), }); - const { data: solBalance = new Decimal(0) } = useSolBalanceQuery({ + const { data: solBalance = new Decimal(0) } = useSolanaGasBalanceQuery({ enabled: ecosystemIds.includes(SOLANA_ECOSYSTEM_ID), }); const { data: ethBalance = new Decimal(0) } = useEvmUserNativeBalanceQuery( diff --git a/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts b/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts index 1b22e38f4..bdea25d3f 100644 --- a/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts @@ -17,7 +17,7 @@ import { import type { AddInteractionState } from "../../models"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; export const useAddInteractionMutation = () => { const queryClient = useQueryClient(); @@ -26,7 +26,8 @@ export const useAddInteractionMutation = () => { const wallets = useWallets(); const solanaClient = useSolanaClient(); const getEvmClient = useGetEvmClient(); - const { data: existingSplTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: existingSplTokenAccounts = [] } = + useUserSolanaTokenAccountsQuery(); const { updateInteractionState } = useInteractionStateV2(); const tokensByPoolId = getTokensByPool(config); diff --git a/apps/ui/src/hooks/interaction/useCreateInteractionState.test.ts b/apps/ui/src/hooks/interaction/useCreateInteractionState.test.ts index b60504750..79d730544 100644 --- a/apps/ui/src/hooks/interaction/useCreateInteractionState.test.ts +++ b/apps/ui/src/hooks/interaction/useCreateInteractionState.test.ts @@ -17,7 +17,7 @@ import { import { Amount, InteractionType } from "../../models"; import { mockOf, renderHookWithAppContext } from "../../testUtils"; import { useWallets } from "../crossEcosystem"; -import { useSolanaWallet, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaWallet, useUserSolanaTokenAccountsQuery } from "../solana"; import { usePoolMathByPoolIds } from "../swim"; import { useCreateInteractionState } from "./useCreateInteractionState"; @@ -37,7 +37,7 @@ jest.mock("../crossEcosystem", () => ({ jest.mock("../solana", () => ({ ...jest.requireActual("../solana"), useSolanaWallet: jest.fn(), - useSplTokenAccountsQuery: jest.fn(), + useUserSolanaTokenAccountsQuery: jest.fn(), })); jest.mock("../swim", () => ({ @@ -48,7 +48,9 @@ jest.mock("../swim", () => ({ // Make typescript happy with jest const useSolanaWalletMock = mockOf(useSolanaWallet); const usePoolMathByPoolIdsMock = mockOf(usePoolMathByPoolIds); -const useSplTokenAccountsQueryMock = mockOf(useSplTokenAccountsQuery); +const useUserSolanaTokenAccountsQueryMock = mockOf( + useUserSolanaTokenAccountsQuery, +); const useWalletsMock = mockOf(useWallets); describe("useCreateInteractionState", () => { @@ -64,7 +66,9 @@ describe("useCreateInteractionState", () => { address: "6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J", }); usePoolMathByPoolIdsMock.mockReturnValue(MOCK_POOL_MATHS_BY_ID); - useSplTokenAccountsQueryMock.mockReturnValue({ data: MOCK_TOKEN_ACCOUNTS }); + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ + data: MOCK_TOKEN_ACCOUNTS, + }); useWalletsMock.mockReturnValue(MOCK_WALLETS); }); @@ -105,7 +109,7 @@ describe("useCreateInteractionState", () => { }); it("create state from SOLANA USDC to ETHEREUM USDC", () => { - useSplTokenAccountsQueryMock.mockReturnValue({ data: [] }); + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ data: [] }); const { result } = renderHookWithAppContext(() => useCreateInteractionState(), ); @@ -142,7 +146,7 @@ describe("useCreateInteractionState", () => { }); it("create state from BNB USDT to ETHEREUM USDC", () => { - useSplTokenAccountsQueryMock.mockReturnValue({ data: [] }); + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ data: [] }); const { result } = renderHookWithAppContext(() => useCreateInteractionState(), ); diff --git a/apps/ui/src/hooks/interaction/useCreateInteractionState.ts b/apps/ui/src/hooks/interaction/useCreateInteractionState.ts index 925ebc277..d12520ed0 100644 --- a/apps/ui/src/hooks/interaction/useCreateInteractionState.ts +++ b/apps/ui/src/hooks/interaction/useCreateInteractionState.ts @@ -30,7 +30,7 @@ import { getTokensByPool, } from "../../models"; import { useWallets } from "../crossEcosystem"; -import { useSolanaWallet, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaWallet, useUserSolanaTokenAccountsQuery } from "../solana"; import { usePoolMathByPoolIds } from "../swim"; export const createRequiredSplTokenAccounts = ( @@ -203,7 +203,7 @@ export const useCreateInteractionState = () => { const wallets = useWallets(); const { env } = useEnvironment(); const tokensByPoolId = getTokensByPool(config); - const { data: tokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: tokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const { address: walletAddress } = useSolanaWallet(); const poolMathsByPoolId = usePoolMathByPoolIds(); diff --git a/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.test.ts b/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.test.ts index dc0827312..c78400acf 100644 --- a/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.test.ts +++ b/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.test.ts @@ -10,7 +10,7 @@ import { MOCK_TOKEN_ACCOUNTS, MOCK_WALLETS } from "../../fixtures"; import { Amount, InteractionType, generateId } from "../../models"; import { mockOf, renderHookWithAppContext } from "../../testUtils"; import { useWallets } from "../crossEcosystem"; -import { useSplTokenAccountsQuery } from "../solana"; +import { useUserSolanaTokenAccountsQuery } from "../solana"; import { useCreateInteractionStateV2 } from "./useCreateInteractionStateV2"; @@ -35,12 +35,14 @@ jest.mock("../crossEcosystem", () => ({ jest.mock("../solana", () => ({ ...jest.requireActual("../solana"), - useSplTokenAccountsQuery: jest.fn(), + useUserSolanaTokenAccountsQuery: jest.fn(), })); // Make typescript happy with jest const generateIdMock = mockOf(generateId); -const useSplTokenAccountsQueryMock = mockOf(useSplTokenAccountsQuery); +const useUserSolanaTokenAccountsQueryMock = mockOf( + useUserSolanaTokenAccountsQuery, +); const useWalletsMock = mockOf(useWallets); describe("useCreateInteractionStateV2", () => { @@ -51,7 +53,9 @@ describe("useCreateInteractionStateV2", () => { envStore.current.setEnv(Env.Testnet); }); generateIdMock.mockReturnValue("11111111111111111111111111111111"); - useSplTokenAccountsQueryMock.mockReturnValue({ data: MOCK_TOKEN_ACCOUNTS }); + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ + data: MOCK_TOKEN_ACCOUNTS, + }); useWalletsMock.mockReturnValue(MOCK_WALLETS); jest.spyOn(Date, "now").mockImplementation(() => 1657544558283); }); diff --git a/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.ts b/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.ts index 6243cf789..09ad899a4 100644 --- a/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.ts +++ b/apps/ui/src/hooks/interaction/useCreateInteractionStateV2.ts @@ -29,7 +29,7 @@ import { getTokensByPool, } from "../../models"; import { useWallets } from "../crossEcosystem"; -import { useSplTokenAccountsQuery } from "../solana"; +import { useUserSolanaTokenAccountsQuery } from "../solana"; const calculateRequiredSplTokenAccounts = ( interaction: SwapInteractionV2, @@ -224,7 +224,7 @@ export const useCreateInteractionStateV2 = () => { const config = useEnvironment(selectConfig, shallow); const wallets = useWallets(); const { env } = useEnvironment(); - const { data: tokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: tokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const solanaWalletAddress = wallets[SOLANA_ECOSYSTEM_ID].address; const tokensByPoolId = getTokensByPool(config); diff --git a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts index 60c24960c..7a01558e0 100644 --- a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts @@ -32,7 +32,7 @@ import { } from "../../models"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; const getTransferredAmountsByTokenId = async ( interactionState: InteractionState, @@ -60,7 +60,7 @@ const getTransferredAmountsByTokenId = async ( }; export const useFromSolanaTransferMutation = () => { - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const config = useEnvironment(selectConfig); const { chains, wormhole } = config; const getEvmClient = useGetEvmClient(); diff --git a/apps/ui/src/hooks/interaction/usePrepareSplTokenAccountMutation.ts b/apps/ui/src/hooks/interaction/usePrepareSplTokenAccountMutation.ts index cd4d6a43a..ac1975e28 100644 --- a/apps/ui/src/hooks/interaction/usePrepareSplTokenAccountMutation.ts +++ b/apps/ui/src/hooks/interaction/usePrepareSplTokenAccountMutation.ts @@ -4,10 +4,10 @@ import { useMutation, useQueryClient } from "react-query"; import { selectGetInteractionState } from "../../core/selectors"; import { useInteractionState } from "../../core/store"; import { - getSplTokenAccountsQueryKey, + getUserSolanaTokenAccountsQueryKey, useSolanaClient, useSolanaWallet, - useSplTokenAccountsQuery, + useUserSolanaTokenAccountsQuery, } from "../solana"; export const usePrepareSplTokenAccountMutation = () => { @@ -18,7 +18,7 @@ export const usePrepareSplTokenAccountMutation = () => { ); const getInteractionState = useInteractionState(selectGetInteractionState); const queryClient = useQueryClient(); - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); return useMutation(async (interactionId: string) => { if (wallet === null) { @@ -56,7 +56,7 @@ export const usePrepareSplTokenAccountMutation = () => { ); if (missingAccountMints.length > 0) { - const splTokenAccountsQueryKey = getSplTokenAccountsQueryKey( + const splTokenAccountsQueryKey = getUserSolanaTokenAccountsQueryKey( interaction.env, solanaAddress, ); diff --git a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts index 0e086ae14..a9a2b7ed6 100644 --- a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts +++ b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts @@ -17,7 +17,7 @@ import { } from "../../models"; import { mockOf, renderHookWithAppContext } from "../../testUtils"; import { useEvmWallet } from "../evm"; -import { useSolanaWallet, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaWallet, useUserSolanaTokenAccountsQuery } from "../solana"; import { useReloadInteractionStateMutation } from "./useReloadInteractionStateMutation"; @@ -33,10 +33,12 @@ const useEvmWalletMock = mockOf(useEvmWallet); jest.mock("../solana", () => ({ ...jest.requireActual("../solana"), useSolanaWallet: jest.fn(), - useSplTokenAccountsQuery: jest.fn(), + useUserSolanaTokenAccountsQuery: jest.fn(), })); const useSolanaWalletMock = mockOf(useSolanaWallet); -const useSplTokenAccountsQueryMock = mockOf(useSplTokenAccountsQuery); +const useUserSolanaTokenAccountsQueryMock = mockOf( + useUserSolanaTokenAccountsQuery, +); jest.mock("../../models", () => ({ ...jest.requireActual("../../models"), @@ -68,7 +70,7 @@ describe("useReloadInteractionStateMutation", () => { }); it("should reload recent tx and recover interaction state", async () => { - useSplTokenAccountsQueryMock.mockReturnValue({ + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ data: [ { mint: new PublicKey("7Lf95y8NuCU5RRC95oUtbBtckPAtbr9ubTgrCiyZ1kEf"), diff --git a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts index 900ec91cc..7715bba1c 100644 --- a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts +++ b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts @@ -25,13 +25,13 @@ import { useEvmWallet, useGetEvmClient } from "../evm"; import { useSolanaClient, useSolanaWallet, - useSplTokenAccountsQuery, + useUserSolanaTokenAccountsQuery, } from "../solana"; export const useReloadInteractionStateMutation = () => { const queryClient = useQueryClient(); const getEvmClient = useGetEvmClient(); - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const solanaClient = useSolanaClient(); const { address: solanaAddress } = useSolanaWallet(); const { address: evmAddress } = useEvmWallet(); diff --git a/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts b/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts index 1cd073fe3..909f6371b 100644 --- a/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts @@ -25,7 +25,7 @@ import { } from "../../models"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; const getPopulatedTxForEvmRemoveInteraction = ( interaction: @@ -90,7 +90,8 @@ export const useRemoveInteractionMutation = () => { const wallets = useWallets(); const solanaClient = useSolanaClient(); const getEvmClient = useGetEvmClient(); - const { data: existingSplTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: existingSplTokenAccounts = [] } = + useUserSolanaTokenAccountsQuery(); const { updateInteractionState } = useInteractionStateV2(); const tokensByPoolId = getTokensByPool(config); diff --git a/apps/ui/src/hooks/interaction/useSingleChainSolanaSwapInteractionMutation.ts b/apps/ui/src/hooks/interaction/useSingleChainSolanaSwapInteractionMutation.ts index 3b0b6cf8d..db7d60f72 100644 --- a/apps/ui/src/hooks/interaction/useSingleChainSolanaSwapInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useSingleChainSolanaSwapInteractionMutation.ts @@ -23,7 +23,7 @@ import { getTokensByPool, } from "../../models"; import { useWallets } from "../crossEcosystem"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; import { useSwimUsd } from "../swim"; const createOperationSpec = ( @@ -101,7 +101,8 @@ export const useSingleChainSolanaSwapInteractionMutation = () => { const config = useEnvironment(selectConfig, shallow); const wallets = useWallets(); const solanaClient = useSolanaClient(); - const { data: existingSplTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: existingSplTokenAccounts = [] } = + useUserSolanaTokenAccountsQuery(); const { updateInteractionState } = useInteractionStateV2(); const swimUsd = useSwimUsd(); const tokensByPoolId = getTokensByPool(config); diff --git a/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts b/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts index bb96b57be..c848f5d2f 100644 --- a/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts +++ b/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts @@ -13,14 +13,14 @@ import { import { useSolanaClient, useSolanaWallet, - useSplTokenAccountsQuery, + useUserSolanaTokenAccountsQuery, } from "../solana"; export const useSolanaPoolOperationsMutation = () => { const { env } = useEnvironment(); const config = useEnvironment(selectConfig, shallow); const { pools } = config; - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const solanaClient = useSolanaClient(); const { wallet, address: solanaWalletAddress } = useSolanaWallet(); const tokensByPoolId = getTokensByPool(config); diff --git a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts index 37d090567..8a0fa302e 100644 --- a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts +++ b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts @@ -14,7 +14,7 @@ import type { Wallets } from "../../models"; import { mockOf, renderHookWithAppContext } from "../../testUtils"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; import { useToSolanaTransferMutation } from "./useToSolanaTransferMutation"; @@ -42,10 +42,12 @@ const getSignedVaaWithRetryMock = mockOf(getSignedVaaWithRetry); jest.mock("../solana", () => ({ ...jest.requireActual("../solana"), useSolanaClient: jest.fn(), - useSplTokenAccountsQuery: jest.fn(), + useUserSolanaTokenAccountsQuery: jest.fn(), })); const useSolanaClientMock = mockOf(useSolanaClient); -const useSplTokenAccountsQueryMock = mockOf(useSplTokenAccountsQuery); +const useUserSolanaTokenAccountsQueryMock = mockOf( + useUserSolanaTokenAccountsQuery, +); describe("useToSolanaTransferMutation", () => { beforeEach(() => { @@ -60,7 +62,7 @@ describe("useToSolanaTransferMutation", () => { }); it("should handle the transfer and patch interactionState with txIds", async () => { - useSplTokenAccountsQueryMock.mockReturnValue({ + useUserSolanaTokenAccountsQueryMock.mockReturnValue({ data: [ { mint: new PublicKey("9idXDPGb5jfwaf5fxjiMacgUcwpy3ZHfdgqSjAV5XLDr"), diff --git a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts index a9231e96c..7c64da9e8 100644 --- a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts @@ -26,10 +26,10 @@ import { } from "../../models"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; export const useToSolanaTransferMutation = () => { - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); const { chains, wormhole } = useEnvironment(selectConfig, shallow); const getEvmClient = useGetEvmClient(); const solanaClient = useSolanaClient(); diff --git a/apps/ui/src/hooks/solana/index.ts b/apps/ui/src/hooks/solana/index.ts index 53241b3b8..5023809c8 100644 --- a/apps/ui/src/hooks/solana/index.ts +++ b/apps/ui/src/hooks/solana/index.ts @@ -1,8 +1,8 @@ export * from "./useAnchorProvider"; export * from "./useCreateSplTokenAccountsMutation"; -export * from "./useSolanaLiquidityQuery"; export * from "./useSolanaClient"; +export * from "./useSolanaGasBalanceQuery"; +export * from "./useSolanaTokenAccountQueries"; export * from "./useSolanaWallet"; -export * from "./useSolBalanceQuery"; -export * from "./useSplTokenAccountsQuery"; -export * from "./useSplUserBalance"; +export * from "./useUserSolanaTokenAccountsQuery"; +export * from "./useUserSolanaTokenBalance"; diff --git a/apps/ui/src/hooks/solana/useCreateSplTokenAccountsMutation.ts b/apps/ui/src/hooks/solana/useCreateSplTokenAccountsMutation.ts index e949a1ab2..0f16a4dfb 100644 --- a/apps/ui/src/hooks/solana/useCreateSplTokenAccountsMutation.ts +++ b/apps/ui/src/hooks/solana/useCreateSplTokenAccountsMutation.ts @@ -7,7 +7,10 @@ import { findOrCreateSplTokenAccount } from "../../models"; import { useSolanaClient } from "./useSolanaClient"; import { useSolanaWallet } from "./useSolanaWallet"; -import { useSplTokenAccountsQuery } from "./useSplTokenAccountsQuery"; +import { + getUserSolanaTokenAccountsQueryKey, + useUserSolanaTokenAccountsQuery, +} from "./useUserSolanaTokenAccountsQuery"; export const useCreateSplTokenAccountsMutation = (): UseMutationResult< readonly TokenAccount[], @@ -18,7 +21,7 @@ export const useCreateSplTokenAccountsMutation = (): UseMutationResult< const queryClient = useQueryClient(); const solanaClient = useSolanaClient(); const { wallet, address } = useSolanaWallet(); - const { data: splTokenAccounts = null } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = null } = useUserSolanaTokenAccountsQuery(); return useMutation( async (mints: readonly string[]): Promise => { @@ -42,7 +45,9 @@ export const useCreateSplTokenAccountsMutation = (): UseMutationResult< }), ), ); - await queryClient.invalidateQueries([env, "tokenAccounts", address]); + await queryClient.invalidateQueries( + getUserSolanaTokenAccountsQueryKey(env, address), + ); return tokenAccountData.map((data) => data.tokenAccount); }, ); diff --git a/apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts b/apps/ui/src/hooks/solana/useSolanaGasBalanceQuery.test.ts similarity index 92% rename from apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts rename to apps/ui/src/hooks/solana/useSolanaGasBalanceQuery.test.ts index bbf1fefe1..6355cc97f 100644 --- a/apps/ui/src/hooks/solana/useSolBalanceQuery.test.ts +++ b/apps/ui/src/hooks/solana/useSolanaGasBalanceQuery.test.ts @@ -5,8 +5,8 @@ import { useQueryClient } from "react-query"; import { mockOf, renderHookWithAppContext } from "../../testUtils"; -import { useSolBalanceQuery } from "./useSolBalanceQuery"; import { useSolanaClient } from "./useSolanaClient"; +import { useSolanaGasBalanceQuery } from "./useSolanaGasBalanceQuery"; import { useSolanaWallet } from "./useSolanaWallet"; jest.mock("./useSolanaClient", () => ({ @@ -23,7 +23,7 @@ jest.mock("./useSolanaWallet", () => ({ const useSolanaWalletMock = mockOf(useSolanaWallet); const useSolanaClientMock = mockOf(useSolanaClient); -describe("useSolBalanceQuery", () => { +describe("useSolanaGasBalanceQuery", () => { beforeEach(() => { // Reset queryClient cache, otherwise test might return previous value renderHookWithAppContext(() => useQueryClient().clear()); @@ -38,7 +38,7 @@ describe("useSolBalanceQuery", () => { } as Partial as unknown as CustomConnection, }); const { result, waitFor } = renderHookWithAppContext(() => - useSolBalanceQuery(), + useSolanaGasBalanceQuery(), ); await waitFor(() => result.current.isSuccess); expect(result.current.data).toEqual(new Decimal("0")); @@ -55,7 +55,7 @@ describe("useSolBalanceQuery", () => { } as Partial as unknown as CustomConnection, }); const { result, waitFor } = renderHookWithAppContext(() => - useSolBalanceQuery(), + useSolanaGasBalanceQuery(), ); await waitFor(() => result.current.isSuccess); expect(result.current.data).toEqual(new Decimal("123")); @@ -74,7 +74,7 @@ describe("useSolBalanceQuery", () => { } as Partial as unknown as CustomConnection, }); const { result, waitFor } = renderHookWithAppContext(() => - useSolBalanceQuery(), + useSolanaGasBalanceQuery(), ); await waitFor(() => result.current.isSuccess); expect(result.current.data).toEqual(new Decimal("0")); diff --git a/apps/ui/src/hooks/solana/useSolBalanceQuery.ts b/apps/ui/src/hooks/solana/useSolanaGasBalanceQuery.ts similarity index 92% rename from apps/ui/src/hooks/solana/useSolBalanceQuery.ts rename to apps/ui/src/hooks/solana/useSolanaGasBalanceQuery.ts index 8d4dcd6a8..270676c2b 100644 --- a/apps/ui/src/hooks/solana/useSolBalanceQuery.ts +++ b/apps/ui/src/hooks/solana/useSolanaGasBalanceQuery.ts @@ -9,14 +9,15 @@ import { useSolanaClient } from "./useSolanaClient"; import { useSolanaWallet } from "./useSolanaWallet"; // Returns user's Solana balance in SOL. -export const useSolBalanceQuery = ( +export const useSolanaGasBalanceQuery = ( options?: UseQueryOptions, ): UseQueryResult => { const { env } = useEnvironment(); const solanaClient = useSolanaClient(); const { address: walletAddress } = useSolanaWallet(); + return useQuery( - [env, "solBalance", walletAddress], + [env, "solanaGasBalance", walletAddress], async () => { if (!walletAddress) { return new Decimal(0); diff --git a/apps/ui/src/hooks/solana/useSolanaLiquidityQuery.ts b/apps/ui/src/hooks/solana/useSolanaTokenAccountQueries.ts similarity index 57% rename from apps/ui/src/hooks/solana/useSolanaLiquidityQuery.ts rename to apps/ui/src/hooks/solana/useSolanaTokenAccountQueries.ts index c4d5034f2..5881e62f0 100644 --- a/apps/ui/src/hooks/solana/useSolanaLiquidityQuery.ts +++ b/apps/ui/src/hooks/solana/useSolanaTokenAccountQueries.ts @@ -1,30 +1,12 @@ import type { TokenAccount } from "@swim-io/solana"; import type { UseQueryResult } from "react-query"; -import { useQueries, useQuery } from "react-query"; +import { useQueries } from "react-query"; import { useEnvironment } from "../../core/store"; import { useSolanaClient } from "./useSolanaClient"; -export const useSolanaLiquidityQuery = ( - tokenAccountAddresses: readonly string[], -): UseQueryResult => { - const { env } = useEnvironment(); - const solanaClient = useSolanaClient(); - - return useQuery( - [env, "liquidity", tokenAccountAddresses.join("")], - async () => { - if (tokenAccountAddresses.length === 0) { - return []; - } - - return await solanaClient.getMultipleTokenAccounts(tokenAccountAddresses); - }, - ); -}; - -export const useSolanaLiquidityQueries = ( +export const useSolanaTokenAccountQueries = ( tokenAccountAddresses: readonly (readonly string[])[], ): readonly UseQueryResult[] => { const { env } = useEnvironment(); @@ -32,7 +14,7 @@ export const useSolanaLiquidityQueries = ( return useQueries( tokenAccountAddresses.map((addresses) => ({ - queryKey: [env, "liquidity", addresses.join("")], + queryKey: [env, "solanaTokenAccounts", addresses.join()], queryFn: async (): Promise => { if (addresses.length === 0) { return []; diff --git a/apps/ui/src/hooks/solana/useSplTokenAccountsQuery.ts b/apps/ui/src/hooks/solana/useUserSolanaTokenAccountsQuery.ts similarity index 86% rename from apps/ui/src/hooks/solana/useSplTokenAccountsQuery.ts rename to apps/ui/src/hooks/solana/useUserSolanaTokenAccountsQuery.ts index f32f7f4ed..0cc0c8f77 100644 --- a/apps/ui/src/hooks/solana/useSplTokenAccountsQuery.ts +++ b/apps/ui/src/hooks/solana/useUserSolanaTokenAccountsQuery.ts @@ -11,12 +11,12 @@ import { useEnvironment } from "../../core/store"; import { useSolanaClient } from "./useSolanaClient"; import { useSolanaWallet } from "./useSolanaWallet"; -export const getSplTokenAccountsQueryKey = ( +export const getUserSolanaTokenAccountsQueryKey = ( env: Env, address: string | null, -) => [env, "tokenAccounts", address]; +) => [env, "userSolanaTokenAccounts", address]; -export const useSplTokenAccountsQuery = ( +export const useUserSolanaTokenAccountsQuery = ( owner?: string, options?: UseQueryOptions, ): UseQueryResult => { @@ -25,7 +25,7 @@ export const useSplTokenAccountsQuery = ( const { address: userAddress } = useSolanaWallet(); const address = owner ?? userAddress; - const queryKey = getSplTokenAccountsQueryKey(env, address); + const queryKey = getUserSolanaTokenAccountsQueryKey(env, address); return useQuery( queryKey, async () => { diff --git a/apps/ui/src/hooks/solana/useSplUserBalance.ts b/apps/ui/src/hooks/solana/useUserSolanaTokenBalance.ts similarity index 83% rename from apps/ui/src/hooks/solana/useSplUserBalance.ts rename to apps/ui/src/hooks/solana/useUserSolanaTokenBalance.ts index 90d61139f..2f721dda9 100644 --- a/apps/ui/src/hooks/solana/useSplUserBalance.ts +++ b/apps/ui/src/hooks/solana/useUserSolanaTokenBalance.ts @@ -4,9 +4,9 @@ import { atomicToHuman } from "@swim-io/utils"; import Decimal from "decimal.js"; import { useSolanaWallet } from "./useSolanaWallet"; -import { useSplTokenAccountsQuery } from "./useSplTokenAccountsQuery"; +import { useUserSolanaTokenAccountsQuery } from "./useUserSolanaTokenAccountsQuery"; -export const useSplUserBalance = ( +export const useUserSolanaTokenBalance = ( tokenDetails: TokenDetails | null, { enabled = true, @@ -18,7 +18,7 @@ export const useSplUserBalance = ( } = {}, ): Decimal | null => { const { address: walletAddress } = useSolanaWallet(); - const { data: splTokenAccounts = null } = useSplTokenAccountsQuery( + const { data: splTokenAccounts = null } = useUserSolanaTokenAccountsQuery( undefined, { enabled }, ); diff --git a/apps/ui/src/hooks/swim/usePoolBalances.ts b/apps/ui/src/hooks/swim/usePoolBalances.ts index bcb468d40..c005a50bc 100644 --- a/apps/ui/src/hooks/swim/usePoolBalances.ts +++ b/apps/ui/src/hooks/swim/usePoolBalances.ts @@ -8,14 +8,14 @@ import { getSolanaTokenDetails } from "../../config"; import { selectConfig } from "../../core/selectors"; import { useEnvironment } from "../../core/store"; import { isEvmPoolState, isSolanaPool } from "../../models"; -import { useSolanaLiquidityQueries } from "../solana"; +import { useSolanaTokenAccountQueries } from "../solana"; import { usePoolStateQueries } from "./usePoolStateQueries"; export const usePoolBalances = (poolSpecs: readonly PoolSpec[]) => { const { tokens } = useEnvironment(selectConfig, shallow); const poolStateQueries = usePoolStateQueries(poolSpecs); - const liquidityQueries = useSolanaLiquidityQueries( + const liquidityQueries = useSolanaTokenAccountQueries( poolSpecs.map((poolSpec) => poolSpec.ecosystem === SOLANA_ECOSYSTEM_ID ? [...poolSpec.tokenAccounts.values()] diff --git a/apps/ui/src/hooks/swim/usePools.ts b/apps/ui/src/hooks/swim/usePools.ts index 9d4e97ff9..066416e49 100644 --- a/apps/ui/src/hooks/swim/usePools.ts +++ b/apps/ui/src/hooks/swim/usePools.ts @@ -11,9 +11,9 @@ import { useEnvironment } from "../../core/store"; import type { PoolState } from "../../models"; import { getPoolUsdValue, isEvmPoolState } from "../../models"; import { - useSolanaLiquidityQueries, + useSolanaTokenAccountQueries, useSolanaWallet, - useSplTokenAccountsQuery, + useUserSolanaTokenAccountsQuery, } from "../solana"; import { usePoolLpMints } from "./usePoolLpMint"; @@ -119,13 +119,13 @@ const constructPool = ( export const usePools = (poolIds: readonly string[]): readonly PoolData[] => { const { pools, tokens: allTokens } = useEnvironment(selectConfig, shallow); const { address: walletAddress } = useSolanaWallet(); - const { data: splTokenAccounts = null } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = null } = useUserSolanaTokenAccountsQuery(); const poolSpecs = poolIds.map((poolId) => findOrThrow(pools, (pool) => pool.id === poolId), ); const poolStates = usePoolStateQueries(poolSpecs); const lpMints = usePoolLpMints(poolSpecs); - const liquidityQueries = useSolanaLiquidityQueries( + const liquidityQueries = useSolanaTokenAccountQueries( poolSpecs.map((poolSpec) => poolSpec.ecosystem === SOLANA_ECOSYSTEM_ID ? [...poolSpec.tokenAccounts.values()] diff --git a/apps/ui/src/models/solana/findOrCreateSplTokenAccount.ts b/apps/ui/src/models/solana/findOrCreateSplTokenAccount.ts index 2c6e7b7af..5e38580f6 100644 --- a/apps/ui/src/models/solana/findOrCreateSplTokenAccount.ts +++ b/apps/ui/src/models/solana/findOrCreateSplTokenAccount.ts @@ -47,7 +47,11 @@ export const findOrCreateSplTokenAccount = async ({ ); await solanaClient.confirmTx(createSplTokenAccountTxId); await sleep(1000); // TODO: Find a better condition - await queryClient.invalidateQueries([env, "tokenAccounts", solanaAddress]); + await queryClient.invalidateQueries([ + env, + "userSolanaTokenAccounts", + solanaAddress, + ]); const tokenAccount = await solanaClient.getTokenAccountWithRetry( splTokenMintAddress, solanaAddress, From 20230182c6fdcd57ddcf3a8cd2365b47dc5908bb Mon Sep 17 00:00:00 2001 From: wormat Date: Tue, 18 Oct 2022 17:32:55 +0200 Subject: [PATCH 04/36] feat(core): Standardize Wormhole client methods --- packages/core/src/client.ts | 29 ++++++++++++++++++++--------- packages/core/src/tx.ts | 5 +++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index dd3bfabcb..7e8d35efc 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -14,7 +14,7 @@ export interface WrappedTokenInfo { readonly wrappedAddress: string; } -export interface InitiateWormholeTransferParams { +export interface InitiatePortalTransferParams { readonly atomicAmount: string; readonly interactionId: string; /** Standardized Wormhole format, ie 32 bytes */ @@ -25,16 +25,27 @@ export interface InitiateWormholeTransferParams { readonly wrappedTokenInfo?: WrappedTokenInfo; } -export interface CompleteWormholeTransferParams { +export interface CompletePortalTransferParams { readonly interactionId: string; readonly vaa: Uint8Array; readonly wallet: Wallet; } +export interface TxGeneratorResult< + OriginalTx, + T extends Tx, + TxType extends string, +> { + readonly tx: T; + readonly type: TxType; +} + export abstract class Client< EcosystemId extends string, C extends ChainConfig, - T extends Tx, + OriginalTx, + TxType extends string, + T extends Tx, Wallet, > { public readonly ecosystemId: EcosystemId; @@ -59,11 +70,11 @@ export abstract class Client< owner: string, tokenDetails: readonly TokenDetails[], ): Promise; - public abstract initiateWormholeTransfer( - params: InitiateWormholeTransferParams, - ): Promise; + public abstract generateInitiatePortalTransferTxs( + params: InitiatePortalTransferParams, + ): AsyncGenerator>; - public abstract completeWormholeTransfer( - params: CompleteWormholeTransferParams, - ): Promise; + public abstract generateCompletePortalTransferTxs( + params: CompletePortalTransferParams, + ): AsyncGenerator>; } diff --git a/packages/core/src/tx.ts b/packages/core/src/tx.ts index d94195ca7..350ffa233 100644 --- a/packages/core/src/tx.ts +++ b/packages/core/src/tx.ts @@ -1,8 +1,9 @@ /** Ecosystem-neutral transaction interface */ -export interface Tx { +export interface Tx { readonly id: string; - readonly ecosystemId: string; + readonly ecosystemId: EcosystemId; /** The time in seconds since Unix epoch */ readonly timestamp: number | null; readonly interactionId: string | null; + readonly original: OriginalTx; } From 6bcfd5c13948a3c4eba982b77874eadc21d8c8ec Mon Sep 17 00:00:00 2001 From: wormat Date: Tue, 18 Oct 2022 17:57:06 +0200 Subject: [PATCH 05/36] feat(evm): Match new client wormhole method signature with tx types --- packages/evm/src/client.ts | 123 ++++++++++++++++++----------------- packages/evm/src/protocol.ts | 11 ++-- 2 files changed, 72 insertions(+), 62 deletions(-) diff --git a/packages/evm/src/client.ts b/packages/evm/src/client.ts index 10566f473..1addf126c 100644 --- a/packages/evm/src/client.ts +++ b/packages/evm/src/client.ts @@ -6,9 +6,10 @@ import { } from "@certusone/wormhole-sdk"; import { Client, getTokenDetails } from "@swim-io/core"; import type { - CompleteWormholeTransferParams, - InitiateWormholeTransferParams, + CompletePortalTransferParams, + InitiatePortalTransferParams, TokenDetails, + TxGeneratorResult, } from "@swim-io/core"; import { ERC20__factory } from "@swim-io/evm-contracts"; import { isNotNull } from "@swim-io/utils"; @@ -17,6 +18,7 @@ import type { ethers, providers } from "ethers"; import { utils as ethersUtils } from "ethers"; import type { EvmChainConfig, EvmEcosystemId, EvmTx } from "./protocol"; +import { EvmTxType } from "./protocol"; import { appendHexDataToEvmTx } from "./utils"; import type { EvmWalletAdapter } from "./walletAdapters"; @@ -62,6 +64,8 @@ export interface EvmClientOptions { export class EvmClient extends Client< EvmEcosystemId, EvmChainConfig, + TransactionReceipt, + EvmTxType, EvmTx, EvmWalletAdapter > { @@ -93,18 +97,11 @@ export class EvmClient extends Client< txIdOrResponse: TransactionResponse | string, ): Promise { const response = typeof txIdOrResponse === "string" ? null : txIdOrResponse; - const id = - typeof txIdOrResponse === "string" ? txIdOrResponse : txIdOrResponse.hash; - const receipt = await this.getTxReceipt(txIdOrResponse); - - if (receipt === null) { - throw new Error(`Transaction not found: ${id}`); - } - + const receipt = await this.getTxReceiptOrThrow(txIdOrResponse); return { id: receipt.transactionHash, ecosystemId: this.ecosystemId, - receipt, + original: receipt, timestamp: response?.timestamp ?? null, interactionId: null, }; @@ -154,7 +151,7 @@ export class EvmClient extends Client< ); } - public async initiateWormholeTransfer({ + public async *generateInitiatePortalTransferTxs({ atomicAmount, interactionId, targetAddress, @@ -162,24 +159,31 @@ export class EvmClient extends Client< tokenProjectId, wallet, wrappedTokenInfo, - }: InitiateWormholeTransferParams): Promise<{ - readonly approvalResponses: readonly ethers.providers.TransactionResponse[]; - readonly transferResponse: ethers.providers.TransactionResponse; - }> { + }: InitiatePortalTransferParams): AsyncGenerator< + TxGeneratorResult< + TransactionReceipt, + EvmTx, + EvmTxType.Erc20Approve | EvmTxType.PortalTransferTokens + > + > { const mintAddress = wrappedTokenInfo?.wrappedAddress ?? getTokenDetails(this.chainConfig, tokenProjectId).address; await wallet.switchNetwork(this.chainConfig.chainId); - const approvalResponses = await this.approveTokenAmount({ + const approvalGenerator = this.generateErc20ApproveTxs({ atomicAmount, mintAddress, spenderAddress: this.chainConfig.wormhole.portal, wallet, }); - const transferResponse = await this.transferToken({ + for await (const approvalTxResult of approvalGenerator) { + yield approvalTxResult; + } + + const tx = await this.transferToken({ interactionId, mintAddress, atomicAmount, @@ -187,27 +191,26 @@ export class EvmClient extends Client< targetAddress, wallet, }); - - if (transferResponse === null) { - throw new Error( - `Transaction not found (lock/burn from ${this.chainConfig.name})`, - ); - } - - return { - approvalResponses, - transferResponse, + yield { + tx, + type: EvmTxType.PortalTransferTokens, }; } /** * Adapted from https://github.com/certusone/wormhole/blob/2998031b164051a466bb98c71d89301ed482b4c5/sdk/js/src/token_bridge/redeem.ts#L24-L33 */ - public async completeWormholeTransfer({ + public async *generateCompletePortalTransferTxs({ interactionId, vaa, wallet, - }: CompleteWormholeTransferParams): Promise { + }: CompletePortalTransferParams): AsyncGenerator< + TxGeneratorResult< + TransactionReceipt, + EvmTx, + EvmTxType.PortalCompleteTransfer + > + > { const { signer } = wallet; if (!signer) { throw new Error("No EVM signer"); @@ -218,16 +221,21 @@ export class EvmClient extends Client< ); const populatedTx = await bridge.populateTransaction.completeTransfer(vaa); const txRequest = appendHexDataToEvmTx(interactionId, populatedTx); - return signer.sendTransaction(txRequest); + const completeResponse = await signer.sendTransaction(txRequest); + const tx = await this.getTx(completeResponse); + yield { + tx, + type: EvmTxType.PortalCompleteTransfer, + }; } - public async approveTokenAmount({ + public async *generateErc20ApproveTxs({ atomicAmount, mintAddress, spenderAddress, wallet, - }: ApproveTokenAmountParams): Promise< - readonly ethers.providers.TransactionResponse[] + }: ApproveTokenAmountParams): AsyncGenerator< + TxGeneratorResult > { const { signer } = wallet; if (!signer) { @@ -235,47 +243,40 @@ export class EvmClient extends Client< } await wallet.switchNetwork(this.chainConfig.chainId); - const allowance = await getAllowanceEth( spenderAddress, mintAddress, signer, ); - let approvalResponses: readonly ethers.providers.TransactionResponse[] = []; if (allowance.lt(atomicAmount)) { if (!allowance.isZero()) { // Reset to 0 to avoid a race condition allowing Wormhole to steal funds // See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 // Note this is required by some ERC20 implementations such as USDT // See line 205 here: https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code - const resetApprovalResponse = await this.approveErc20Token({ + const resetTx = await this.approveErc20Token({ atomicAmount: "0", mintAddress, signer, spenderAddress, }); - approvalResponses = resetApprovalResponse - ? [...approvalResponses, resetApprovalResponse] - : approvalResponses; + yield { + tx: resetTx, + type: EvmTxType.Erc20Approve, + }; } - const approvalResponse = await this.approveErc20Token({ + const tx = await this.approveErc20Token({ atomicAmount, mintAddress, signer, spenderAddress, }); - approvalResponses = approvalResponse - ? [...approvalResponses, approvalResponse] - : approvalResponses; - - // Wait for approvalResponse to be mined, otherwise the transfer might fail ("transfer amount exceeds allowance") - if (approvalResponse) { - await this.getTxReceiptOrThrow(approvalResponse); - } + yield { + tx, + type: EvmTxType.Erc20Approve, + }; } - - return approvalResponses; } private async getTxReceipt( @@ -319,11 +320,15 @@ export class EvmClient extends Client< } private async getTxReceiptOrThrow( - txResponse: TransactionResponse, + txIdOrResponse: string | TransactionResponse, ): Promise { - const txReceipt = await this.getTxReceipt(txResponse); + const txReceipt = await this.getTxReceipt(txIdOrResponse); if (txReceipt === null) { - throw new Error(`Transaction not found: ${txResponse.hash}`); + const id = + typeof txIdOrResponse === "string" + ? txIdOrResponse + : txIdOrResponse.hash; + throw new Error(`Transaction not found: ${id}`); } return txReceipt; } @@ -333,9 +338,10 @@ export class EvmClient extends Client< signer, spenderAddress, mintAddress, - }: ApproveErc20TokenParams): Promise { + }: ApproveErc20TokenParams): Promise { const token = ERC20__factory.connect(mintAddress, signer); - return token.approve(spenderAddress, atomicAmount); + const txResponse = await token.approve(spenderAddress, atomicAmount); + return await this.getTx(txResponse); } /** @@ -348,7 +354,7 @@ export class EvmClient extends Client< targetChainId, mintAddress, wallet, - }: TransferTokenParams): Promise { + }: TransferTokenParams): Promise { const { signer } = wallet; if (!signer) { throw new Error("No EVM signer"); @@ -367,6 +373,7 @@ export class EvmClient extends Client< createNonce(), ); const txRequest = appendHexDataToEvmTx(interactionId, populatedTx); - return signer.sendTransaction(txRequest); + const txResponse = await signer.sendTransaction(txRequest); + return await this.getTx(txResponse); } } diff --git a/packages/evm/src/protocol.ts b/packages/evm/src/protocol.ts index 4d6da2e86..02acf4418 100644 --- a/packages/evm/src/protocol.ts +++ b/packages/evm/src/protocol.ts @@ -46,10 +46,13 @@ export interface EvmEcosystemConfig readonly chains: Partial>>; } -export interface EvmTx extends Tx { - readonly ecosystemId: EvmEcosystemId; - readonly receipt: ethers.providers.TransactionReceipt; +export enum EvmTxType { + Erc20Approve = "erc20:approve", + PortalTransferTokens = "portal:transferTokens", + PortalCompleteTransfer = "portal:completeTransfer", } -export const isEvmTx = (tx: Tx): tx is EvmTx => +export type EvmTx = Tx; + +export const isEvmTx = (tx: Tx): tx is EvmTx => isEvmEcosystemId(tx.ecosystemId); From 8b1f979d50bc1f50001c33875230725ce6ee870b Mon Sep 17 00:00:00 2001 From: wormat Date: Tue, 18 Oct 2022 18:37:30 +0200 Subject: [PATCH 06/36] feat(solana): Match new client wormhole method signature with tx types --- packages/solana/src/client.ts | 193 +++++++++++++++++++++----------- packages/solana/src/protocol.ts | 12 +- packages/solana/src/wormhole.ts | 55 ++++++--- 3 files changed, 174 insertions(+), 86 deletions(-) diff --git a/packages/solana/src/client.ts b/packages/solana/src/client.ts index 8337acf84..2e9e863db 100644 --- a/packages/solana/src/client.ts +++ b/packages/solana/src/client.ts @@ -1,7 +1,4 @@ -import { - createPostVaaInstructionSolana, - createVerifySignaturesInstructionsSolana, -} from "@certusone/wormhole-sdk"; +import { createVerifySignaturesInstructionsSolana } from "@certusone/wormhole-sdk"; import { createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, @@ -23,9 +20,10 @@ import { PublicKey, } from "@solana/web3.js"; import type { - CompleteWormholeTransferParams, - InitiateWormholeTransferParams, + CompletePortalTransferParams, + InitiatePortalTransferParams, TokenDetails, + TxGeneratorResult, } from "@swim-io/core"; import { Client, getTokenDetails } from "@swim-io/core"; import { atomicToHuman, chunks, sleep } from "@swim-io/utils"; @@ -36,12 +34,16 @@ import type { SolanaEcosystemId, SolanaTx, } from "./protocol"; -import { SOLANA_ECOSYSTEM_ID } from "./protocol"; +import { SOLANA_ECOSYSTEM_ID, SolanaTxType } from "./protocol"; import type { TokenAccount } from "./serialization"; import { deserializeTokenAccount } from "./serialization"; import { createMemoIx, createTx } from "./utils"; import type { SolanaWalletAdapter } from "./walletAdapters"; -import { createRedeemOnSolanaTx, createTransferFromSolanaTx } from "./wormhole"; +import { + createPostVaaTx, + createRedeemOnSolanaTx, + createTransferFromSolanaTx, +} from "./wormhole"; export const DEFAULT_MAX_RETRIES = 10; export const DEFAULT_COMMITMENT_LEVEL: Finality = "confirmed"; @@ -51,15 +53,14 @@ type WithOptionalAuxiliarySigner = T & { readonly auxiliarySigner?: Keypair; }; -interface GeneratePostVaaTxIdsParams - extends Omit< - WithOptionalAuxiliarySigner< - CompleteWormholeTransferParams - >, - "wallet" - > { +type CompleteWormholeMessageParams = + CompletePortalTransferParams; + +interface GenerateVerifySignaturesTxsParams + extends Omit, "wallet"> { readonly signTx: (tx: Transaction) => Promise; readonly payerAddress: string; + readonly auxiliarySigner: Keypair; } export interface GetSolanaTransactionOptions { @@ -84,14 +85,13 @@ export class CustomConnection extends Connection { } export const parsedTxToSolanaTx = ( - txId: string, parsedTx: ParsedTransactionWithMeta, ): SolanaTx => ({ - id: txId, + id: parsedTx.transaction.signatures[0], ecosystemId: SOLANA_ECOSYSTEM_ID, timestamp: parsedTx.blockTime ?? null, interactionId: null, - parsedTx, + original: parsedTx, }); export interface SolanaClientOptions { @@ -105,6 +105,8 @@ export interface SolanaClientOptions { export class SolanaClient extends Client< SolanaEcosystemId, SolanaChainConfig, + ParsedTransactionWithMeta, + SolanaTxType, SolanaTx, SolanaWalletAdapter > { @@ -132,14 +134,12 @@ export class SolanaClient extends Client< public async getTx(txId: string): Promise { const parsedTx = await this.getParsedTx(txId); - return parsedTxToSolanaTx(txId, parsedTx); + return parsedTxToSolanaTx(parsedTx); } public async getTxs(txIds: readonly string[]): Promise { const parsedTxs = await this.getParsedTxs(txIds); - return parsedTxs.map((parsedTx, i) => - parsedTxToSolanaTx(txIds[i], parsedTx), - ); + return parsedTxs.map((parsedTx) => parsedTxToSolanaTx(parsedTx)); } public async getGasBalance(address: string): Promise { @@ -175,7 +175,7 @@ export class SolanaClient extends Client< ); } - public async initiateWormholeTransfer({ + public async *generateInitiatePortalTransferTxs({ atomicAmount, targetChainId, targetAddress, @@ -185,8 +185,14 @@ export class SolanaClient extends Client< wrappedTokenInfo, auxiliarySigner = Keypair.generate(), }: WithOptionalAuxiliarySigner< - InitiateWormholeTransferParams - >): Promise { + InitiatePortalTransferParams + >): AsyncGenerator< + TxGeneratorResult< + ParsedTransactionWithMeta, + SolanaTx, + SolanaTxType.PortalInitiateTransfer + > + > { const solanaWalletAddress = wallet.publicKey?.toBase58() ?? null; if (solanaWalletAddress === null) { throw new Error("No Solana wallet address"); @@ -198,7 +204,7 @@ export class SolanaClient extends Client< new PublicKey(mintAddress), new PublicKey(solanaWalletAddress), ).toBase58(); - const tx = await createTransferFromSolanaTx({ + const txRequest = await createTransferFromSolanaTx({ interactionId, connection: this.connection, bridgeAddress: this.chainConfig.wormhole.bridge, @@ -210,23 +216,31 @@ export class SolanaClient extends Client< amount: BigInt(atomicAmount), targetAddress, targetChainId, - originAddress: wrappedTokenInfo?.originAddress, - originChain: wrappedTokenInfo?.originChainId, + wrappedTokenInfo, }); - return await this.sendAndConfirmTx(async (txToSign: Transaction) => { + const txId = await this.sendAndConfirmTx(async (txToSign: Transaction) => { txToSign.partialSign(auxiliarySigner); return wallet.signTransaction(txToSign); - }, tx); + }, txRequest); + const tx = await this.getTx(txId); + yield { + tx, + type: SolanaTxType.PortalInitiateTransfer, + }; } - public async *generateCompleteWormholeTransferTxIds({ + public async *generateCompleteWormholeMessageTxs({ interactionId, vaa, wallet, - auxiliarySigner, - }: WithOptionalAuxiliarySigner< - CompleteWormholeTransferParams - >): AsyncGenerator { + auxiliarySigner = Keypair.generate(), + }: WithOptionalAuxiliarySigner): AsyncGenerator< + TxGeneratorResult< + ParsedTransactionWithMeta, + SolanaTx, + SolanaTxType.WormholeVerifySignatures | SolanaTxType.WormholePostVaa + > + > { const walletAddress = wallet.publicKey?.toBase58(); if (!walletAddress) { throw new Error("No Solana public key"); @@ -234,37 +248,80 @@ export class SolanaClient extends Client< const signTx = wallet.signTransaction.bind(wallet); - const postVaaSolanaTxIdsGenerator = this.generatePostVaaTxIds({ + const verifySignaturesTxsGenerator = this.generateVerifySignaturesTxs({ interactionId, signTx, payerAddress: walletAddress, vaa, auxiliarySigner, }); - for await (const txId of postVaaSolanaTxIdsGenerator) { - yield txId; + for await (const verifyTxResult of verifySignaturesTxsGenerator) { + yield verifyTxResult; } - const redeemTx = await createRedeemOnSolanaTx({ + + const postVaaTxRequest = await createPostVaaTx({ interactionId, bridgeAddress: this.chainConfig.wormhole.bridge, - portalAddress: this.chainConfig.wormhole.portal, payerAddress: walletAddress, vaa, + auxiliarySigner, }); - yield await this.sendAndConfirmTx(signTx, redeemTx); + const postVaaTxId = await this.sendAndConfirmTx(signTx, postVaaTxRequest); + const postVaaTx = await this.getTx(postVaaTxId); + yield { + tx: postVaaTx, + type: SolanaTxType.WormholePostVaa, + }; } - public async completeWormholeTransfer( - params: WithOptionalAuxiliarySigner< - CompleteWormholeTransferParams - >, - ): Promise { - let txIds: readonly string[] = []; - const generator = this.generateCompleteWormholeTransferTxIds(params); - for await (const txId of generator) { - txIds = [...txIds, txId]; + public async *generateCompletePortalTransferTxs({ + interactionId, + vaa, + wallet, + auxiliarySigner = Keypair.generate(), + }: WithOptionalAuxiliarySigner< + CompletePortalTransferParams + >): AsyncGenerator< + TxGeneratorResult< + ParsedTransactionWithMeta, + SolanaTx, + | SolanaTxType.WormholeVerifySignatures + | SolanaTxType.WormholePostVaa + | SolanaTxType.PortalRedeem + > + > { + const walletAddress = wallet.publicKey?.toBase58(); + if (!walletAddress) { + throw new Error("No Solana public key"); } - return txIds; + + const signTx = wallet.signTransaction.bind(wallet); + + const completeWormholeTransferTxsGenerator = + this.generateCompleteWormholeMessageTxs({ + interactionId, + vaa, + wallet, + auxiliarySigner, + }); + + for await (const result of completeWormholeTransferTxsGenerator) { + yield result; + } + + const redeemTxRequest = await createRedeemOnSolanaTx({ + interactionId, + bridgeAddress: this.chainConfig.wormhole.bridge, + portalAddress: this.chainConfig.wormhole.portal, + payerAddress: walletAddress, + vaa, + }); + const redeemTxId = await this.sendAndConfirmTx(signTx, redeemTxRequest); + const redeemTx = await this.getTx(redeemTxId); + yield { + tx: redeemTx, + type: SolanaTxType.PortalRedeem, + }; } public async confirmTx( @@ -567,13 +624,19 @@ export class SolanaClient extends Client< ); } - private async *generatePostVaaTxIds({ + private async *generateVerifySignaturesTxs({ interactionId, payerAddress, vaa, signTx, - auxiliarySigner = Keypair.generate(), - }: GeneratePostVaaTxIdsParams): AsyncGenerator { + auxiliarySigner, + }: GenerateVerifySignaturesTxsParams): AsyncGenerator< + TxGeneratorResult< + ParsedTransactionWithMeta, + SolanaTx, + SolanaTxType.WormholeVerifySignatures + > + > { const memoIx = createMemoIx(interactionId, []); const verifyIxs: readonly TransactionInstruction[] = await createVerifySignaturesInstructionsSolana( @@ -583,24 +646,14 @@ export class SolanaClient extends Client< Buffer.from(vaa), auxiliarySigner, ); - const finalIx: TransactionInstruction = - await createPostVaaInstructionSolana( - this.chainConfig.wormhole.bridge, - payerAddress, - Buffer.from(vaa), - auxiliarySigner, - ); // The verify signatures instructions can be batched into groups of 2 safely, // reducing the total number of transactions const batchableChunks = chunks(verifyIxs, 2); const feePayer = new PublicKey(payerAddress); - const unsignedTxs = batchableChunks.map((chunk) => + const verifyTxRequests = batchableChunks.map((chunk) => createTx({ feePayer }).add(...chunk, memoIx), ); - // The postVaa instruction can only execute after the verifySignature transactions have - // successfully completed - const finalTx = createTx({ feePayer }).add(finalIx, memoIx); // The signatureSet keypair also needs to sign the verifySignature transactions, thus a wrapper is needed const partialSignWrapper = async ( @@ -610,9 +663,13 @@ export class SolanaClient extends Client< return signTx(tx); }; - for (const tx of unsignedTxs) { - yield await this.sendAndConfirmTx(partialSignWrapper, tx); + for (const txRequest of verifyTxRequests) { + const txId = await this.sendAndConfirmTx(partialSignWrapper, txRequest); + const tx = await this.getTx(txId); + yield { + tx, + type: SolanaTxType.WormholeVerifySignatures, + }; } - yield await this.sendAndConfirmTx(signTx, finalTx); } } diff --git a/packages/solana/src/protocol.ts b/packages/solana/src/protocol.ts index d04232a0a..91bc9bc2c 100644 --- a/packages/solana/src/protocol.ts +++ b/packages/solana/src/protocol.ts @@ -37,10 +37,14 @@ export interface SolanaEcosystemConfig extends EcosystemConfig { readonly chains: Partial>; } -export interface SolanaTx extends Tx { - readonly ecosystemId: SolanaEcosystemId; - readonly parsedTx: ParsedTransactionWithMeta; +export enum SolanaTxType { + PortalInitiateTransfer = "portal:initiateTransfer", + PortalRedeem = "portal:redeem", + WormholeVerifySignatures = "wormhole:verifySignatures", + WormholePostVaa = "wormhole:postVaa", } -export const isSolanaTx = (tx: Tx): tx is SolanaTx => +export type SolanaTx = Tx; + +export const isSolanaTx = (tx: Tx): tx is SolanaTx => tx.ecosystemId === SOLANA_ECOSYSTEM_ID; diff --git a/packages/solana/src/wormhole.ts b/packages/solana/src/wormhole.ts index a6d7e70cf..087db56df 100644 --- a/packages/solana/src/wormhole.ts +++ b/packages/solana/src/wormhole.ts @@ -2,6 +2,7 @@ import type { ChainId } from "@certusone/wormhole-sdk"; import { CHAIN_ID_SOLANA, createNonce, + createPostVaaInstructionSolana, getBridgeFeeIx, importCoreWasm, importTokenWasm, @@ -10,11 +11,13 @@ import { import { createApproveInstruction } from "@solana/spl-token"; import type { Connection, + Keypair, ParsedTransactionWithMeta, Transaction, VersionedTransactionResponse, } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js"; +import type { WrappedTokenInfo } from "@swim-io/core/types"; import { createMemoIx, createTx } from "./utils"; @@ -48,8 +51,7 @@ export interface CreateTransferFromSolanaTxParams { readonly amount: bigint; readonly targetAddress: Uint8Array; readonly targetChainId: ChainId; - readonly originAddress?: Uint8Array; - readonly originChain?: ChainId; + readonly wrappedTokenInfo?: WrappedTokenInfo; readonly fromOwnerAddress?: string; } export const createTransferFromSolanaTx = async ({ @@ -64,34 +66,32 @@ export const createTransferFromSolanaTx = async ({ amount, targetAddress, targetChainId, - originAddress, - originChain, + wrappedTokenInfo, fromOwnerAddress, }: CreateTransferFromSolanaTxParams): Promise => { const nonce = createNonce().readUInt32LE(0); - const fee = BigInt(0); // for now, this won't do anything, we may add later + const fee = BigInt(0); // not currently used const bridgeFeeIx = await getBridgeFeeIx( connection, bridgeAddress, payerAddress, ); + // eslint-disable-next-line @typescript-eslint/unbound-method const { + approval_authority_address, transfer_native_ix, transfer_wrapped_ix, - approval_authority_address, } = await importTokenWasm(); const approvalIx = createApproveInstruction( new PublicKey(fromAddress), new PublicKey(approval_authority_address(portalAddress)), - new PublicKey(fromOwnerAddress || payerAddress), + new PublicKey(fromOwnerAddress ?? payerAddress), amount, ); const isSolanaNative = - originChain === undefined || originChain === CHAIN_ID_SOLANA; - if (!isSolanaNative && !originAddress) { - throw new Error("originAddress is required when specifying originChain"); - } + wrappedTokenInfo === undefined || + wrappedTokenInfo.originChainId === CHAIN_ID_SOLANA; const transferIx = ixFromRust( isSolanaNative ? transfer_native_ix( @@ -114,8 +114,8 @@ export const createTransferFromSolanaTx = async ({ auxiliarySignerAddress, fromAddress, fromOwnerAddress || payerAddress, - originChain as number, // checked by isSolanaNative - originAddress as Uint8Array, // checked by throw + wrappedTokenInfo.originChainId, + wrappedTokenInfo.originAddress, nonce, amount, fee, @@ -123,10 +123,37 @@ export const createTransferFromSolanaTx = async ({ targetChainId, ), ); + const memoIx = createMemoIx(interactionId, []); - const tx = createTx({ + return createTx({ feePayer: new PublicKey(payerAddress), }).add(bridgeFeeIx, approvalIx, transferIx, memoIx); +}; + +export interface CreatePostVaaTxParams { + readonly interactionId: string; + readonly bridgeAddress: string; + readonly payerAddress: string; + readonly vaa: Uint8Array; + readonly auxiliarySigner: Keypair; +} +export const createPostVaaTx = async ({ + interactionId, + bridgeAddress, + payerAddress, + vaa, + auxiliarySigner, +}: CreatePostVaaTxParams): Promise => { + const postVaaIx = await createPostVaaInstructionSolana( + bridgeAddress, + payerAddress, + Buffer.from(vaa), + auxiliarySigner, + ); + const memoIx = createMemoIx(interactionId, []); + const tx = createTx({ + feePayer: new PublicKey(payerAddress), + }).add(postVaaIx, memoIx); return tx; }; From 41d96389a43df684611d0f9fe56f7230d062dbbc Mon Sep 17 00:00:00 2001 From: wormat Date: Tue, 18 Oct 2022 18:40:15 +0200 Subject: [PATCH 07/36] feat(aptos): Match new client wormhole method signature --- packages/aptos/src/client.ts | 19 +++++++++++++++---- packages/aptos/src/protocol.ts | 9 +++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/aptos/src/client.ts b/packages/aptos/src/client.ts index 563c89334..b97696262 100644 --- a/packages/aptos/src/client.ts +++ b/packages/aptos/src/client.ts @@ -1,4 +1,4 @@ -import type { TokenDetails } from "@swim-io/core"; +import type { TokenDetails, TxGeneratorResult } from "@swim-io/core"; import { Client } from "@swim-io/core"; import { atomicToHuman } from "@swim-io/utils"; import { @@ -9,7 +9,12 @@ import { } from "aptos"; import Decimal from "decimal.js"; -import type { AptosChainConfig, AptosEcosystemId, AptosTx } from "./protocol"; +import type { + AptosChainConfig, + AptosEcosystemId, + AptosTx, + AptosTxType, +} from "./protocol"; import { APTOS_ECOSYSTEM_ID } from "./protocol"; import type { AptosWalletAdapter } from "./walletAdapters"; @@ -27,6 +32,8 @@ export interface AptosClientOptions { export class AptosClient extends Client< AptosEcosystemId, AptosChainConfig, + any, + AptosTxType, AptosTx, AptosWalletAdapter > { @@ -102,11 +109,15 @@ export class AptosClient extends Client< throw new Error("Not implemented"); } - public initiateWormholeTransfer(): Promise { + public generateInitiatePortalTransferTxs(): AsyncGenerator< + TxGeneratorResult + > { throw new Error("Not implemented"); } - public completeWormholeTransfer(): Promise { + public generateCompletePortalTransferTxs(): AsyncGenerator< + TxGeneratorResult + > { throw new Error("Not implemented"); } } diff --git a/packages/aptos/src/protocol.ts b/packages/aptos/src/protocol.ts index e6200fa0e..b7f86ad47 100644 --- a/packages/aptos/src/protocol.ts +++ b/packages/aptos/src/protocol.ts @@ -27,9 +27,10 @@ export interface AptosEcosystemConfig extends EcosystemConfig { readonly chains: Partial>; } -export interface AptosTx extends Tx { - readonly ecosystemId: AptosEcosystemId; -} +// TODO: Make an enum +export type AptosTxType = string; + +export type AptosTx = Tx; -export const isAptosTx = (tx: Tx): tx is AptosTx => +export const isAptosTx = (tx: Tx): tx is AptosTx => tx.ecosystemId === APTOS_ECOSYSTEM_ID; From 8864bb72874656dafeb48c53b1fb90f3b9cee8e2 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 20 Oct 2022 13:36:39 +0200 Subject: [PATCH 08/36] chore: Bump version --- packages/aptos/package.json | 2 +- packages/core/package.json | 2 +- packages/eslint-config/package.json | 2 +- packages/evm-contracts/package.json | 2 +- packages/evm/package.json | 2 +- packages/pool-math/package.json | 2 +- packages/solana-contracts/package.json | 2 +- packages/solana-usdc-usdt-swap/package.json | 2 +- packages/solana/package.json | 2 +- packages/token-projects/package.json | 2 +- packages/tsconfig/package.json | 2 +- packages/utils/package.json | 2 +- packages/wormhole/package.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/aptos/package.json b/packages/aptos/package.json index 8cbb967a0..94e5d6587 100644 --- a/packages/aptos/package.json +++ b/packages/aptos/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/aptos", - "version": "0.39.0", + "version": "0.40.0", "description": "Swim Aptos SDK.", "main": "build", "types": "types", diff --git a/packages/core/package.json b/packages/core/package.json index 99902856f..5dfe4d12f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/core", - "version": "0.39.0", + "version": "0.40.0", "description": "Basic ecosystem-neutral types and helpers used by Swim.", "main": "build", "types": "types", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 136eb6358..6627a8a6c 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/eslint-config", - "version": "0.39.0", + "version": "0.40.0", "description": "Shared default ESLint configuration for Swim TS projects.", "exports": { ".": "./index.cjs", diff --git a/packages/evm-contracts/package.json b/packages/evm-contracts/package.json index b29469338..a84333f4c 100644 --- a/packages/evm-contracts/package.json +++ b/packages/evm-contracts/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/evm-contracts", - "version": "0.39.0", + "version": "0.40.0", "description": "TS bindings for Swim EVM pool contracts.", "main": "build", "types": "types", diff --git a/packages/evm/package.json b/packages/evm/package.json index b96c282d6..f96ea8aaf 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/evm", - "version": "0.39.0", + "version": "0.40.0", "description": "Swim code relating to EVM.", "main": "build", "types": "types", diff --git a/packages/pool-math/package.json b/packages/pool-math/package.json index e71f451f8..a3cb02426 100644 --- a/packages/pool-math/package.json +++ b/packages/pool-math/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/pool-math", - "version": "0.39.0", + "version": "0.40.0", "description": "A package for calculating input/output amounts for Swim liquidity pools.", "main": "build", "types": "types", diff --git a/packages/solana-contracts/package.json b/packages/solana-contracts/package.json index 03142d05d..ea176d2a5 100644 --- a/packages/solana-contracts/package.json +++ b/packages/solana-contracts/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/solana-contracts", - "version": "0.39.0", + "version": "0.40.0", "description": "TS bindings for Swim Solana pool contracts.", "main": "build", "types": "types", diff --git a/packages/solana-usdc-usdt-swap/package.json b/packages/solana-usdc-usdt-swap/package.json index 360de68c7..ba8163dc5 100644 --- a/packages/solana-usdc-usdt-swap/package.json +++ b/packages/solana-usdc-usdt-swap/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/solana-usdc-usdt-swap", - "version": "0.39.0", + "version": "0.40.0", "description": "Create Swim swap instructions for hexapool to swap Solana native USDC and USDT", "main": "build", "types": "types", diff --git a/packages/solana/package.json b/packages/solana/package.json index f50224923..3759dc960 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/solana", - "version": "0.39.0", + "version": "0.40.0", "description": "Swim code relating to Solana.", "main": "build", "types": "types", diff --git a/packages/token-projects/package.json b/packages/token-projects/package.json index 1f5d738f2..76f55e3c2 100644 --- a/packages/token-projects/package.json +++ b/packages/token-projects/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/token-projects", - "version": "0.39.0", + "version": "0.40.0", "description": "A registry of token projects used by Swim.", "main": "build", "types": "types", diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index 78a54c8a8..858d7c773 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/tsconfig", - "version": "0.39.0", + "version": "0.40.0", "description": "Shared default TypeScript configuration for Swim TS projects.", "files": [ "*.json", diff --git a/packages/utils/package.json b/packages/utils/package.json index 1ea75ca8a..2bce0da3f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/utils", - "version": "0.39.0", + "version": "0.40.0", "description": "Minimal-dependency general-purpose utils.", "main": "build", "types": "types", diff --git a/packages/wormhole/package.json b/packages/wormhole/package.json index 69af16861..d059923d1 100644 --- a/packages/wormhole/package.json +++ b/packages/wormhole/package.json @@ -1,6 +1,6 @@ { "name": "@swim-io/wormhole", - "version": "0.39.0", + "version": "0.40.0", "description": "Swim code relating to Wormhole.", "main": "build", "types": "types", From 80f209544c47b095da118a369fa77ac48ac5b8ef Mon Sep 17 00:00:00 2001 From: wormat Date: Wed, 19 Oct 2022 19:28:52 +0200 Subject: [PATCH 09/36] style(ui): Improve naming of fetchEvmTxsForInteractionId function --- .../interaction/useReloadInteractionStateMutation.test.ts | 6 +++--- .../hooks/interaction/useReloadInteractionStateMutation.ts | 4 ++-- apps/ui/src/models/swim/interactionId.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts index 0e086ae14..1b764664e 100644 --- a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts +++ b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts @@ -12,7 +12,7 @@ import { SOLANA_TXS_FOR_RELOAD_INTERACTION, } from "../../fixtures/tx/useReloadInteractionStateMutationFixture"; import { - fetchEvmTxForInteractionId, + fetchEvmTxsForInteractionId, fetchSolanaTxsForInteractionId, } from "../../models"; import { mockOf, renderHookWithAppContext } from "../../testUtils"; @@ -44,7 +44,7 @@ jest.mock("../../models", () => ({ fetchSolanaTxsForInteractionId: jest.fn(), EvmConnection: jest.fn(), })); -const fetchEvmTxForInteractionIdMock = mockOf(fetchEvmTxForInteractionId); +const fetchEvmTxsForInteractionIdMock = mockOf(fetchEvmTxsForInteractionId); const fetchSolanaTxsForInteractionIdMock = mockOf( fetchSolanaTxsForInteractionId, ); @@ -90,7 +90,7 @@ describe("useReloadInteractionStateMutation", () => { useEvmWalletMock.mockReturnValue({ address: "0xb0a05611328d1068c91f58e2c83ab4048de8cd7f", }); - fetchEvmTxForInteractionIdMock.mockReturnValue( + fetchEvmTxsForInteractionIdMock.mockReturnValue( Promise.resolve(EVM_TXS_FOR_RELOAD_INTERACTION), ); fetchSolanaTxsForInteractionIdMock.mockReturnValue( diff --git a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts index 900ec91cc..04a5d99c0 100644 --- a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts +++ b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts @@ -7,7 +7,7 @@ import { Protocol, getSolanaTokenDetails } from "../../config"; import { selectConfig, selectGetInteractionState } from "../../core/selectors"; import { useEnvironment, useInteractionState } from "../../core/store"; import { - fetchEvmTxForInteractionId, + fetchEvmTxsForInteractionId, fetchSolanaTxsForInteractionId, getFromEcosystemOfToSolanaTransfer, getRequiredEcosystems, @@ -80,7 +80,7 @@ export const useReloadInteractionStateMutation = () => { ); // Get other evm tx - const evmTxs = await fetchEvmTxForInteractionId( + const evmTxs = await fetchEvmTxsForInteractionId( interactionId, queryClient, interaction.env, diff --git a/apps/ui/src/models/swim/interactionId.ts b/apps/ui/src/models/swim/interactionId.ts index 9faa8d8ea..ba51540c9 100644 --- a/apps/ui/src/models/swim/interactionId.ts +++ b/apps/ui/src/models/swim/interactionId.ts @@ -27,7 +27,7 @@ const findEvmInteractionId = ( return dataHex.slice(-INTERACTION_ID_LENGTH_HEX); }; -export const fetchEvmTxForInteractionId = async ( +export const fetchEvmTxsForInteractionId = async ( interactionId: string, queryClient: QueryClient, env: Env, From c2486c8331ca0fd9a729220808cbb07d3318efd7 Mon Sep 17 00:00:00 2001 From: wormat Date: Tue, 18 Oct 2022 18:40:54 +0200 Subject: [PATCH 10/36] refactor(ui): Update for new client wormhole methods Updates @swim-io dependencies to v0.40.0 --- apps/ui/package.json | 20 +-- ...seReloadInteractionStateMutationFixture.ts | 132 ++++++++-------- .../interaction/useAddInteractionMutation.ts | 23 ++- ...ossChainEvmToEvmSwapInteractionMutation.ts | 26 ++-- .../useFromSolanaTransferMutation.ts | 87 +++++------ .../useReloadInteractionStateMutation.test.ts | 2 +- .../useReloadInteractionStateMutation.ts | 2 +- .../useRemoveInteractionMutation.ts | 21 ++- ...seSingleChainEvmSwapInteractionMutation.ts | 27 ++-- .../useToSolanaTransferMutation.test.ts | 53 ++++--- .../useToSolanaTransferMutation.ts | 103 +++++++------ apps/ui/src/models/crossEcosystem/tx.test.ts | 4 +- .../models/solana/getSwimUsdBalanceChange.ts | 4 +- .../swim/doSingleSolanaPoolOperation.ts | 6 +- .../src/models/swim/getTransferredAmounts.ts | 4 +- apps/ui/src/models/swim/interactionId.ts | 2 +- apps/ui/src/models/swim/pool.test.ts | 18 +-- apps/ui/src/models/swim/pool.ts | 2 +- apps/ui/src/models/wormhole/evm.ts | 8 +- apps/ui/src/models/wormhole/solana.test.ts | 10 +- apps/ui/src/models/wormhole/solana.ts | 18 +-- yarn.lock | 141 +++++++++++++----- 22 files changed, 395 insertions(+), 318 deletions(-) diff --git a/apps/ui/package.json b/apps/ui/package.json index baeabd239..a87d9fba5 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -50,16 +50,16 @@ "@sentry/types": "^7.9.0", "@solana/spl-token": "^0.3.5", "@solana/web3.js": "^1.62.0", - "@swim-io/aptos": "^0.39.0", - "@swim-io/core": "^0.39.0", - "@swim-io/evm": "^0.39.0", - "@swim-io/evm-contracts": "^0.39.0", - "@swim-io/pool-math": "^0.39.0", - "@swim-io/solana": "^0.39.0", - "@swim-io/solana-contracts": "^0.39.0", - "@swim-io/token-projects": "^0.39.0", - "@swim-io/utils": "^0.39.0", - "@swim-io/wormhole": "^0.39.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", "bn.js": "^5.2.1", "classnames": "^2.3.1", "decimal.js": "^10.3.1", diff --git a/apps/ui/src/fixtures/tx/useReloadInteractionStateMutationFixture.ts b/apps/ui/src/fixtures/tx/useReloadInteractionStateMutationFixture.ts index 872f41715..b5ded97f3 100644 --- a/apps/ui/src/fixtures/tx/useReloadInteractionStateMutationFixture.ts +++ b/apps/ui/src/fixtures/tx/useReloadInteractionStateMutationFixture.ts @@ -133,7 +133,7 @@ export const SOLANA_TXS_FOR_RELOAD_INTERACTION: readonly SolanaTx[] = [ id: "53PBEMpqPraH1KFGSQfGn8JR62kndfU6iv6XqeJdDtpuEyD9FLkGjtnUZUB6TPv4H8A7kVxk2WiyEJPY7bLCNQGC", timestamp: 1656406854, interactionId: "a9747f341d116e592f6eac839b7f222d", - parsedTx: { + original: { blockTime: 1656406854, meta: { err: null, @@ -483,7 +483,7 @@ export const SOLANA_TXS_FOR_RELOAD_INTERACTION: readonly SolanaTx[] = [ id: "53mCCVJEvoERa1anMkJxm5JD3doRcMBoQVyw8ZgtJ5sMuDZsw1QaW8worMbsbWBqAhwAheURKNKA7xrafSHyDEjA", timestamp: 1656406848, interactionId: "a9747f341d116e592f6eac839b7f222d", - parsedTx: { + original: { blockTime: 1656406848, meta: { err: null, @@ -850,7 +850,7 @@ export const SOLANA_TXS_FOR_RELOAD_INTERACTION: readonly SolanaTx[] = [ id: "4LCZusMofy5oPLZe5cX5VCn4T1n6qgGsxCRhbwTVAcKSvZRvQLdeEWXJef2m5sD9u6XfRgRNRcBHJBwB48tun2eQ", timestamp: 1656406843, interactionId: "a9747f341d116e592f6eac839b7f222d", - parsedTx: { + original: { blockTime: 1656406843, meta: { err: null, @@ -1345,7 +1345,7 @@ export const SOLANA_TXS_FOR_RELOAD_INTERACTION: readonly SolanaTx[] = [ id: "5UfH9wni8vGP8Ch2KQp2JjoPKyWssFjePVpxAduFErWQVFEfF7Av3iCK9wA7CyQTWUkHZtr6ThoWxZXjr73dVQqF", timestamp: 1656406839, interactionId: "a9747f341d116e592f6eac839b7f222d", - parsedTx: { + original: { blockTime: 1656406839, meta: { err: null, @@ -1642,7 +1642,7 @@ export const SOLANA_TXS_FOR_RELOAD_INTERACTION: readonly SolanaTx[] = [ id: "reEurpv1vonjzLPpqoMWvcNV5bbJmhJwfPPM7d7PEEVcb8mN6DzZTqPtYMLcenJ6VLMa3naXe4gPzPkxurjQy4e", timestamp: 1656406836, interactionId: "a9747f341d116e592f6eac839b7f222d", - parsedTx: { + original: { blockTime: 1656406836, meta: { err: null, @@ -1812,7 +1812,7 @@ export const SOLANA_TXS_FOR_RELOAD_INTERACTION: readonly SolanaTx[] = [ id: "61FvZ4bp3Ua2ED6cv32rqZnLnW5hGDYMf6racBeoZXJaxzVUVZzEEqtut29aqeBoGwxk3Dhr7mbXY6ziVpCDiHTT", timestamp: 1656406832, interactionId: "a9747f341d116e592f6eac839b7f222d", - parsedTx: { + original: { blockTime: 1656406832, meta: { err: null, @@ -1982,36 +1982,36 @@ export const EVM_TXS_FOR_RELOAD_INTERACTION = [ id: "0xdacf9f474992e86e079b588573eff53542f1722386280c55aa71057e5771732f", timestamp: 1656406577, interactionId: "a9747f341d116e592f6eac839b7f222d", - response: { - hash: "0xdacf9f474992e86e079b588573eff53542f1722386280c55aa71057e5771732f", - type: 0, - accessList: null, - blockHash: - "0xa0cef0931f71340206080d50fd4f00fb1d924f3bd8a4ff436027f05147f2f2f2", - blockNumber: 7132783, - transactionIndex: 15, - confirmations: 60, - from: "0xb0A05611328d1068c91F58e2c83Ab4048De8CD7f", - gasPrice: { - type: "BigNumber", - hex: "0x59682f07", - }, - gasLimit: { - type: "BigNumber", - hex: "0x0191ca", - }, - to: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7", - value: { - type: "BigNumber", - hex: "0x00", - }, - nonce: 41, - data: "0x0f5287b000000000000000000000000045b167cf5b14007ca0490dcfb7c4b870ec0c0aa6000000000000000000000000000000000000000000000000000000002b5c01900000000000000000000000000000000000000000000000000000000000000001a77f337a7b4a9d31232af9108048171b0b120b5cf09e06469b980e64c97f0dd400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059080100a9747f341d116e592f6eac839b7f222d", - creates: null, - chainId: 0, - timestamp: 1656406577, - }, - receipt: { + // response: { + // hash: "0xdacf9f474992e86e079b588573eff53542f1722386280c55aa71057e5771732f", + // type: 0, + // accessList: null, + // blockHash: + // "0xa0cef0931f71340206080d50fd4f00fb1d924f3bd8a4ff436027f05147f2f2f2", + // blockNumber: 7132783, + // transactionIndex: 15, + // confirmations: 60, + // from: "0xb0A05611328d1068c91F58e2c83Ab4048De8CD7f", + // gasPrice: { + // type: "BigNumber", + // hex: "0x59682f07", + // }, + // gasLimit: { + // type: "BigNumber", + // hex: "0x0191ca", + // }, + // to: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7", + // value: { + // type: "BigNumber", + // hex: "0x00", + // }, + // nonce: 41, + // data: "0x0f5287b000000000000000000000000045b167cf5b14007ca0490dcfb7c4b870ec0c0aa6000000000000000000000000000000000000000000000000000000002b5c01900000000000000000000000000000000000000000000000000000000000000001a77f337a7b4a9d31232af9108048171b0b120b5cf09e06469b980e64c97f0dd400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059080100a9747f341d116e592f6eac839b7f222d", + // creates: null, + // chainId: 0, + // timestamp: 1656406577, + // }, + original: { to: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7", from: "0xb0A05611328d1068c91F58e2c83Ab4048De8CD7f", contractAddress: null, @@ -2095,36 +2095,36 @@ export const EVM_TXS_FOR_RELOAD_INTERACTION = [ id: "0x5ddfb1925096babf7939393b62970700a4db183a5dd9ae36dfd2fc9c5d7da302", timestamp: 1656406883, interactionId: "a9747f341d116e592f6eac839b7f222d", - response: { - hash: "0x5ddfb1925096babf7939393b62970700a4db183a5dd9ae36dfd2fc9c5d7da302", - type: 0, - accessList: null, - blockHash: - "0x54d21312a280bff7c065a94a0f79c5d63067353ae3519a77127b661387d6c7f7", - blockNumber: 11039320, - transactionIndex: 1, - confirmations: 232, - from: "0xb0A05611328d1068c91F58e2c83Ab4048De8CD7f", - gasPrice: { - type: "BigNumber", - hex: "0x062b85e900", - }, - gasLimit: { - type: "BigNumber", - hex: "0x01ea1a", - }, - to: "0x61E44E506Ca5659E6c0bba9b678586fA2d729756", - value: { - type: "BigNumber", - hex: "0x00", - }, - nonce: 4, - data: "0xc687851900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100010000000001003c0275cf8e546d379e4af28406521092c8e7e4bc4854d9ee834f0c34cae36ae215eaedadf3f85e46ea9d0e865afc2af74998ab0c5112f8f492a5aeeef910dde20162bac3460000d2e900013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca9800000000000007752001000000000000000000000000000000000000000000000000000000002f32206c00000000000000000000000092934a8b10ddf85e81b65be1d6810544744700dc0006000000000000000000000000b0a05611328d1068c91f58e2c83ab4048de8cd7f00060000000000000000000000000000000000000000000000000000000000000000a9747f341d116e592f6eac839b7f222d", - creates: null, - chainId: 0, - timestamp: 1656406883, - }, - receipt: { + // response: { + // hash: "0x5ddfb1925096babf7939393b62970700a4db183a5dd9ae36dfd2fc9c5d7da302", + // type: 0, + // accessList: null, + // blockHash: + // "0x54d21312a280bff7c065a94a0f79c5d63067353ae3519a77127b661387d6c7f7", + // blockNumber: 11039320, + // transactionIndex: 1, + // confirmations: 232, + // from: "0xb0A05611328d1068c91F58e2c83Ab4048De8CD7f", + // gasPrice: { + // type: "BigNumber", + // hex: "0x062b85e900", + // }, + // gasLimit: { + // type: "BigNumber", + // hex: "0x01ea1a", + // }, + // to: "0x61E44E506Ca5659E6c0bba9b678586fA2d729756", + // value: { + // type: "BigNumber", + // hex: "0x00", + // }, + // nonce: 4, + // data: "0xc687851900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100010000000001003c0275cf8e546d379e4af28406521092c8e7e4bc4854d9ee834f0c34cae36ae215eaedadf3f85e46ea9d0e865afc2af74998ab0c5112f8f492a5aeeef910dde20162bac3460000d2e900013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca9800000000000007752001000000000000000000000000000000000000000000000000000000002f32206c00000000000000000000000092934a8b10ddf85e81b65be1d6810544744700dc0006000000000000000000000000b0a05611328d1068c91f58e2c83ab4048de8cd7f00060000000000000000000000000000000000000000000000000000000000000000a9747f341d116e592f6eac839b7f222d", + // creates: null, + // chainId: 0, + // timestamp: 1656406883, + // }, + original: { to: "0x61E44E506Ca5659E6c0bba9b678586fA2d729756", from: "0xb0A05611328d1068c91F58e2c83Ab4048De8CD7f", contractAddress: null, diff --git a/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts b/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts index 1b22e38f4..ef79d5145 100644 --- a/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts @@ -1,4 +1,4 @@ -import { isEvmEcosystemId } from "@swim-io/evm"; +import { EvmTxType, isEvmEcosystemId } from "@swim-io/evm"; import { Pool__factory } from "@swim-io/evm-contracts"; import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana"; import { findOrThrow } from "@swim-io/utils"; @@ -123,24 +123,21 @@ export const useAddInteractionMutation = () => { if (tokenDetails === null) { throw new Error("Missing token detail"); } - const responses = await evmClient.approveTokenAmount({ + const approveTxGenerator = evmClient.generateErc20ApproveTxs({ atomicAmount: amount.toAtomicString(ecosystem), wallet, mintAddress: tokenDetails.address, spenderAddress: poolSpec.address, }); - await Promise.all( - responses.map(async (response) => { - const tx = await evmClient.getTx(response); - updateInteractionState(interaction.id, (draft) => { - if (draft.interactionType !== interaction.type) { - throw new Error("Interaction type mismatch"); - } - draft.approvalTxIds = [...draft.approvalTxIds, tx.id]; - }); - }), - ); + for await (const result of approveTxGenerator) { + updateInteractionState(interaction.id, (draft) => { + if (draft.interactionType !== interaction.type) { + throw new Error("Interaction type mismatch"); + } + draft.approvalTxIds.push(result.tx.id); + }); + } }), ); diff --git a/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts b/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts index 85e4d9dd5..ea966d137 100644 --- a/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts @@ -5,6 +5,7 @@ import { import { getTokenDetails } from "@swim-io/core"; import { EVM_ECOSYSTEMS, + EvmTxType, evmAddressToWormhole, isEvmEcosystemId, } from "@swim-io/evm"; @@ -95,22 +96,23 @@ export const useCrossChainEvmToEvmSwapInteractionMutation = () => { fromTokenData.tokenConfig, fromTokenData.ecosystemId, ); - const approvalResponses = await fromEvmClient.approveTokenAmount({ + const approvalTxGenerator = fromEvmClient.generateErc20ApproveTxs({ atomicAmount, mintAddress: fromTokenDetails.address, wallet, spenderAddress: fromChainConfig.routingContractAddress, }); - const approvalTxs = await fromEvmClient.getTxs(approvalResponses); - updateInteractionState(interaction.id, (draft) => { - if ( - draft.interactionType !== InteractionType.SwapV2 || - draft.swapType !== SwapType.CrossChainEvmToEvm - ) { - throw new Error("Interaction type mismatch"); - } - draft.approvalTxIds = approvalTxs.map((tx) => tx.id); - }); + for await (const result of approvalTxGenerator) { + updateInteractionState(interaction.id, (draft) => { + if ( + draft.interactionType !== InteractionType.SwapV2 || + draft.swapType !== SwapType.CrossChainEvmToEvm + ) { + throw new Error("Interaction type mismatch"); + } + draft.approvalTxIds.push(result.tx.id); + }); + } const crossChainInitiateRequest = await fromRouting.populateTransaction[ "crossChainInitiate(address,uint256,uint256,uint16,bytes32,bytes16)" ]( @@ -144,7 +146,7 @@ export const useCrossChainEvmToEvmSwapInteractionMutation = () => { crossChainInitiateTxId, ); const wormholeSequence = parseSequenceFromLogEth( - crossChainInitiateTx.receipt, + crossChainInitiateTx.original, fromChainConfig.wormhole.bridge, ); const { wormholeChainId: emitterChainId } = ECOSYSTEMS[fromEcosystem]; diff --git a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts index 60c24960c..1ace25439 100644 --- a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts @@ -1,8 +1,10 @@ import { getEmitterAddressSolana } from "@certusone/wormhole-sdk"; import { Keypair } from "@solana/web3.js"; -import type { SolanaClient, TokenAccount } from "@swim-io/solana"; +import { EvmTxType } from "@swim-io/evm"; +import type { SolanaClient, SolanaTx, TokenAccount } from "@swim-io/solana"; import { SOLANA_ECOSYSTEM_ID, + SolanaTxType, findTokenAccountForMint, parseSequenceFromLogSolana, } from "@swim-io/solana"; @@ -104,7 +106,7 @@ export const useFromSolanaTransferMutation = () => { config, ); - let transferSplTokenTxIds: readonly string[] = []; + let transferSplTokenTxs: readonly SolanaTx[] = []; for (const [index, transfer] of fromSolanaTransfers.entries()) { const toEcosystem = getToEcosystemOfFromSolanaTransfer( transfer, @@ -113,10 +115,8 @@ export const useFromSolanaTransferMutation = () => { const { token, txIds } = transfer; // Transfer already completed, skip if (txIds.transferSplToken !== null) { - transferSplTokenTxIds = [ - ...transferSplTokenTxIds, - txIds.transferSplToken, - ]; + const tx = await solanaClient.getTx(txIds.transferSplToken); + transferSplTokenTxs = [...transferSplTokenTxs, tx]; continue; } @@ -152,32 +152,30 @@ export const useFromSolanaTransferMutation = () => { throw new Error("Missing SPL token account"); } - let transferSplTokenTxId = transfer.txIds.transferSplToken; - if (transferSplTokenTxId === null) { + if (transfer.txIds.transferSplToken === null) { // No existing tx const auxiliarySigner = Keypair.generate(); - transferSplTokenTxId = await solanaClient.initiateWormholeTransfer({ - atomicAmount: amount.toAtomicString(SOLANA_ECOSYSTEM_ID), - interactionId, - targetAddress: formatWormholeAddress( - evmEcosystem.protocol, - evmWalletAddress, - ), - targetChainId: evmEcosystem.wormholeChainId, - tokenProjectId: token.projectId, - wallet: solanaWallet, - auxiliarySigner, - wrappedTokenInfo: getWrappedTokenInfo(token, SOLANA_ECOSYSTEM_ID), - }); - // Update transfer state with txId - updateInteractionState(interactionId, (draft) => { - draft.fromSolanaTransfers[index].txIds.transferSplToken = - transferSplTokenTxId; - }); - transferSplTokenTxIds = [ - ...transferSplTokenTxIds, - transferSplTokenTxId, - ]; + const initiateTransferTxGenerator = + solanaClient.generateInitiatePortalTransferTxs({ + atomicAmount: amount.toAtomicString(SOLANA_ECOSYSTEM_ID), + interactionId, + targetAddress: formatWormholeAddress( + evmEcosystem.protocol, + evmWalletAddress, + ), + targetChainId: evmEcosystem.wormholeChainId, + tokenProjectId: token.projectId, + wallet: solanaWallet, + auxiliarySigner, + wrappedTokenInfo: getWrappedTokenInfo(token, SOLANA_ECOSYSTEM_ID), + }); + for await (const result of initiateTransferTxGenerator) { + transferSplTokenTxs = [...transferSplTokenTxs, result.tx]; + updateInteractionState(interactionId, (draft) => { + draft.fromSolanaTransfers[index].txIds.transferSplToken = + result.tx.id; + }); + } } } @@ -190,7 +188,7 @@ export const useFromSolanaTransferMutation = () => { transfer, interaction, ); - const transferSplTokenTxId = transferSplTokenTxIds[index]; + const transferTx = transferSplTokenTxs[index]; const evmWallet = wallets[toEcosystem].wallet; if (!evmWallet) { throw new Error("No EVM wallet"); @@ -199,8 +197,7 @@ export const useFromSolanaTransferMutation = () => { chains[Protocol.Evm], ({ ecosystem }) => ecosystem === toEcosystem, ); - const transferTx = await solanaClient.getTx(transferSplTokenTxId); - const sequence = parseSequenceFromLogSolana(transferTx.parsedTx); + const sequence = parseSequenceFromLogSolana(transferTx.original); const emitterAddress = await getEmitterAddressSolana( solanaWormhole.portal, ); @@ -217,21 +214,17 @@ export const useFromSolanaTransferMutation = () => { ); await evmWallet.switchNetwork(evmChain.chainId); const evmClient = getEvmClient(toEcosystem); - const redeemResponse = await evmClient.completeWormholeTransfer({ - interactionId, - vaa: vaaBytesResponse.vaaBytes, - wallet: evmWallet, - }); - if (redeemResponse === null) { - throw new Error( - `Transaction not found: (unlock/mint on ${evmChain.ecosystem})`, - ); + const completeTransferTxGenerator = + evmClient.generateCompletePortalTransferTxs({ + interactionId, + vaa: vaaBytesResponse.vaaBytes, + wallet: evmWallet, + }); + for await (const result of completeTransferTxGenerator) { + updateInteractionState(interactionId, (draft) => { + draft.fromSolanaTransfers[index].txIds.claimTokenOnEvm = result.tx.id; + }); } - // Update transfer state with txId - updateInteractionState(interactionId, (draft) => { - draft.fromSolanaTransfers[index].txIds.claimTokenOnEvm = - redeemResponse.hash; - }); } }); }; diff --git a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts index 1b764664e..f242f603e 100644 --- a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts +++ b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.test.ts @@ -40,7 +40,7 @@ const useSplTokenAccountsQueryMock = mockOf(useSplTokenAccountsQuery); jest.mock("../../models", () => ({ ...jest.requireActual("../../models"), - fetchEvmTxForInteractionId: jest.fn(), + fetchEvmTxsForInteractionId: jest.fn(), fetchSolanaTxsForInteractionId: jest.fn(), EvmConnection: jest.fn(), })); diff --git a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts index 04a5d99c0..7489291d8 100644 --- a/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts +++ b/apps/ui/src/hooks/interaction/useReloadInteractionStateMutation.ts @@ -170,7 +170,7 @@ export const useReloadInteractionStateMutation = () => { const match = solanaTxs.find( (solanaTx) => isPoolTx(poolSpec.contract, solanaTx) && - solanaTx.parsedTx.transaction.message.accountKeys.some( + solanaTx.original.transaction.message.accountKeys.some( (key) => key.pubkey.toBase58() === poolSpec.address, ), ); diff --git a/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts b/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts index 1cd073fe3..ae0ac3999 100644 --- a/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useRemoveInteractionMutation.ts @@ -183,23 +183,20 @@ export const useRemoveInteractionMutation = () => { if (tokenDetails === null) { throw new Error("Missing token detail"); } - const approvalResponses = await evmClient.approveTokenAmount({ + const approveTxGenerator = evmClient.generateErc20ApproveTxs({ atomicAmount: removeAmount.toAtomicString(ecosystem), wallet, mintAddress: tokenDetails.address, spenderAddress: poolSpec.address, }); - await Promise.all( - approvalResponses.map(async (response) => { - const tx = await evmClient.getTx(response); - updateInteractionState(interaction.id, (draft) => { - if (draft.interactionType !== interaction.type) { - throw new Error("Interaction type mismatch"); - } - draft.approvalTxIds.push(tx.id); - }); - }), - ); + for await (const result of approveTxGenerator) { + updateInteractionState(interaction.id, (draft) => { + if (draft.interactionType !== interaction.type) { + throw new Error("Interaction type mismatch"); + } + draft.approvalTxIds.push(result.tx.id); + }); + } const poolContract = Pool__factory.connect(poolSpec.address, signer); const txRequest = await getPopulatedTxForEvmRemoveInteraction( diff --git a/apps/ui/src/hooks/interaction/useSingleChainEvmSwapInteractionMutation.ts b/apps/ui/src/hooks/interaction/useSingleChainEvmSwapInteractionMutation.ts index fc91308f4..1e8582abd 100644 --- a/apps/ui/src/hooks/interaction/useSingleChainEvmSwapInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useSingleChainEvmSwapInteractionMutation.ts @@ -66,27 +66,24 @@ export const useSingleChainEvmSwapInteractionMutation = () => { fromTokenSpec, fromTokenData.ecosystemId, ); - const approvalResponses = await client.approveTokenAmount({ + const approveTxGenerator = client.generateErc20ApproveTxs({ atomicAmount: inputAmountAtomicString, wallet, mintAddress: tokenDetails.address, spenderAddress: routingContractAddress, }); - await Promise.all( - approvalResponses.map(async (response) => { - const tx = await client.getTx(response); - updateInteractionState(interaction.id, (draft) => { - if ( - draft.interactionType !== InteractionType.SwapV2 || - draft.swapType !== SwapType.SingleChainEvm - ) { - throw new Error("Interaction type mismatch"); - } - draft.approvalTxIds.push(tx.id); - }); - }), - ); + for await (const result of approveTxGenerator) { + updateInteractionState(interaction.id, (draft) => { + if ( + draft.interactionType !== InteractionType.SwapV2 || + draft.swapType !== SwapType.SingleChainEvm + ) { + throw new Error("Interaction type mismatch"); + } + draft.approvalTxIds.push(result.tx.id); + }); + } const routingContract = Routing__factory.connect( routingContractAddress, diff --git a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts index 37d090567..40418b80f 100644 --- a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts +++ b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.test.ts @@ -1,7 +1,7 @@ import { PublicKey } from "@solana/web3.js"; -import { EvmEcosystemId } from "@swim-io/evm"; -import type { TokenAccount } from "@swim-io/solana"; -import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana"; +import { EvmEcosystemId, EvmTxType } from "@swim-io/evm"; +import type { SolanaTx, TokenAccount } from "@swim-io/solana"; +import { SOLANA_ECOSYSTEM_ID, SolanaTxType } from "@swim-io/solana"; import { act, renderHook } from "@testing-library/react-hooks"; import { useQueryClient } from "react-query"; @@ -75,19 +75,26 @@ describe("useToSolanaTransferMutation", () => { mint, } as unknown as TokenAccount), ), - generateCompleteWormholeTransferTxIds: jest - .fn() - .mockReturnValue([ - Promise.resolve( - "3o1NH8sMDs5m9DMoVcqD5eZRny2JrrFBohn9TwEKHXhX4Xxg6uQV7JrupVuDJcwaHBuP8fCZhv1HWBYicMixsSPg", - ), - Promise.resolve( - "3ok2VJpHqZ2EqoDGVMyugENdKawTjNbmM4sm4tHpsoF6T8BHx78fk5vZBXH7KRpgX7P43vhnMnN5zb5NSogUfCsj", - ), - Promise.resolve( - "5rYoqeehFL7j5MbMqzE8NruiUeBaRVhwFpCKsdXUnuAr6NNcPiX3XUxq72SA2MtPhtEhEDU2ZPVP9m4rmkHgy2cC", - ), - ] as Partial>), + generateCompletePortalTransferTxs: jest.fn().mockReturnValue([ + Promise.resolve({ + tx: { + id: "3o1NH8sMDs5m9DMoVcqD5eZRny2JrrFBohn9TwEKHXhX4Xxg6uQV7JrupVuDJcwaHBuP8fCZhv1HWBYicMixsSPg", + }, + type: SolanaTxType.WormholeVerifySignatures, + }), + Promise.resolve({ + tx: { + id: "3ok2VJpHqZ2EqoDGVMyugENdKawTjNbmM4sm4tHpsoF6T8BHx78fk5vZBXH7KRpgX7P43vhnMnN5zb5NSogUfCsj", + }, + type: SolanaTxType.WormholePostVaa, + }), + Promise.resolve({ + tx: { + id: "5rYoqeehFL7j5MbMqzE8NruiUeBaRVhwFpCKsdXUnuAr6NNcPiX3XUxq72SA2MtPhtEhEDU2ZPVP9m4rmkHgy2cC", + }, + type: SolanaTxType.PortalRedeem, + }), + ] as Partial>), }); useWalletsMock.mockReturnValue({ [EvmEcosystemId.Bnb]: { @@ -109,14 +116,14 @@ describe("useToSolanaTransferMutation", () => { id: hash, }), ), - initiateWormholeTransfer: jest.fn(() => { - return Promise.resolve({ - approvalResponses: [], - transferResponse: { - hash: "0xd528c49eedda9d5a5a7f04a00355b7b124a30502b46532503cc83891844715b9", + generateInitiatePortalTransferTxs: jest.fn().mockReturnValue([ + Promise.resolve({ + tx: { + id: "0xd528c49eedda9d5a5a7f04a00355b7b124a30502b46532503cc83891844715b9", }, - }); - }), + type: EvmTxType.PortalTransferTokens, + }), + ]), provider: { getTransaction: jest.fn(() => Promise.resolve({ diff --git a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts index a9231e96c..6edf4b3b5 100644 --- a/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useToSolanaTransferMutation.ts @@ -3,7 +3,13 @@ import { parseSequenceFromLogEth, } from "@certusone/wormhole-sdk"; import { Keypair } from "@solana/web3.js"; -import { SOLANA_ECOSYSTEM_ID, findTokenAccountForMint } from "@swim-io/solana"; +import type { EvmTx } from "@swim-io/evm"; +import { EvmTxType } from "@swim-io/evm"; +import { + SOLANA_ECOSYSTEM_ID, + SolanaTxType, + findTokenAccountForMint, +} from "@swim-io/solana"; import { findOrThrow } from "@swim-io/utils"; import { WormholeChainId } from "@swim-io/wormhole"; import { useMutation } from "react-query"; @@ -62,7 +68,7 @@ export const useToSolanaTransferMutation = () => { ); const evmClients = fromEcosystems.map(getEvmClient); - let transferTxIds: readonly string[] = []; + let transferTxs: readonly EvmTx[] = []; for (const [index, transfer] of toSolanaTransfers.entries()) { const { token, value, txIds } = transfer; const fromEcosystem = getFromEcosystemOfToSolanaTransfer( @@ -71,12 +77,12 @@ export const useToSolanaTransferMutation = () => { ); // Transfer completed, skip if (txIds.approveAndTransferEvmToken.length > 0) { - transferTxIds = [ - ...transferTxIds, + const transferTx = await evmClients[index].getTx( txIds.approveAndTransferEvmToken[ txIds.approveAndTransferEvmToken.length - 1 ], - ]; + ); + transferTxs = [...transferTxs, transferTx]; continue; } const evmWallet = wallets[fromEcosystem].wallet; @@ -94,9 +100,9 @@ export const useToSolanaTransferMutation = () => { } // Process transfer if transfer txId does not exist - const { approvalResponses, transferResponse } = await evmClients[ + const evmTxGenerator = evmClients[ index - ].initiateWormholeTransfer({ + ].generateInitiatePortalTransferTxs({ atomicAmount: humanDecimalToAtomicString(value, token, fromEcosystem), interactionId, targetAddress: formatWormholeAddress( @@ -109,31 +115,35 @@ export const useToSolanaTransferMutation = () => { wrappedTokenInfo: getWrappedTokenInfo(token, fromEcosystem), }); - // Update transfer state with txId - const approveAndTransferEvmTokenTxIds = [ - ...approvalResponses, - transferResponse, - ].map(({ hash }) => hash); - updateInteractionState(interactionId, (draft) => { - draft.toSolanaTransfers[index].txIds.approveAndTransferEvmToken = - approveAndTransferEvmTokenTxIds; - }); - transferTxIds = [...transferTxIds, transferResponse.hash]; + for await (const result of evmTxGenerator) { + switch (result.type) { + case EvmTxType.PortalTransferTokens: + case EvmTxType.Erc20Approve: + if (result.type === EvmTxType.PortalTransferTokens) { + transferTxs = [...transferTxs, result.tx]; + } + updateInteractionState(interactionId, (draft) => { + draft.toSolanaTransfers[ + index + ].txIds.approveAndTransferEvmToken.push(result.tx.id); + }); + break; + default: + throw new Error(`Unexpected transaction type: ${result.tx.id}`); + } + } } - const sequences = await Promise.all( - toSolanaTransfers.map(async (transfer, index) => { - // Claim token completed, skip - if (transfer.txIds.claimTokenOnSolana !== null) { - return null; - } - const transferTx = await evmClients[index].getTx(transferTxIds[index]); - return parseSequenceFromLogEth( - transferTx.receipt, - evmChains[index].wormhole.bridge, - ); - }), - ); + const sequences = toSolanaTransfers.map((transfer, index) => { + // Claim token completed, skip + if (transfer.txIds.claimTokenOnSolana !== null) { + return null; + } + return parseSequenceFromLogEth( + transferTxs[index].original, + evmChains[index].wormhole.bridge, + ); + }); for (const [index, transfer] of toSolanaTransfers.entries()) { const fromEcosystem = getFromEcosystemOfToSolanaTransfer( @@ -167,25 +177,32 @@ export const useToSolanaTransferMutation = () => { retries, ); const unlockSplTokenTxIdsGenerator = - solanaClient.generateCompleteWormholeTransferTxIds({ + solanaClient.generateCompletePortalTransferTxs({ interactionId, vaa, wallet: solanaWallet, auxiliarySigner, }); - let unlockSplTokenTxIds: readonly string[] = []; - for await (const txId of unlockSplTokenTxIdsGenerator) { - unlockSplTokenTxIds = [...unlockSplTokenTxIds, txId]; + for await (const result of unlockSplTokenTxIdsGenerator) { + switch (result.type) { + case SolanaTxType.WormholeVerifySignatures: + case SolanaTxType.WormholePostVaa: + updateInteractionState(interactionId, (draft) => { + draft.toSolanaTransfers[index].txIds.postVaaOnSolana.push( + result.tx.id, + ); + }); + break; + case SolanaTxType.PortalRedeem: + updateInteractionState(interactionId, (draft) => { + draft.toSolanaTransfers[index].txIds.claimTokenOnSolana = + result.tx.id; + }); + break; + default: + throw new Error(`Unexpected transaction type: ${result.tx.id}`); + } } - // Update transfer state with txId - const postVaaOnSolanaTxIds = unlockSplTokenTxIds.slice(0, -1); - const [claimTokenOnSolanaTxId] = unlockSplTokenTxIds.slice(-1); - updateInteractionState(interactionId, (draft) => { - draft.toSolanaTransfers[index].txIds.postVaaOnSolana = - postVaaOnSolanaTxIds; - draft.toSolanaTransfers[index].txIds.claimTokenOnSolana = - claimTokenOnSolanaTxId; - }); } }); }; diff --git a/apps/ui/src/models/crossEcosystem/tx.test.ts b/apps/ui/src/models/crossEcosystem/tx.test.ts index d3bc723ce..a0c04b97b 100644 --- a/apps/ui/src/models/crossEcosystem/tx.test.ts +++ b/apps/ui/src/models/crossEcosystem/tx.test.ts @@ -15,7 +15,7 @@ describe("Cross-ecosystem tx", () => { id: "34PhSGJi3XboZEhZEirTM6FEh1hNiYHSio1va1nNgH7S9LSNJQGSAiizEyVbgbVJzFjtsbyuJ2WijN53FSC83h7h", timestamp: defaultTimestamp, interactionId: defaultInteractionId, - parsedTx: parsedSwimSwapTx, + original: parsedSwimSwapTx, }; const ethereumTx: EvmTx = { @@ -23,7 +23,7 @@ describe("Cross-ecosystem tx", () => { id: "0x743087e871039d66b82fcb2cb719f6a541e650e05735c32c1be871ef9ae9a456", timestamp: defaultTimestamp, interactionId: defaultInteractionId, - receipt: mock(), + original: mock(), }; const bnbTx: EvmTx = { diff --git a/apps/ui/src/models/solana/getSwimUsdBalanceChange.ts b/apps/ui/src/models/solana/getSwimUsdBalanceChange.ts index 482e02c7f..468965d7f 100644 --- a/apps/ui/src/models/solana/getSwimUsdBalanceChange.ts +++ b/apps/ui/src/models/solana/getSwimUsdBalanceChange.ts @@ -6,8 +6,8 @@ export const getSwimUsdBalanceChange = async ( solanaClient: SolanaClient, swimUsdSplTokenAccount: TokenAccount, ): Promise => { - const { parsedTx } = await solanaClient.getTx(swapToSwimUsdTxId); - const { preTokenBalances, postTokenBalances } = parsedTx.meta ?? {}; + const { original } = await solanaClient.getTx(swapToSwimUsdTxId); + const { preTokenBalances, postTokenBalances } = original.meta ?? {}; if (!preTokenBalances || !postTokenBalances) { throw new Error(`Invalid transaction: ${swapToSwimUsdTxId}`); } diff --git a/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts b/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts index 5cac9885f..d77634e37 100644 --- a/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts +++ b/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts @@ -91,18 +91,18 @@ const getTransferredAmount = ( mintAddress: string, walletAddress: string, splTokenAccounts: readonly TokenAccount[], - tx: SolanaTx, + { original }: SolanaTx, ): Decimal => inputOperationSpec.instruction === SwimDefiInstruction.Add ? getAmountMintedToAccountByMint( splTokenAccounts, - tx.parsedTx, + original, mintAddress, walletAddress, ) : getAmountTransferredToAccountByMint( splTokenAccounts, - tx.parsedTx, + original, mintAddress, walletAddress, ); diff --git a/apps/ui/src/models/swim/getTransferredAmounts.ts b/apps/ui/src/models/swim/getTransferredAmounts.ts index 031b85eef..d21ec3fde 100644 --- a/apps/ui/src/models/swim/getTransferredAmounts.ts +++ b/apps/ui/src/models/swim/getTransferredAmounts.ts @@ -33,7 +33,7 @@ export const getTransferredAmounts = ( // Solana-native token amount = getAmountTransferredToAccountByMint( splTokenAccounts, - tx.parsedTx, + tx.original, mint, walletAddress, ); @@ -43,7 +43,7 @@ export const getTransferredAmounts = ( // Wormhole-wrapped token amount = getAmountMintedToAccountByMint( splTokenAccounts, - tx.parsedTx, + tx.original, mint, walletAddress, ); diff --git a/apps/ui/src/models/swim/interactionId.ts b/apps/ui/src/models/swim/interactionId.ts index ba51540c9..7e59617ee 100644 --- a/apps/ui/src/models/swim/interactionId.ts +++ b/apps/ui/src/models/swim/interactionId.ts @@ -87,7 +87,7 @@ const addSolanaInteractionId = async ( const tx = await solanaClient.getTx(signatureInfo.signature); // NOTE: Getting the ID from the log is more brittle but simpler than getting it from the instructions const memoLog = - tx.parsedTx.meta?.logMessages?.find((log) => MEMO_LOG_REGEXP.test(log)) ?? + tx.original.meta?.logMessages?.find((log) => MEMO_LOG_REGEXP.test(log)) ?? null; const match = memoLog?.match(MEMO_LOG_REGEXP); const interactionId = match?.groups?.[INTERACTION_ID_MATCH_GROUP] ?? null; diff --git a/apps/ui/src/models/swim/pool.test.ts b/apps/ui/src/models/swim/pool.test.ts index 658786d02..221fdef2e 100644 --- a/apps/ui/src/models/swim/pool.test.ts +++ b/apps/ui/src/models/swim/pool.test.ts @@ -47,7 +47,7 @@ describe("Pool tests", () => { id: "string", timestamp: 123456789, ecosystemId: ecosystemId, - receipt: txReceipt, + original: txReceipt, interactionId: "1", }; expect(isPoolTx(contractAddress, tx)).toBe(false); @@ -55,34 +55,34 @@ describe("Pool tests", () => { it("returns false, if not pool Solana tx", () => { const contractAddress = "SWiMDJYFUGj6cPrQ6QYYYWZtvXQdRChSVAygDZDsCHC"; - const ptx = { + const parsedTx = { ...mockDeep(), transaction: parsedWormholeRedeemEvmUnlockWrappedTx.transaction, }; - const txs: SolanaTx = { + const tx: SolanaTx = { ecosystemId: SOLANA_ECOSYSTEM_ID, - parsedTx: ptx, + original: parsedTx, id: "string", timestamp: 123456789, interactionId: "1", }; - expect(isPoolTx(contractAddress, txs)).toBe(false); + expect(isPoolTx(contractAddress, tx)).toBe(false); }); it("returns true, if it's pool solana tx", () => { const contractAddress = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"; - const ptx = { + const parsedTx = { ...mockDeep(), transaction: parsedWormholeRedeemEvmUnlockWrappedTx.transaction, }; - const txs: SolanaTx = { + const tx: SolanaTx = { ecosystemId: SOLANA_ECOSYSTEM_ID, - parsedTx: ptx, + original: parsedTx, id: "string", timestamp: 123456789, interactionId: "1", }; - expect(isPoolTx(contractAddress, txs)).toBe(true); + expect(isPoolTx(contractAddress, tx)).toBe(true); }); }); diff --git a/apps/ui/src/models/swim/pool.ts b/apps/ui/src/models/swim/pool.ts index 4c16f4466..580987d54 100644 --- a/apps/ui/src/models/swim/pool.ts +++ b/apps/ui/src/models/swim/pool.ts @@ -75,7 +75,7 @@ export const isPoolTx = ( if (!isSolanaTx(tx)) { return false; } - const { message } = tx.parsedTx.transaction; + const { message } = tx.original.transaction; return message.instructions.some( (ix) => ix.programId.toBase58() === poolContractAddress, ); diff --git a/apps/ui/src/models/wormhole/evm.ts b/apps/ui/src/models/wormhole/evm.ts index 59ad92cb9..0daf4fd58 100644 --- a/apps/ui/src/models/wormhole/evm.ts +++ b/apps/ui/src/models/wormhole/evm.ts @@ -14,11 +14,11 @@ export const isLockEvmTx = ( return false; } if ( - tx.receipt.to.toLowerCase() !== wormholeChainConfig.portal.toLowerCase() + tx.original.to.toLowerCase() !== wormholeChainConfig.portal.toLowerCase() ) { return false; } - return tx.receipt.logs.some( + return tx.original.logs.some( (log) => log.address.toLowerCase() === tokenDetails.address.toLowerCase(), ); }; @@ -33,11 +33,11 @@ export const isUnlockEvmTx = ( return false; } if ( - tx.receipt.to.toLowerCase() !== wormholeChainConfig.portal.toLowerCase() + tx.original.to.toLowerCase() !== wormholeChainConfig.portal.toLowerCase() ) { return false; } - return tx.receipt.logs.some( + return tx.original.logs.some( (log) => log.address.toLowerCase() === tokenDetails.address.toLowerCase(), ); }; diff --git a/apps/ui/src/models/wormhole/solana.test.ts b/apps/ui/src/models/wormhole/solana.test.ts index 38a4f5c24..19934d10e 100644 --- a/apps/ui/src/models/wormhole/solana.test.ts +++ b/apps/ui/src/models/wormhole/solana.test.ts @@ -41,7 +41,7 @@ describe("models - Wormhole utils", () => { ecosystemId: SOLANA_ECOSYSTEM_ID, timestamp: parsedSwimSwapTx.blockTime ?? null, id: parsedSwimSwapTx.transaction.signatures[0], - parsedTx: parsedSwimSwapTx, + original: parsedSwimSwapTx, }; expect( @@ -76,7 +76,7 @@ describe("models - Wormhole utils", () => { ecosystemId: SOLANA_ECOSYSTEM_ID, timestamp: parsedTx.blockTime!, id: parsedTx.transaction.signatures[0], - parsedTx, + original: parsedTx, }; const result = isPostVaaSolanaTx( @@ -101,7 +101,7 @@ describe("models - Wormhole utils", () => { ecosystemId: SOLANA_ECOSYSTEM_ID, timestamp: parsedWormholeRedeemEvmUnlockWrappedTx.blockTime!, id: parsedWormholeRedeemEvmUnlockWrappedTx.transaction.signatures[0], - parsedTx: parsedWormholeRedeemEvmUnlockWrappedTx, + original: parsedWormholeRedeemEvmUnlockWrappedTx, }; const result = isPostVaaSolanaTx( @@ -132,7 +132,7 @@ describe("models - Wormhole utils", () => { ecosystemId: SOLANA_ECOSYSTEM_ID, timestamp: parsedTx.blockTime!, id: parsedTx.transaction.signatures[0], - parsedTx, + original: parsedTx, }; const result = isRedeemOnSolanaTx( @@ -160,7 +160,7 @@ describe("models - Wormhole utils", () => { ecosystemId: SOLANA_ECOSYSTEM_ID, timestamp: parsedWormholeRedeemEvmUnlockWrappedTx.blockTime!, id: parsedWormholeRedeemEvmUnlockWrappedTx.transaction.signatures[0], - parsedTx: parsedWormholeRedeemEvmUnlockWrappedTx, + original: parsedWormholeRedeemEvmUnlockWrappedTx, }; const result = isRedeemOnSolanaTx( diff --git a/apps/ui/src/models/wormhole/solana.ts b/apps/ui/src/models/wormhole/solana.ts index bdb2a1457..e90049e1e 100644 --- a/apps/ui/src/models/wormhole/solana.ts +++ b/apps/ui/src/models/wormhole/solana.ts @@ -19,10 +19,10 @@ export const isLockSplTx = ( wormholeChainConfig: WormholeChainConfig, splTokenAccountAddress: string, token: TokenConfig, - { parsedTx }: SolanaTx, + { original }: SolanaTx, ): boolean => { if ( - !parsedTx.transaction.message.instructions.some( + !original.transaction.message.instructions.some( (ix) => ix.programId.toBase58() === wormholeChainConfig.portal, ) ) { @@ -31,11 +31,11 @@ export const isLockSplTx = ( return token.nativeEcosystemId === SOLANA_ECOSYSTEM_ID ? getAmountTransferredFromAccount( - parsedTx, + original, splTokenAccountAddress, ).greaterThan(0) : getAmountBurnedByMint( - parsedTx, + original, getSolanaTokenDetails(token).address, ).greaterThan(0); }; @@ -53,7 +53,7 @@ export const isPostVaaSolanaTx = ( if (signatureSetAddress === null) { return false; } - return tx.parsedTx.transaction.message.instructions.some( + return tx.original.transaction.message.instructions.some( (ix) => isPartiallyDecodedInstruction(ix) && ix.programId.toBase58() === wormholeChainConfig.bridge && @@ -65,16 +65,16 @@ export const isRedeemOnSolanaTx = ( wormholeChainConfig: WormholeChainConfig, token: TokenConfig, splTokenAccount: string, - { parsedTx }: SolanaTx, + { original }: SolanaTx, ): boolean => { if ( - !parsedTx.transaction.message.instructions.some( + !original.transaction.message.instructions.some( (ix) => ix.programId.toBase58() === wormholeChainConfig.portal, ) ) { return false; } return token.nativeEcosystemId === SOLANA_ECOSYSTEM_ID - ? getAmountTransferredToAccount(parsedTx, splTokenAccount).greaterThan(0) - : getAmountMintedToAccount(parsedTx, splTokenAccount).greaterThan(0); + ? getAmountTransferredToAccount(original, splTokenAccount).greaterThan(0) + : getAmountMintedToAccount(original, splTokenAccount).greaterThan(0); }; diff --git a/yarn.lock b/yarn.lock index 4c0814ed2..7f1ecd27d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7156,6 +7156,20 @@ __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": version: 0.0.0-use.local resolution: "@swim-io/aptos@workspace:packages/aptos" @@ -7247,6 +7261,19 @@ __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" @@ -7338,10 +7365,10 @@ __metadata: languageName: node linkType: hard -"@swim-io/evm-contracts@npm:^0.39.0": - version: 0.39.0 - resolution: "@swim-io/evm-contracts@npm:0.39.0" - checksum: 9c375aed437790788e6ac53ebf13829f34da2fe3d00859aed4d563b3e173d8ec2faac0b217c0ee11069978b755fa47c1db94b2a8cf3596157605716798a432ea +"@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 @@ -7399,14 +7426,14 @@ __metadata: languageName: node linkType: hard -"@swim-io/evm@npm:^0.39.0": - version: 0.39.0 - resolution: "@swim-io/evm@npm:0.39.0" +"@swim-io/evm@npm:^0.40.0": + version: 0.40.0 + resolution: "@swim-io/evm@npm:0.40.0" dependencies: - "@swim-io/core": ^0.39.0 - "@swim-io/evm-contracts": ^0.39.0 - "@swim-io/token-projects": ^0.39.0 - "@swim-io/utils": ^0.39.0 + "@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 @@ -7415,7 +7442,7 @@ __metadata: decimal.js: ^10.3.1 ethers: ^5.6.9 eventemitter3: ^4.0.7 - checksum: d45ae02e098b910f357ad4f584c74405d5882f223ba8f1469532e20739ad189e46729ee258421170fe5d5b4dab06486b74406beab0a4a3bc4896794dac6d6a2d + checksum: ba65ee3dcf51c9a4c1e329a7597b41c4226028f2fc76afd57c9e8185b20e04ef5563d47cdf017ec9aa122e148162408b16d544a167c6953b251a77fc83cebdfb languageName: node linkType: hard @@ -7478,12 +7505,12 @@ __metadata: languageName: unknown linkType: soft -"@swim-io/pool-math@npm:^0.39.0": - version: 0.39.0 - resolution: "@swim-io/pool-math@npm:0.39.0" +"@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: 68f86c9a614630192dd54a04a7f96765823e2fb2444a8807de172714570695f0ee0f8c82a676b682973dc64f54eca422b95828b7df9eacfa9b71d5cc5a819a63 + checksum: 6b3de0916194bcab630111aa1bc34d46d046c7d1960437a734b354a5f376e23e89ccfa228a077332ff33989373a9de6d883ff437849bd2cf61526d6db52d5843 languageName: node linkType: hard @@ -7613,15 +7640,15 @@ __metadata: languageName: unknown linkType: soft -"@swim-io/solana-contracts@npm:^0.39.0": - version: 0.39.0 - resolution: "@swim-io/solana-contracts@npm:0.39.0" +"@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: 891cea2e53838d8e6048054174c5685782cd5ef86c0d8e062e123fd26839bf03d0e15b9245f037de27fcca87352b695735e0fbde6a3abfa8dba156c5d8cb7575 + checksum: 5f78da938ff92fa660024ed2ad63345db68bee8b0f83f7e26cb86aa7e959a6e35d8afece1d11374274037b0821b4d55e1597f1ba40e1814a65e8faf75e75c041 languageName: node linkType: hard @@ -7752,6 +7779,28 @@ __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" @@ -7842,6 +7891,15 @@ __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" @@ -7908,18 +7966,18 @@ __metadata: "@storybook/manager-webpack4": ^6.5.10 "@storybook/node-logger": ^6.5.10 "@storybook/react": ^6.5.10 - "@swim-io/aptos": ^0.39.0 - "@swim-io/core": ^0.39.0 + "@swim-io/aptos": ^0.40.0 + "@swim-io/core": ^0.40.0 "@swim-io/eslint-config": "workspace:^" - "@swim-io/evm": ^0.39.0 - "@swim-io/evm-contracts": ^0.39.0 - "@swim-io/pool-math": ^0.39.0 - "@swim-io/solana": ^0.39.0 - "@swim-io/solana-contracts": ^0.39.0 - "@swim-io/token-projects": ^0.39.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/tsconfig": "workspace:^" - "@swim-io/utils": ^0.39.0 - "@swim-io/wormhole": ^0.39.0 + "@swim-io/utils": ^0.40.0 + "@swim-io/wormhole": ^0.40.0 "@testing-library/jest-dom": ^5.16.4 "@testing-library/react": ^12.1.5 "@testing-library/react-hooks": ^7.0.2 @@ -7992,6 +8050,15 @@ __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" @@ -8019,20 +8086,20 @@ __metadata: languageName: unknown linkType: soft -"@swim-io/wormhole@npm:^0.39.0": - version: 0.39.0 - resolution: "@swim-io/wormhole@npm:0.39.0" +"@swim-io/wormhole@npm:^0.40.0": + version: 0.40.0 + resolution: "@swim-io/wormhole@npm:0.40.0" dependencies: - "@swim-io/core": ^0.39.0 - "@swim-io/solana": ^0.39.0 - "@swim-io/utils": ^0.39.0 + "@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: 953ed6377f6614fffa9b7a335543e804598380092bbdf71dee4ab52a90eef4738f3249133f729e4e89888592e76bf5205195931444f73b3aeaad25519cb85055 + checksum: 12af304e4184bb1db601df1bc8a3c40f38f76ef64d18de5bfcc783b55680adc3090fb7beb08bcee15c9a22313a724fb6418713eaa628f0fad1be449865455f9e languageName: node linkType: hard From a50f83377a22d4648ed65a26d5b139921e8ba66b Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 20 Oct 2022 17:00:42 +0200 Subject: [PATCH 11/36] style(ui): Fix lint --- apps/ui/src/hooks/interaction/useAddInteractionMutation.ts | 2 +- .../interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts | 1 - apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts b/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts index ef79d5145..955894432 100644 --- a/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useAddInteractionMutation.ts @@ -1,4 +1,4 @@ -import { EvmTxType, isEvmEcosystemId } from "@swim-io/evm"; +import { isEvmEcosystemId } from "@swim-io/evm"; import { Pool__factory } from "@swim-io/evm-contracts"; import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana"; import { findOrThrow } from "@swim-io/utils"; diff --git a/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts b/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts index ea966d137..981822b5e 100644 --- a/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useCrossChainEvmToEvmSwapInteractionMutation.ts @@ -5,7 +5,6 @@ import { import { getTokenDetails } from "@swim-io/core"; import { EVM_ECOSYSTEMS, - EvmTxType, evmAddressToWormhole, isEvmEcosystemId, } from "@swim-io/evm"; diff --git a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts index 1ace25439..296fc253d 100644 --- a/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts +++ b/apps/ui/src/hooks/interaction/useFromSolanaTransferMutation.ts @@ -1,10 +1,8 @@ import { getEmitterAddressSolana } from "@certusone/wormhole-sdk"; import { Keypair } from "@solana/web3.js"; -import { EvmTxType } from "@swim-io/evm"; import type { SolanaClient, SolanaTx, TokenAccount } from "@swim-io/solana"; import { SOLANA_ECOSYSTEM_ID, - SolanaTxType, findTokenAccountForMint, parseSequenceFromLogSolana, } from "@swim-io/solana"; From 7b6af8fd29d8ca7ad3443c877647a778ccc16aad Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 17:40:12 +0200 Subject: [PATCH 12/36] chore(ui): Add tmp dir to .gitignore --- apps/ui/.gitignore | 1 + 1 file changed, 1 insertion(+) 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/ From f23e1f910dfb8e5b28459fe7c292257aadb487ab Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 17:41:00 +0200 Subject: [PATCH 13/36] chore(ui): Add ts-node and csv-parse dev dependencies --- apps/ui/package.json | 2 ++ yarn.lock | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/apps/ui/package.json b/apps/ui/package.json index a87d9fba5..4a848ea2d 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -113,6 +113,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 +130,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/yarn.lock b/yarn.lock index 7f1ecd27d..851b20be7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7991,6 +7991,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 +8026,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 @@ -14515,6 +14517,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" From 1076b5fe54170ce375fa4a6cf8e26cfe28a21a1c Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 17:42:01 +0200 Subject: [PATCH 14/36] feat(ui): Add Wormhole token data scraper --- apps/ui/scripts/processWormholeTokenData.ts | 182 ++++++++++++++++++++ apps/ui/scripts/scrapeWormholeTokenData.sh | 12 ++ apps/ui/tsconfig.json | 7 +- 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 apps/ui/scripts/processWormholeTokenData.ts create mode 100755 apps/ui/scripts/scrapeWormholeTokenData.sh diff --git a/apps/ui/scripts/processWormholeTokenData.ts b/apps/ui/scripts/processWormholeTokenData.ts new file mode 100644 index 000000000..132d86f27 --- /dev/null +++ b/apps/ui/scripts/processWormholeTokenData.ts @@ -0,0 +1,182 @@ +import { CHAINS, ChainId } from "@certusone/wormhole-sdk"; +import fs from "fs"; +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 processWrappedDetails = ( + wrappedDetails: WrappedDetails, +): readonly WormholeTokenDetails[] => + Object.entries(sourceToChainId).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 processedRecords: WormholeToken[] = []; + const parser = fs + .createReadStream(`${__dirname}/../tmp/wormholeTokenData.csv`) + .pipe( + parse({ + bom: true, + columns: true, + }), + ); + + for await (const record of parser) { + const processedRecord = processRecord(record); + if (Object.keys(processedRecord.wrappedDetails).length > 0) { + processedRecords.push(processedRecord); + } + } + + return new Promise((resolve) => { + fs.writeFile( + `${__dirname}/../src/config/wormholeTokens.json`, + JSON.stringify(processedRecords), + 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/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" + } + } } From 89fb3bf1bc16a79fca1256fb9cc71ea21c16b871 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 17:56:53 +0200 Subject: [PATCH 15/36] feat(ui): Add scraped Wormhole token data --- apps/ui/src/config/index.ts | 1 + apps/ui/src/config/wormholeTokens.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 apps/ui/src/config/wormholeTokens.json 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..520368be5 --- /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},{"chainId":3,"address":"terra1ht5sepn28z999jx33sdduuxm9acthad507jg9q","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},{"chainId":3,"address":"terra17h82zsq6q8x5tsgm5ugcx4gytw3axguvzt4pkc","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":3,"address":"terra190tqwgqx7s8qrknz6kckct7v607cu068gfujpk","decimals":8},{"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},{"chainId":3,"address":"terra1dkam9wd5yvaswv4yq3n2aqd4wm5j8n82qc0c7c","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":3,"address":"terra1e6mq63y64zcxz8xyu5van4tgkhemj3r86yvgu4","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":3,"address":"terra1hd9n65snaluvf7en0p4hqzse9eqecejz2k8rl5","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":3,"address":"terra1qvlpf2v0zmru3gtex40sqq02wxp39x3cjh359y","decimals":8},{"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},{"chainId":3,"address":"terra1nc6flp57m5hsr6y5y8aexzszy43ksr0drdr8rp","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},{"chainId":3,"address":"terra1apxgj5agkkfdm2tprwvykug0qtahxvfmugnhx2","decimals":8}]},{"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},{"chainId":3,"address":"terra1zmclyfepfmqvfqflu8r3lv6f75trmg05z7xq95","decimals":8}]},{"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":3,"address":"terra14tl83xcwqjy0ken9peu4pjjuu755lrry2uy25r","decimals":8},{"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},{"chainId":3,"address":"terra1jxypgnfa07j6w92wazzyskhreq2ey2a5crgt6z","decimals":8}]},{"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},{"chainId":3,"address":"terra12dfv3f0e6m22z6cnhfn3nxk2en3z3zeqy6ctym","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},{"chainId":3,"address":"terra15a9dr3a2a2lj5fclrw35xxg9yuxg0d908wpf2y","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":3,"address":"terra1mddcdx0ujx89f38gu7zspk2r2ffdl5enyz2u03","decimals":8},{"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":"SD","displayName":"Stader SD (Portal)","logo":"https://raw.githubusercontent.com/stader-labs/assets/main/eth/SD.png","coinGeckoId":"stader","nativeDetails":{"chainId":2,"address":"0x30D20208d987713f46DFD34EF128Bb16C404D10f","decimals":18},"wrappedDetails":[{"chainId":3,"address":"terra1ustvnmngueq0p4jd7gfnutgvdc6ujpsjhsjd02","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},{"chainId":3,"address":"terra1huku2lecfjhq9d00k5a8dh73gw7dwe6vvuf2dd","decimals":8}]},{"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},{"chainId":3,"address":"terra1csvuzlf92nyemu6tv25h0l79etpe8hz3h5vn4a","decimals":8}]},{"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},{"chainId":3,"address":"terra1wyxkuy5jq545fn7xfn3enpvs5zg9f9dghf6gxf","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":3,"address":"terra1pepwcav40nvj3kh60qqgrk8k07ydmc00xyat06","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":3,"address":"terra1ce06wkrdm4vl6t0hvc0g86rsy27pu8yadg3dva","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":3,"address":"terra1aa7upykmmqqc63l924l5qfap8mrmx5rfdm0v55","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":"bETH","displayName":"Lido bETH (Portal)","logo":"https://static.lido.fi/bETH/bETH.png","coinGeckoId":"anchor-beth-token","nativeDetails":{"chainId":2,"address":"0x707f9118e33a9b8998bea41dd0d46f38bb963fc8","decimals":18},"wrappedDetails":[{"chainId":3,"address":"terra1u5szg038ur9kzuular3cae8hq6q5rk5u27tuvz","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},{"chainId":3,"address":"terra1fpfn2kkr8mv390wx4dtpfk3vkjx9ch3thvykl3","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},{"chainId":3,"address":"terra1w7ywr6waxtjuvn5svk5wqydqpjj0q9ps7qct4d","decimals":8}]},{"symbol":"wstETH","displayName":"Lido wstETH (Portal)","logo":"https://static.lido.fi/wstETH/wstETH.png","coinGeckoId":"wrapped-steth","nativeDetails":{"chainId":2,"address":"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0","decimals":18},"wrappedDetails":[{"chainId":3,"address":"terra133chr09wu8sakfte5v7vd8qzq9vghtkv4tn0ur","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":3,"address":"terra1cetg5wruw2wsdjp7j46rj44xdel00z006e9yg8","decimals":8},{"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":3,"address":"terra1skjr69exm6v8zellgjpaa2emhwutrk5a6dz7dd","decimals":8},{"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":3,"address":"terra1xvqlpjl2dxyel9qrp6qvtrg04xe3jh9cyxc6av","decimals":8},{"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":3,"address":"terra1yljlrxvkar0c6ujpvf8g57m5rpcwl7r032zyvu","decimals":8},{"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":3,"address":"terra1vlqeghv5mt5udh96kt5zxlh2wkh8q4kewkr0dd","decimals":8},{"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":3,"address":"terra1dtqlfecglk47yplfrtwjzyagkgcqqngd5lgjp8","decimals":8},{"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":3,"address":"terra1kkyyh7vganlpkj0gkc2rfmhy858ma4rtwywe3x","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":3,"address":"terra1hj8de24c3yqvcsv9r8chr03fzwsak3hgd8gv3m","decimals":8},{"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":3,"address":"terra1pvel56a2hs93yd429pzv9zp5aptcjg5ulhkz7w","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":3,"address":"terra1eqvq3thjhye7anv6f6mhxpjhyvww8zjvqcdgjx","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 From affb56b9d5f5b0a1f02b3b765acb5cae8aeebd89 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 18:04:40 +0200 Subject: [PATCH 16/36] refactor(core): Adjust InitiateWormholeTransferParams to take sourceAddress --- packages/core/src/client.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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; } From 8ae50a2759ee91e1e30447badb6f99c859c5fba6 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 18:08:37 +0200 Subject: [PATCH 17/36] refactor(evm): Update for new InitiateWormholeTransferParams --- packages/evm/src/client.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) 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, From 181ae87b32706f6faabcb2699c030214481165f2 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 18:09:13 +0200 Subject: [PATCH 18/36] refactor(solana): Update for new InitiateWormholeTransferParams --- packages/solana/src/client.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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, From ce5a83fff8c97c8e8acdc70ac3ed830ff264b6d5 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 18:11:38 +0200 Subject: [PATCH 19/36] WIP: revert --- apps/ui/package.json | 20 +++--- yarn.lock | 160 ++++--------------------------------------- 2 files changed, 24 insertions(+), 156 deletions(-) diff --git a/apps/ui/package.json b/apps/ui/package.json index 4a848ea2d..8c9b1eeff 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -50,16 +50,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", diff --git a/yarn.lock b/yarn.lock index 851b20be7..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 @@ -8052,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" @@ -8088,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: From 1dede9c637385f86eab21ec6be7c6b5474c660d5 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 18:11:53 +0200 Subject: [PATCH 20/36] refactor(ui): Update for new InitiateWormholeTransferParams --- .../hooks/interaction/useFromSolanaTransferMutation.ts | 6 ++---- .../hooks/interaction/useToSolanaTransferMutation.ts | 2 +- apps/ui/src/models/wormhole/getWrappedTokenInfo.ts | 10 +--------- 3 files changed, 4 insertions(+), 14 deletions(-) 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/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, }; }; From 2d6a15d521f745d3e14c8405b544c6a4f15f87bf Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 21:08:13 +0200 Subject: [PATCH 21/36] feat(ui): Add Wormhole transfer models --- apps/ui/src/models/wormhole/index.ts | 1 + apps/ui/src/models/wormhole/transfer.ts | 57 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 apps/ui/src/models/wormhole/transfer.ts 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, + }; +}; From 0585838721f12a3056a42da772721d0776fe8c68 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 21:18:13 +0200 Subject: [PATCH 22/36] feat(ui): Add Wormhole transfer hooks --- apps/ui/src/hooks/index.ts | 1 + apps/ui/src/hooks/wormhole/index.ts | 1 + .../wormhole/useTransferEvmToEvmMutation.ts | 141 ++++++++++++++++ .../useTransferEvmToSolanaMutation.ts | 155 ++++++++++++++++++ .../useTransferSolanaToEvmMutation.ts | 128 +++++++++++++++ .../src/hooks/wormhole/useWormholeTransfer.ts | 42 +++++ 6 files changed, 468 insertions(+) create mode 100644 apps/ui/src/hooks/wormhole/index.ts create mode 100644 apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts create mode 100644 apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts create mode 100644 apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts create mode 100644 apps/ui/src/hooks/wormhole/useWormholeTransfer.ts 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/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..5323115a2 --- /dev/null +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts @@ -0,0 +1,141 @@ +import { + getEmitterAddressEth, + parseSequenceFromLogEth, +} from "@certusone/wormhole-sdk"; +import { 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 { approvalResponses, transferResponse } = + await sourceClient.initiateWormholeTransfer({ + 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, + ), + }); + + approvalResponses.forEach((response) => + onTxResult({ + chainId: sourceDetails.chainId, + txId: response.hash, + }), + ); + + const transferTxReceipt = await sourceClient.getTxReceiptOrThrow( + transferResponse, + ); + onTxResult({ + chainId: sourceDetails.chainId, + txId: transferTxReceipt.transactionHash, + }); + const sequence = parseSequenceFromLogEth( + transferTxReceipt, + 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 redeemResponse = await targetClient.completeWormholeTransfer({ + interactionId, + vaa, + wallet: evmWallet, + }); + if (redeemResponse === null) { + throw new Error( + `Transaction not found: (unlock/mint on ${targetEcosystemId})`, + ); + } + const evmReceipt = await targetClient.getTxReceiptOrThrow(redeemResponse); + onTxResult({ + chainId: targetDetails.chainId, + txId: evmReceipt.transactionHash, + }); + }, + ); +}; diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts new file mode 100644 index 000000000..1aaa05e7f --- /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 { 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, useSplTokenAccountsQuery } 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 = [] } = useSplTokenAccountsQuery(); + + 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 { approvalResponses, transferResponse } = + await evmClient.initiateWormholeTransfer({ + 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, + ), + }); + + approvalResponses.forEach((response) => + onTxResult({ + chainId: sourceDetails.chainId, + txId: response.hash, + }), + ); + + const transferTxReceipt = await evmClient.getTxReceiptOrThrow( + transferResponse, + ); + onTxResult({ + chainId: sourceDetails.chainId, + txId: transferTxReceipt.transactionHash, + }); + const sequence = parseSequenceFromLogEth( + transferTxReceipt, + 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 unlockSplTokenTxIdsGenerator = + solanaClient.generateCompleteWormholeTransferTxIds({ + interactionId, + vaa, + wallet: solanaWallet, + auxiliarySigner, + }); + + for await (const txId of unlockSplTokenTxIdsGenerator) { + onTxResult({ + chainId: targetDetails.chainId, + txId, + }); + } + }, + ); +}; diff --git a/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts new file mode 100644 index 000000000..87f4d8e70 --- /dev/null +++ b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts @@ -0,0 +1,128 @@ +import { getEmitterAddressSolana } from "@certusone/wormhole-sdk"; +import { Keypair } from "@solana/web3.js"; +import { isEvmEcosystemId } from "@swim-io/evm"; +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 transferSplTokenTxId = await solanaClient.initiateWormholeTransfer({ + 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, + ), + }); + onTxResult({ + chainId: sourceDetails.chainId, + txId: transferSplTokenTxId, + }); + + const parsedTx = await solanaClient.getParsedTx(transferSplTokenTxId); + const sequence = parseSequenceFromLogSolana(parsedTx); + 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 redeemResponse = await evmClient.completeWormholeTransfer({ + interactionId, + vaa, + wallet: evmWallet, + }); + if (redeemResponse === null) { + throw new Error( + `Transaction not found: (unlock/mint on ${evmChain.ecosystem})`, + ); + } + const evmReceipt = await evmClient.getTxReceiptOrThrow(redeemResponse); + onTxResult({ + chainId: targetDetails.chainId, + txId: evmReceipt.transactionHash, + }); + }, + ); +}; 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`, + ); + } + }); +}; From 65a2fc5db9c8811e3ba3c2b7f422e246b6910b5e Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 21:18:27 +0200 Subject: [PATCH 23/36] feat(ui): Add basic Wormhole form --- apps/ui/src/components/WormholeForm.tsx | 232 ++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 apps/ui/src/components/WormholeForm.tsx diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx new file mode 100644 index 000000000..b036e04fa --- /dev/null +++ b/apps/ui/src/components/WormholeForm.tsx @@ -0,0 +1,232 @@ +import type { ChainId } from "@certusone/wormhole-sdk"; +import { CHAIN_ID_TO_NAME } from "@certusone/wormhole-sdk"; +import type { EuiSelectOption } from "@elastic/eui"; +import { + EuiButton, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, +} from "@elastic/eui"; +import { findOrThrow } from "@swim-io/utils"; +import Decimal from "decimal.js"; +import type { ReactElement } from "react"; +import { useEffect, useMemo, useState } from "react"; + +import { wormholeTokens as rawWormholeTokens } from "../config"; +import { useWormholeTransfer } from "../hooks"; +import type { TxResult, WormholeToken, WormholeTokenDetails } from "../models"; +import { generateId } from "../models"; + +import { EuiFieldIntlNumber } from "./EuiFieldIntlNumber"; + +const getDetailsByChainId = ( + token: WormholeToken, + chainId: ChainId, +): WormholeTokenDetails => + findOrThrow( + [token.nativeDetails, ...token.wrappedDetails], + (details) => details.chainId === chainId, + ); + +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 handleTxResult = (txResult: TxResult): void => { + setTxResults((previousResults) => [...previousResults, txResult]); + }; + + const handleSubmit = () => { + (async (): Promise => { + setTxResults([]); + const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); + const targetDetails = getDetailsByChainId(currentToken, targetChainId); + 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}
{"Target Chain"}{targetChainId}
{"Loading?"}{isLoading.toString()}
+ {txResults.length > 0 && ( + <> +

Tx results

+ + + + + + {txResults.map(({ chainId, txId }) => ( + + + + + ))} +
Chain IDTx 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"} + + +
+ ); +}; From 34c5150e5f6a0eb0e23921fbf44032d857ab9b94 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 13 Oct 2022 21:18:39 +0200 Subject: [PATCH 24/36] feat(ui): Add Wormhole page --- apps/ui/src/App.tsx | 2 ++ apps/ui/src/pages/WormholePage.scss | 10 ++++++++ apps/ui/src/pages/WormholePage.tsx | 38 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 apps/ui/src/pages/WormholePage.scss create mode 100644 apps/ui/src/pages/WormholePage.tsx 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/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; From b3672d9b7e22d78e0c88804577640133f082f799 Mon Sep 17 00:00:00 2001 From: wormat Date: Fri, 14 Oct 2022 16:40:43 +0200 Subject: [PATCH 25/36] chore(ui): Add npm script for Wormhole token scraper --- apps/ui/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ui/package.json b/apps/ui/package.json index 8c9b1eeff..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", From 723c8be05f89e00f3c77632576c7086f6b634d93 Mon Sep 17 00:00:00 2001 From: wormat Date: Fri, 14 Oct 2022 16:46:41 +0200 Subject: [PATCH 26/36] feat(ui): Restrict Wormhole tokens to min 2 Solana/EVM chains --- apps/ui/scripts/processWormholeTokenData.ts | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/ui/scripts/processWormholeTokenData.ts b/apps/ui/scripts/processWormholeTokenData.ts index 132d86f27..e9c8d09f5 100644 --- a/apps/ui/scripts/processWormholeTokenData.ts +++ b/apps/ui/scripts/processWormholeTokenData.ts @@ -1,4 +1,4 @@ -import { CHAINS, ChainId } from "@certusone/wormhole-sdk"; +import { CHAINS, ChainId, isEVMChain } from "@certusone/wormhole-sdk"; import fs from "fs"; import { parse } from "csv-parse"; @@ -103,10 +103,26 @@ const sourceToChainId: Record = { 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(sourceToChainId).reduce( + Object.entries(supportedSourceToChainId).reduce( (accumulator: readonly WormholeTokenDetails[], [source, chainId]) => { const address: string = wrappedDetails[`${source}Address` as keyof WrappedDetails]; @@ -162,7 +178,11 @@ const main = async () => { for await (const record of parser) { const processedRecord = processRecord(record); - if (Object.keys(processedRecord.wrappedDetails).length > 0) { + const supportedChains = [ + processedRecord.nativeDetails, + ...processedRecord.wrappedDetails, + ].filter((details) => isChainSupported(details.chainId)); + if (supportedChains.length >= 2) { processedRecords.push(processedRecord); } } From 6e6e46b7573dfe3b23e0040d98cdfe672911558d Mon Sep 17 00:00:00 2001 From: wormat Date: Fri, 14 Oct 2022 16:48:11 +0200 Subject: [PATCH 27/36] chore(ui): Update Wormhole token list --- apps/ui/src/config/wormholeTokens.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ui/src/config/wormholeTokens.json b/apps/ui/src/config/wormholeTokens.json index 520368be5..208961037 100644 --- a/apps/ui/src/config/wormholeTokens.json +++ b/apps/ui/src/config/wormholeTokens.json @@ -1 +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},{"chainId":3,"address":"terra1ht5sepn28z999jx33sdduuxm9acthad507jg9q","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},{"chainId":3,"address":"terra17h82zsq6q8x5tsgm5ugcx4gytw3axguvzt4pkc","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":3,"address":"terra190tqwgqx7s8qrknz6kckct7v607cu068gfujpk","decimals":8},{"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},{"chainId":3,"address":"terra1dkam9wd5yvaswv4yq3n2aqd4wm5j8n82qc0c7c","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":3,"address":"terra1e6mq63y64zcxz8xyu5van4tgkhemj3r86yvgu4","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":3,"address":"terra1hd9n65snaluvf7en0p4hqzse9eqecejz2k8rl5","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":3,"address":"terra1qvlpf2v0zmru3gtex40sqq02wxp39x3cjh359y","decimals":8},{"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},{"chainId":3,"address":"terra1nc6flp57m5hsr6y5y8aexzszy43ksr0drdr8rp","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},{"chainId":3,"address":"terra1apxgj5agkkfdm2tprwvykug0qtahxvfmugnhx2","decimals":8}]},{"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},{"chainId":3,"address":"terra1zmclyfepfmqvfqflu8r3lv6f75trmg05z7xq95","decimals":8}]},{"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":3,"address":"terra14tl83xcwqjy0ken9peu4pjjuu755lrry2uy25r","decimals":8},{"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},{"chainId":3,"address":"terra1jxypgnfa07j6w92wazzyskhreq2ey2a5crgt6z","decimals":8}]},{"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},{"chainId":3,"address":"terra12dfv3f0e6m22z6cnhfn3nxk2en3z3zeqy6ctym","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},{"chainId":3,"address":"terra15a9dr3a2a2lj5fclrw35xxg9yuxg0d908wpf2y","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":3,"address":"terra1mddcdx0ujx89f38gu7zspk2r2ffdl5enyz2u03","decimals":8},{"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":"SD","displayName":"Stader SD (Portal)","logo":"https://raw.githubusercontent.com/stader-labs/assets/main/eth/SD.png","coinGeckoId":"stader","nativeDetails":{"chainId":2,"address":"0x30D20208d987713f46DFD34EF128Bb16C404D10f","decimals":18},"wrappedDetails":[{"chainId":3,"address":"terra1ustvnmngueq0p4jd7gfnutgvdc6ujpsjhsjd02","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},{"chainId":3,"address":"terra1huku2lecfjhq9d00k5a8dh73gw7dwe6vvuf2dd","decimals":8}]},{"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},{"chainId":3,"address":"terra1csvuzlf92nyemu6tv25h0l79etpe8hz3h5vn4a","decimals":8}]},{"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},{"chainId":3,"address":"terra1wyxkuy5jq545fn7xfn3enpvs5zg9f9dghf6gxf","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":3,"address":"terra1pepwcav40nvj3kh60qqgrk8k07ydmc00xyat06","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":3,"address":"terra1ce06wkrdm4vl6t0hvc0g86rsy27pu8yadg3dva","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":3,"address":"terra1aa7upykmmqqc63l924l5qfap8mrmx5rfdm0v55","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":"bETH","displayName":"Lido bETH (Portal)","logo":"https://static.lido.fi/bETH/bETH.png","coinGeckoId":"anchor-beth-token","nativeDetails":{"chainId":2,"address":"0x707f9118e33a9b8998bea41dd0d46f38bb963fc8","decimals":18},"wrappedDetails":[{"chainId":3,"address":"terra1u5szg038ur9kzuular3cae8hq6q5rk5u27tuvz","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},{"chainId":3,"address":"terra1fpfn2kkr8mv390wx4dtpfk3vkjx9ch3thvykl3","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},{"chainId":3,"address":"terra1w7ywr6waxtjuvn5svk5wqydqpjj0q9ps7qct4d","decimals":8}]},{"symbol":"wstETH","displayName":"Lido wstETH (Portal)","logo":"https://static.lido.fi/wstETH/wstETH.png","coinGeckoId":"wrapped-steth","nativeDetails":{"chainId":2,"address":"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0","decimals":18},"wrappedDetails":[{"chainId":3,"address":"terra133chr09wu8sakfte5v7vd8qzq9vghtkv4tn0ur","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":3,"address":"terra1cetg5wruw2wsdjp7j46rj44xdel00z006e9yg8","decimals":8},{"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":3,"address":"terra1skjr69exm6v8zellgjpaa2emhwutrk5a6dz7dd","decimals":8},{"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":3,"address":"terra1xvqlpjl2dxyel9qrp6qvtrg04xe3jh9cyxc6av","decimals":8},{"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":3,"address":"terra1yljlrxvkar0c6ujpvf8g57m5rpcwl7r032zyvu","decimals":8},{"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":3,"address":"terra1vlqeghv5mt5udh96kt5zxlh2wkh8q4kewkr0dd","decimals":8},{"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":3,"address":"terra1dtqlfecglk47yplfrtwjzyagkgcqqngd5lgjp8","decimals":8},{"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":3,"address":"terra1kkyyh7vganlpkj0gkc2rfmhy858ma4rtwywe3x","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":3,"address":"terra1hj8de24c3yqvcsv9r8chr03fzwsak3hgd8gv3m","decimals":8},{"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":3,"address":"terra1pvel56a2hs93yd429pzv9zp5aptcjg5ulhkz7w","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":3,"address":"terra1eqvq3thjhye7anv6f6mhxpjhyvww8zjvqcdgjx","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 +[{"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 From 0514be4c2f6f816e730fb97207d344135b21b852 Mon Sep 17 00:00:00 2001 From: wormat Date: Fri, 14 Oct 2022 17:32:48 +0200 Subject: [PATCH 28/36] chore(ui): Update TS/ESLint configs for scripts --- apps/ui/.eslintrc.cjs | 10 +++++++++- apps/ui/scripts/tsconfig.json | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 apps/ui/scripts/tsconfig.json 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/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" + } + } +} From 01e6133dae1bd60447021f91edaad4f07440c5ee Mon Sep 17 00:00:00 2001 From: wormat Date: Fri, 14 Oct 2022 17:40:48 +0200 Subject: [PATCH 29/36] style(ui): Fix lint issues --- apps/ui/scripts/processWormholeTokenData.ts | 10 +++++++--- apps/ui/src/components/WormholeForm.tsx | 8 ++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/ui/scripts/processWormholeTokenData.ts b/apps/ui/scripts/processWormholeTokenData.ts index e9c8d09f5..0ba74925f 100644 --- a/apps/ui/scripts/processWormholeTokenData.ts +++ b/apps/ui/scripts/processWormholeTokenData.ts @@ -1,5 +1,7 @@ -import { CHAINS, ChainId, isEVMChain } from "@certusone/wormhole-sdk"; 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 = @@ -166,7 +168,6 @@ const processRecord = ({ }); const main = async () => { - const processedRecords: WormholeToken[] = []; const parser = fs .createReadStream(`${__dirname}/../tmp/wormholeTokenData.csv`) .pipe( @@ -176,13 +177,16 @@ const main = async () => { }), ); + // eslint-disable-next-line functional/prefer-readonly-type + const processedRecords: WormholeToken[] = []; for await (const record of parser) { - const processedRecord = processRecord(record); + 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); } } diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx index b036e04fa..05d26f949 100644 --- a/apps/ui/src/components/WormholeForm.tsx +++ b/apps/ui/src/components/WormholeForm.tsx @@ -149,14 +149,14 @@ export const WormholeForm = (): ReactElement => { {txResults.length > 0 && ( <> -

Tx results

+

{"Tx results"}

- - + + {txResults.map(({ chainId, txId }) => ( - + From f473c2fe584799064d61303943a9140bd30fb768 Mon Sep 17 00:00:00 2001 From: wormat Date: Fri, 14 Oct 2022 17:41:39 +0200 Subject: [PATCH 30/36] fix(ui): Catch errors properly in scraper script --- apps/ui/scripts/processWormholeTokenData.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ui/scripts/processWormholeTokenData.ts b/apps/ui/scripts/processWormholeTokenData.ts index 0ba74925f..36a8c8c4e 100644 --- a/apps/ui/scripts/processWormholeTokenData.ts +++ b/apps/ui/scripts/processWormholeTokenData.ts @@ -191,11 +191,11 @@ const main = async () => { } } - return new Promise((resolve) => { + return new Promise((resolve, reject) => { fs.writeFile( `${__dirname}/../src/config/wormholeTokens.json`, JSON.stringify(processedRecords), - resolve, + (err) => (err ? reject(err) : resolve()), ); }); }; From 3b1cf9c6dd6568d4ef5676c8fe89cd031db1b92d Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 20 Oct 2022 16:38:41 +0200 Subject: [PATCH 31/36] refactor(ui): Update wormhole hooks for SDK client changes --- .../hooks/wormhole/useTransferEvmToEvmMutation.ts | 12 +++++------- .../hooks/wormhole/useTransferEvmToSolanaMutation.ts | 8 +++----- .../hooks/wormhole/useTransferSolanaToEvmMutation.ts | 8 ++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts index 5323115a2..c8f002136 100644 --- a/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts @@ -98,15 +98,13 @@ export const useTransferEvmToEvmMutation = () => { }), ); - const transferTxReceipt = await sourceClient.getTxReceiptOrThrow( - transferResponse, - ); + const transferTx = await sourceClient.getTx(transferResponse); onTxResult({ chainId: sourceDetails.chainId, - txId: transferTxReceipt.transactionHash, + txId: transferTx.id, }); const sequence = parseSequenceFromLogEth( - transferTxReceipt, + transferTx.receipt, sourceChain.wormhole.bridge, ); const retries = getWormholeRetries(sourceDetails.chainId); @@ -131,10 +129,10 @@ export const useTransferEvmToEvmMutation = () => { `Transaction not found: (unlock/mint on ${targetEcosystemId})`, ); } - const evmReceipt = await targetClient.getTxReceiptOrThrow(redeemResponse); + const redeemTx = await targetClient.getTx(redeemResponse); onTxResult({ chainId: targetDetails.chainId, - txId: evmReceipt.transactionHash, + txId: redeemTx.id, }); }, ); diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts index 1aaa05e7f..5675638f3 100644 --- a/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts @@ -113,15 +113,13 @@ export const useTransferEvmToSolanaMutation = () => { }), ); - const transferTxReceipt = await evmClient.getTxReceiptOrThrow( - transferResponse, - ); + const transferTx = await evmClient.getTx(transferResponse); onTxResult({ chainId: sourceDetails.chainId, - txId: transferTxReceipt.transactionHash, + txId: transferTx.id, }); const sequence = parseSequenceFromLogEth( - transferTxReceipt, + transferTx.receipt, evmChain.wormhole.bridge, ); diff --git a/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts index 87f4d8e70..f1482b9a3 100644 --- a/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts +++ b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts @@ -92,8 +92,8 @@ export const useTransferSolanaToEvmMutation = () => { txId: transferSplTokenTxId, }); - const parsedTx = await solanaClient.getParsedTx(transferSplTokenTxId); - const sequence = parseSequenceFromLogSolana(parsedTx); + const solanaTx = await solanaClient.getTx(transferSplTokenTxId); + const sequence = parseSequenceFromLogSolana(solanaTx.parsedTx); const emitterAddress = await getEmitterAddressSolana( solanaChain.wormhole.portal, ); @@ -118,10 +118,10 @@ export const useTransferSolanaToEvmMutation = () => { `Transaction not found: (unlock/mint on ${evmChain.ecosystem})`, ); } - const evmReceipt = await evmClient.getTxReceiptOrThrow(redeemResponse); + const evmTx = await evmClient.getTx(redeemResponse); onTxResult({ chainId: targetDetails.chainId, - txId: evmReceipt.transactionHash, + txId: evmTx.id, }); }, ); From b7351496e49dbee6ba6a779de1368d427d1f0256 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 20 Oct 2022 18:09:35 +0200 Subject: [PATCH 32/36] feat(ui): Add balance queries to basic Wormhole form --- apps/ui/src/components/WormholeForm.tsx | 97 +++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx index 05d26f949..add03a933 100644 --- a/apps/ui/src/components/WormholeForm.tsx +++ b/apps/ui/src/components/WormholeForm.tsx @@ -1,5 +1,26 @@ -import type { ChainId } from "@certusone/wormhole-sdk"; -import { CHAIN_ID_TO_NAME } from "@certusone/wormhole-sdk"; +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, @@ -8,18 +29,43 @@ import { 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 { 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 { useWormholeTransfer } from "../hooks"; +import { useEvmWallet, useSplUserBalance, 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, @@ -29,6 +75,37 @@ const getDetailsByChainId = ( (details) => details.chainId === chainId, ); +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( @@ -78,6 +155,14 @@ export const WormholeForm = (): ReactElement => { const { mutateAsync: transfer, isLoading } = useWormholeTransfer(); + const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); + const targetDetails = getDetailsByChainId(currentToken, targetChainId); + const splBalance = useSplUserBalance( + 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]); }; @@ -85,8 +170,6 @@ export const WormholeForm = (): ReactElement => { const handleSubmit = () => { (async (): Promise => { setTxResults([]); - const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); - const targetDetails = getDetailsByChainId(currentToken, targetChainId); await transfer({ interactionId: generateId(), value: inputAmount, @@ -138,6 +221,10 @@ export const WormholeForm = (): ReactElement => { + + + + From dfdb38ee24e3d05da45db378e9fc5dfe9f401af5 Mon Sep 17 00:00:00 2001 From: wormat Date: Thu, 20 Oct 2022 19:45:49 +0200 Subject: [PATCH 33/36] refactor(ui): Update Wormhole form/hooks for SDK client changes --- apps/ui/src/components/WormholeForm.tsx | 8 +- .../wormhole/useTransferEvmToEvmMutation.ts | 66 ++++++++-------- .../useTransferEvmToSolanaMutation.ts | 78 ++++++++++--------- .../useTransferSolanaToEvmMutation.ts | 39 ++++++---- 4 files changed, 100 insertions(+), 91 deletions(-) diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx index add03a933..d3f106e7f 100644 --- a/apps/ui/src/components/WormholeForm.tsx +++ b/apps/ui/src/components/WormholeForm.tsx @@ -40,7 +40,11 @@ import type { UseQueryResult } from "react-query"; import { useQuery } from "react-query"; import { wormholeTokens as rawWormholeTokens } from "../config"; -import { useEvmWallet, useSplUserBalance, useWormholeTransfer } from "../hooks"; +import { + useEvmWallet, + useUserSolanaTokenBalance, + useWormholeTransfer, +} from "../hooks"; import type { TxResult, WormholeToken, WormholeTokenDetails } from "../models"; import { generateId } from "../models"; @@ -157,7 +161,7 @@ export const WormholeForm = (): ReactElement => { const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); const targetDetails = getDetailsByChainId(currentToken, targetChainId); - const splBalance = useSplUserBalance( + const splBalance = useUserSolanaTokenBalance( sourceChainId === CHAIN_ID_SOLANA ? sourceDetails : null, { enabled: sourceChainId === CHAIN_ID_SOLANA }, ); diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts index c8f002136..1e2765be8 100644 --- a/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToEvmMutation.ts @@ -2,7 +2,8 @@ import { getEmitterAddressEth, parseSequenceFromLogEth, } from "@certusone/wormhole-sdk"; -import { isEvmEcosystemId } from "@swim-io/evm"; +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"; @@ -77,34 +78,35 @@ export const useTransferEvmToEvmMutation = () => { await evmWallet.switchNetwork(sourceChain.chainId); // Process transfer if transfer txId does not exist - const { approvalResponses, transferResponse } = - await sourceClient.initiateWormholeTransfer({ - 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, - ), - }); + 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, + ), + }); - approvalResponses.forEach((response) => + let transferTx: EvmTx | null = null; + for await (const result of sourceTxGenerator) { + if (result.type === EvmTxType.PortalTransferTokens) { + transferTx = result.tx; + } onTxResult({ chainId: sourceDetails.chainId, - txId: response.hash, - }), - ); + txId: result.tx.id, + }); + } - const transferTx = await sourceClient.getTx(transferResponse); - onTxResult({ - chainId: sourceDetails.chainId, - txId: transferTx.id, - }); + if (transferTx === null) { + throw new Error("Missing source transaction"); + } const sequence = parseSequenceFromLogEth( - transferTx.receipt, + transferTx.original, sourceChain.wormhole.bridge, ); const retries = getWormholeRetries(sourceDetails.chainId); @@ -119,21 +121,17 @@ export const useTransferEvmToEvmMutation = () => { ); await evmWallet.switchNetwork(targetChain.chainId); - const redeemResponse = await targetClient.completeWormholeTransfer({ + const targetTxGenerator = targetClient.generateCompletePortalTransferTxs({ interactionId, vaa, wallet: evmWallet, }); - if (redeemResponse === null) { - throw new Error( - `Transaction not found: (unlock/mint on ${targetEcosystemId})`, - ); + for await (const result of targetTxGenerator) { + onTxResult({ + chainId: targetDetails.chainId, + txId: result.tx.id, + }); } - const redeemTx = await targetClient.getTx(redeemResponse); - onTxResult({ - chainId: targetDetails.chainId, - txId: redeemTx.id, - }); }, ); }; diff --git a/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts index 5675638f3..240791586 100644 --- a/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts +++ b/apps/ui/src/hooks/wormhole/useTransferEvmToSolanaMutation.ts @@ -3,7 +3,8 @@ import { parseSequenceFromLogEth, } from "@certusone/wormhole-sdk"; import { Keypair } from "@solana/web3.js"; -import { isEvmEcosystemId } from "@swim-io/evm"; +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"; @@ -22,7 +23,7 @@ import { } from "../../models"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useSplTokenAccountsQuery } from "../solana"; +import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; export const useTransferEvmToSolanaMutation = () => { const queryClient = useQueryClient(); @@ -32,7 +33,7 @@ export const useTransferEvmToSolanaMutation = () => { const solanaClient = useSolanaClient(); const wallets = useWallets(); const solanaWallet = wallets[SOLANA_ECOSYSTEM_ID].wallet; - const { data: splTokenAccounts = [] } = useSplTokenAccountsQuery(); + const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); return useMutation( async ({ @@ -89,37 +90,39 @@ export const useTransferEvmToSolanaMutation = () => { await evmWallet.switchNetwork(evmChain.chainId); // Process transfer if transfer txId does not exist - const { approvalResponses, transferResponse } = - await evmClient.initiateWormholeTransfer({ - 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, - ), - }); + 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, + ), + }); - approvalResponses.forEach((response) => + let evmTransferTx: EvmTx | null = null; + for await (const result of evmTxGenerator) { + if (result.type === EvmTxType.PortalTransferTokens) { + evmTransferTx = result.tx; + } onTxResult({ chainId: sourceDetails.chainId, - txId: response.hash, - }), - ); + txId: result.tx.id, + }); + } + + if (evmTransferTx === null) { + throw new Error("Missing EVM transaction"); + } - const transferTx = await evmClient.getTx(transferResponse); - onTxResult({ - chainId: sourceDetails.chainId, - txId: transferTx.id, - }); const sequence = parseSequenceFromLogEth( - transferTx.receipt, + evmTransferTx.original, evmChain.wormhole.bridge, ); @@ -134,18 +137,17 @@ export const useTransferEvmToSolanaMutation = () => { undefined, retries, ); - const unlockSplTokenTxIdsGenerator = - solanaClient.generateCompleteWormholeTransferTxIds({ - interactionId, - vaa, - wallet: solanaWallet, - auxiliarySigner, - }); + const splTxGenerator = solanaClient.generateCompletePortalTransferTxs({ + interactionId, + vaa, + wallet: solanaWallet, + auxiliarySigner, + }); - for await (const txId of unlockSplTokenTxIdsGenerator) { + for await (const result of splTxGenerator) { onTxResult({ chainId: targetDetails.chainId, - txId, + txId: result.tx.id, }); } }, diff --git a/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts index f1482b9a3..06d7c222b 100644 --- a/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts +++ b/apps/ui/src/hooks/wormhole/useTransferSolanaToEvmMutation.ts @@ -1,6 +1,7 @@ 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, @@ -74,7 +75,7 @@ export const useTransferSolanaToEvmMutation = () => { } const auxiliarySigner = Keypair.generate(); - const transferSplTokenTxId = await solanaClient.initiateWormholeTransfer({ + const solanaTxGenerator = solanaClient.generateInitiatePortalTransferTxs({ atomicAmount: humanToAtomic(value, sourceDetails.decimals).toString(), interactionId, sourceAddress: sourceDetails.address, @@ -87,13 +88,20 @@ export const useTransferSolanaToEvmMutation = () => { nativeDetails, ), }); - onTxResult({ - chainId: sourceDetails.chainId, - txId: transferSplTokenTxId, - }); - const solanaTx = await solanaClient.getTx(transferSplTokenTxId); - const sequence = parseSequenceFromLogSolana(solanaTx.parsedTx); + 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, ); @@ -108,21 +116,18 @@ export const useTransferSolanaToEvmMutation = () => { retries, ); await evmWallet.switchNetwork(evmChain.chainId); - const redeemResponse = await evmClient.completeWormholeTransfer({ + const evmTxGenerator = evmClient.generateCompletePortalTransferTxs({ interactionId, vaa, wallet: evmWallet, }); - if (redeemResponse === null) { - throw new Error( - `Transaction not found: (unlock/mint on ${evmChain.ecosystem})`, - ); + + for await (const result of evmTxGenerator) { + onTxResult({ + chainId: targetDetails.chainId, + txId: result.tx.id, + }); } - const evmTx = await evmClient.getTx(redeemResponse); - onTxResult({ - chainId: targetDetails.chainId, - txId: evmTx.id, - }); }, ); }; From 5a3a28830478c060cc78abf3d358ffdcb979e3c5 Mon Sep 17 00:00:00 2001 From: cypher Date: Wed, 19 Oct 2022 18:48:27 +0200 Subject: [PATCH 34/36] feat: wormhole form --- apps/ui/src/components/Layout/Layout.tsx | 3 + apps/ui/src/components/TokenIcon.tsx | 26 +- apps/ui/src/components/TokenSearchModal.scss | 2 +- apps/ui/src/components/WormholeForm.tsx | 323 --- .../WormholeForm/WormholeChainSelect.tsx | 79 + .../components/WormholeForm/WormholeForm.scss | 21 + .../components/WormholeForm/WormholeForm.tsx | 297 ++ .../WormholeForm/WormholeTokenModal.scss | 40 + .../WormholeForm/WormholeTokenModal.tsx | 92 + .../WormholeForm/WormholeTokenSelect.tsx | 45 + apps/ui/src/components/WormholeForm/index.ts | 1 + apps/ui/src/config/wormhole.ts | 152 + apps/ui/src/config/wormholeTokens.json | 2532 ++++++++++++++++- apps/ui/src/images/ecosystems/algorand.svg | 1 + apps/ui/src/images/ecosystems/celo.svg | 18 + apps/ui/src/images/ecosystems/klaytn.svg | 19 + apps/ui/src/images/ecosystems/neon.svg | 65 + apps/ui/src/images/ecosystems/oasis.svg | 1 + apps/ui/src/images/ecosystems/terra.svg | 1 + apps/ui/src/locales/en/translation.json | 22 +- apps/ui/src/pages/SwapPage/SwapPage.tsx | 2 +- apps/ui/src/pages/WormholePage.scss | 10 - apps/ui/src/pages/WormholePage.tsx | 27 +- 23 files changed, 3428 insertions(+), 351 deletions(-) delete mode 100644 apps/ui/src/components/WormholeForm.tsx create mode 100644 apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx create mode 100644 apps/ui/src/components/WormholeForm/WormholeForm.scss create mode 100644 apps/ui/src/components/WormholeForm/WormholeForm.tsx create mode 100644 apps/ui/src/components/WormholeForm/WormholeTokenModal.scss create mode 100644 apps/ui/src/components/WormholeForm/WormholeTokenModal.tsx create mode 100644 apps/ui/src/components/WormholeForm/WormholeTokenSelect.tsx create mode 100644 apps/ui/src/components/WormholeForm/index.ts create mode 100644 apps/ui/src/images/ecosystems/algorand.svg create mode 100644 apps/ui/src/images/ecosystems/celo.svg create mode 100644 apps/ui/src/images/ecosystems/klaytn.svg create mode 100644 apps/ui/src/images/ecosystems/neon.svg create mode 100644 apps/ui/src/images/ecosystems/oasis.svg create mode 100644 apps/ui/src/images/ecosystems/terra.svg delete mode 100644 apps/ui/src/pages/WormholePage.scss diff --git a/apps/ui/src/components/Layout/Layout.tsx b/apps/ui/src/components/Layout/Layout.tsx index 4b82b4e66..de3ef4c15 100644 --- a/apps/ui/src/components/Layout/Layout.tsx +++ b/apps/ui/src/components/Layout/Layout.tsx @@ -48,6 +48,9 @@ export const Layout = ({ {/* TODO: Enable when token is launched */} {/* Stake */} + + {t("nav.wormhole")} + {t("nav.help")} diff --git a/apps/ui/src/components/TokenIcon.tsx b/apps/ui/src/components/TokenIcon.tsx index b2b61a417..4690eed15 100644 --- a/apps/ui/src/components/TokenIcon.tsx +++ b/apps/ui/src/components/TokenIcon.tsx @@ -1,12 +1,13 @@ import { EuiIcon } from "@elastic/eui"; import type { TokenProject } from "@swim-io/token-projects"; import { TOKEN_PROJECTS_BY_ID } from "@swim-io/token-projects"; +import type { WormholeToken } from "models"; import type { ComponentProps, ReactElement } from "react"; import { Fragment } from "react"; import { Trans } from "react-i18next"; -import type { EcosystemId, TokenConfig } from "../config"; import { ECOSYSTEMS } from "../config"; +import type { EcosystemId, TokenConfig } from "../config"; import { useIntlListSeparators } from "../hooks"; import type { Amount } from "../models/amount"; @@ -18,6 +19,29 @@ interface TokenIconProps readonly showFullName?: boolean; } +type WormholeTokenIconProps = { + readonly token: WormholeToken; + readonly isSelected: boolean; +}; + +export const WormholeTokenIcon = ({ + token, + isSelected, +}: WormholeTokenIconProps): ReactElement => { + const { logo, symbol, displayName } = token; + return ( +
+ + {isSelected ? `${symbol}` : `${symbol} - ${displayName}`} +
+ ); +}; + type WithIconProps = ComponentProps; const WithIcon = ({ children, ...rest }: WithIconProps) => { return ( diff --git a/apps/ui/src/components/TokenSearchModal.scss b/apps/ui/src/components/TokenSearchModal.scss index c6ecd3836..d0da167d8 100644 --- a/apps/ui/src/components/TokenSearchModal.scss +++ b/apps/ui/src/components/TokenSearchModal.scss @@ -1,6 +1,6 @@ .tokenSearchModal { min-height: 50vh; - max-width: 430px; + max-width: 430px !important; } .modalBody { height: 30vh; diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx deleted file mode 100644 index d3f106e7f..000000000 --- a/apps/ui/src/components/WormholeForm.tsx +++ /dev/null @@ -1,323 +0,0 @@ -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 { 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 => - findOrThrow( - [token.nativeDetails, ...token.wrappedDetails], - (details) => details.chainId === chainId, - ); - -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); - 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 = () => { - (async (): Promise => { - 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 */} -
Chain IDTx ID{"Chain ID"}{"Tx ID"}
{chainId} {txId}
{"Source Chain"} {sourceChainId}
{"Balance"}{splBalance?.toString() ?? erc20Balance?.toString() ?? "-"}
{"Target Chain"} {targetChainId}
- - - - - - - - - {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/components/WormholeForm/WormholeChainSelect.tsx b/apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx new file mode 100644 index 000000000..0f19bdc74 --- /dev/null +++ b/apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx @@ -0,0 +1,79 @@ +import type { ChainId } from "@certusone/wormhole-sdk"; +import { CHAIN_ID_TO_NAME } from "@certusone/wormhole-sdk"; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiSuperSelect, + EuiText, +} from "@elastic/eui"; + +import type { WormholeEcosystemId } from "../../config"; +import { WORMHOLE_ECOSYSTEMS } from "../../config"; + +import "./WormholeForm.scss"; + +interface Props { + readonly chains: readonly ChainId[]; + readonly selectedChainId: ChainId; + readonly onSelectChain: (chain: ChainId) => void; + readonly label?: string; +} + +const WormholeChainSelect = ({ + chains, + selectedChainId, + onSelectChain, + label, +}: Props) => { + const chainOptions = chains.map((chainId) => ({ + value: String(chainId), + inputDisplay: ( + + + + + + + { + WORMHOLE_ECOSYSTEMS[ + CHAIN_ID_TO_NAME[chainId] as WormholeEcosystemId + ].displayName + } + + + + ), + append: ( + + ), + selected: chainId === selectedChainId, + })); + + return ( + <> + + onSelectChain(Number(value) as ChainId)} + hasDividers + /> + + + ); +}; + +export default WormholeChainSelect; diff --git a/apps/ui/src/components/WormholeForm/WormholeForm.scss b/apps/ui/src/components/WormholeForm/WormholeForm.scss new file mode 100644 index 000000000..78c29ff05 --- /dev/null +++ b/apps/ui/src/components/WormholeForm/WormholeForm.scss @@ -0,0 +1,21 @@ +.wormholeForm { + width: 100%; + max-width: 500px; + transition: all 0.5s ease; +} + +.chainName { + text-transform: capitalize; +} + +.transactions { + padding: 10; + overflow-x: auto; + transition: all 1s ease-out; +} + +@media (min-width: 768px) { + .wormholeForm { + min-width: 460px; + } +} diff --git a/apps/ui/src/components/WormholeForm/WormholeForm.tsx b/apps/ui/src/components/WormholeForm/WormholeForm.tsx new file mode 100644 index 000000000..ece728a21 --- /dev/null +++ b/apps/ui/src/components/WormholeForm/WormholeForm.tsx @@ -0,0 +1,297 @@ +import type { ChainId } from "@certusone/wormhole-sdk"; +import { CHAIN_ID_TO_NAME } from "@certusone/wormhole-sdk"; +import { + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from "@elastic/eui"; +import { findOrThrow } from "@swim-io/utils"; +import Decimal from "decimal.js"; +import type { FormEvent, ReactElement } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import type { EcosystemId } from "../../config"; +import { wormholeTokens as rawWormholeTokens } from "../../config"; +import { useNotification } from "../../core/store"; +import { useWormholeTransfer } from "../../hooks"; +import { generateId } from "../../models"; +import type { + TxResult, + WormholeToken, + WormholeTokenDetails, +} from "../../models"; +import { ConfirmModal } from "../ConfirmModal"; +import { MultiConnectButton } from "../ConnectButton"; +import { EuiFieldIntlNumber } from "../EuiFieldIntlNumber"; +import { TxListItem } from "../molecules/TxListItem"; + +import WormholeChainSelect from "./WormholeChainSelect"; +import { WormholeTokenSelect } from "./WormholeTokenSelect"; + +import "./WormholeForm.scss"; + +const getDetailsByChainId = ( + token: WormholeToken, + chainId: ChainId, +): WormholeTokenDetails => + findOrThrow( + [token.nativeDetails, ...token.wrappedDetails], + (details) => details.chainId === chainId, + ); + +export const WormholeForm = (): ReactElement => { + const { t } = useTranslation(); + const { notify } = useNotification(); + const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const [error, setError] = useState(null); + const [inputAmount, setInputAmount] = useState(new Decimal(0)); + const [txResults, setTxResults] = useState([]); + const [amountErrors, setAmountErrors] = useState([]); + const wormholeTokens = rawWormholeTokens as readonly WormholeToken[]; + const { mutateAsync: transfer, isLoading } = useWormholeTransfer(); + + const [currentTokenSymbol, setCurrentTokenSymbol] = useState( + wormholeTokens[0].symbol, + ); + const currentToken = findOrThrow( + wormholeTokens, + (token) => token.symbol === currentTokenSymbol, + ); + + const sourceChains = useMemo( + () => [ + currentToken.nativeDetails.chainId, + ...currentToken.wrappedDetails.map(({ chainId }) => chainId), + ], + [currentToken], + ); + const [sourceChainId, setSourceChainId] = useState(sourceChains[0]); + + const targetChains = useMemo( + () => sourceChains.filter((option) => option !== sourceChainId), + [sourceChains, sourceChainId], + ); + const [targetChainId, setTargetChainId] = useState(targetChains[0]); + + const handleTxResult = (txResult: TxResult): void => { + setTxResults((previousResults) => [...previousResults, txResult]); + }; + + const handleSubmit = () => { + (async (): Promise => { + setTxResults([]); + setError(null); + const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); + const targetDetails = getDetailsByChainId(currentToken, targetChainId); + await transfer({ + interactionId: generateId(), + value: inputAmount, + sourceDetails, + targetDetails, + nativeDetails: currentToken.nativeDetails, + onTxResult: handleTxResult, + }); + })().catch((e) => { + console.error(e); + notify("Error", String(e), "error"); + setError(String(e)); + }); + }; + + const handleConfirmSubmit = (e: FormEvent): void => { + e.preventDefault(); + setIsConfirmModalVisible(true); + }; + + useEffect(() => { + setSourceChainId(sourceChains[0]); + }, [sourceChains]); + + useEffect(() => { + if (targetChainId === sourceChainId) { + setTargetChainId(targetChains[0]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [targetChains]); + + const handleConfirmModalCancel = (): void => { + setIsConfirmModalVisible(false); + }; + + const handleConfirmModalConfirm = (): void => { + setIsConfirmModalVisible(false); + handleSubmit(); + }; + + const handleBlurTransferAmount = useCallback((): void => { + let errors: readonly string[] = []; + if (inputAmount.isNeg()) { + errors = [...errors, t("general.amount_of_tokens_invalid")]; + } else if (inputAmount.lte(0)) { + errors = [...errors, t("general.amount_of_tokens_less_than_one")]; + // } else if ( + // currentToken && + // new Decimal(inputAmount).gt(currentToken) + // ) { + // errors = [...errors, t("general.amount_of_tokens_exceed_balance")]; + } else { + errors = []; + } + setAmountErrors(errors); + }, [inputAmount, t]); + + const handleTransferAmountChange = useCallback((value: string): void => { + if (value === "") { + setInputAmount(new Decimal(0)); + } else { + setInputAmount(new Decimal(value)); + } + }, []); + + return ( + + + + +

{t("wormhole_page.title")}

+
+
+ + + +
+ + + + + + setCurrentTokenSymbol(token.symbol) + } + /> + + + + {t("swap_form.user_balance")}} + isInvalid={amountErrors.length > 0} + error={amountErrors} + > + 0} + /> + + + + + + + + + + + + + + {!inputAmount.isZero() && ( + <> + + + {t("wormhole_page.receiving_amount", { + amount: inputAmount, + token: currentToken.symbol, + })} + + + )} + + + {isLoading + ? t("wormhole_page.button.bridging") + : t("wormhole_page.button.transfer")} + + + {txResults.length > 0 && ( + <> + + +

{t("wormhole_page.transfer_info")}

+
+ {txResults.map(({ chainId, txId }) => ( +
+ + + +
+ ))} +
+
+ + )} + {error !== null && txResults.length > 0 && ( + <> + + + + {t("wormhole_page.error.message")} + +

{error}

+
+ + )} + + +
+ ); +}; diff --git a/apps/ui/src/components/WormholeForm/WormholeTokenModal.scss b/apps/ui/src/components/WormholeForm/WormholeTokenModal.scss new file mode 100644 index 000000000..90c936ac8 --- /dev/null +++ b/apps/ui/src/components/WormholeForm/WormholeTokenModal.scss @@ -0,0 +1,40 @@ +.wormholeModal { + min-height: 50vh; + max-width: 430px !important; +} +.modalBody { + height: 30vh; + overflow: auto; + .saveToken { + cursor: pointer; + } +} + +.networkPanel { + max-height: 30vh; + display: flex; + justify-content: flex-start; + align-items: center; + padding: 20px; +} +.ecosystemIcon { + width: fit-content; + flex-basis: 0; + .euiButton__text { + display: flex; + justify-content: space-evenly; + align-items: center; + width: 100%; + } +} + +@media only screen and (max-width: 540px) { + .networkPanel { + justify-content: space-around; + overflow-y: auto; + } + .euiModalHeader__title { + text-align: center; + width: 100%; + } +} diff --git a/apps/ui/src/components/WormholeForm/WormholeTokenModal.tsx b/apps/ui/src/components/WormholeForm/WormholeTokenModal.tsx new file mode 100644 index 000000000..1b0eb7de8 --- /dev/null +++ b/apps/ui/src/components/WormholeForm/WormholeTokenModal.tsx @@ -0,0 +1,92 @@ +import type { EuiSelectableOption } from "@elastic/eui"; +import { + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSelectable, +} from "@elastic/eui"; +import { useCallback } from "react"; +import type { ReactElement, ReactNode } from "react"; +import { useTranslation } from "react-i18next"; + +import type { WormholeToken } from "../../models"; +import { CustomModal } from "../CustomModal"; +import { WormholeTokenIcon } from "../TokenIcon"; + +import "./WormholeTokenModal.scss"; + +type TokenOption = EuiSelectableOption<{ + readonly data: Readonly; +}>; + +const renderTokenOption = (option: TokenOption): ReactNode => ( + +); + +interface Props { + readonly handleClose: () => void; + readonly handleSelectToken: (token: WormholeToken) => void; + readonly tokens: readonly WormholeToken[]; +} + +export const WormholeTokenModal = ({ + handleClose, + handleSelectToken, + tokens, +}: Props): ReactElement => { + const { t } = useTranslation(); + + const options = tokens.map((token) => ({ + label: `${token.symbol} ${token.displayName}`, + searchableLabel: `${token.symbol} ${token.displayName}`, + showIcons: false, + data: token, + })); + + const onSelectToken = useCallback( + (opts: readonly TokenOption[]) => { + const selected = opts.find((token: TokenOption) => token.checked); + if (selected) { + handleSelectToken(selected.data); + handleClose(); + } + }, + [handleClose, handleSelectToken], + ); + + return ( + + + + {t("wormhole_page.token_select_modal.title")} + + + + + {(list, search) => ( + <> + {search} + {list} + + )} + + + + ); +}; + +export {}; diff --git a/apps/ui/src/components/WormholeForm/WormholeTokenSelect.tsx b/apps/ui/src/components/WormholeForm/WormholeTokenSelect.tsx new file mode 100644 index 000000000..23892df14 --- /dev/null +++ b/apps/ui/src/components/WormholeForm/WormholeTokenSelect.tsx @@ -0,0 +1,45 @@ +import { EuiButton } from "@elastic/eui"; +import type { WormholeToken } from "models"; +import type { ReactElement } from "react"; +import { useCallback, useState } from "react"; + +import { WormholeTokenIcon } from "../TokenIcon"; + +import { WormholeTokenModal } from "./WormholeTokenModal"; + +interface Props { + readonly onSelectToken: (token: WormholeToken) => void; + readonly tokens: readonly WormholeToken[]; + readonly selectedToken: WormholeToken; +} + +export const WormholeTokenSelect = ({ + onSelectToken, + selectedToken, + tokens, +}: Props): ReactElement => { + const [showModal, setShowModal] = useState(false); + + const openModal = useCallback(() => setShowModal(true), [setShowModal]); + const closeModal = useCallback(() => setShowModal(false), [setShowModal]); + + return ( + <> + + + + {showModal && ( + + )} + + ); +}; diff --git a/apps/ui/src/components/WormholeForm/index.ts b/apps/ui/src/components/WormholeForm/index.ts new file mode 100644 index 000000000..6739fc40c --- /dev/null +++ b/apps/ui/src/components/WormholeForm/index.ts @@ -0,0 +1 @@ +export * from "./WormholeForm"; diff --git a/apps/ui/src/config/wormhole.ts b/apps/ui/src/config/wormhole.ts index 6a63881cf..35379dfd8 100644 --- a/apps/ui/src/config/wormhole.ts +++ b/apps/ui/src/config/wormhole.ts @@ -1,5 +1,22 @@ +import type { ChainId, ChainName } from "@certusone/wormhole-sdk"; +import type { ReadonlyRecord } from "@swim-io/utils"; import { WormholeChainId } from "@swim-io/wormhole"; +import ACALA_SVG from "../images/ecosystems/acala.svg"; +import ALGORAND_SVG from "../images/ecosystems/algorand.svg"; +import APTOS_SVG from "../images/ecosystems/aptos.svg"; +import AURORA_SVG from "../images/ecosystems/aurora.svg"; +import AVALANCHE_SVG from "../images/ecosystems/avalanche.svg"; +import BNB_SVG from "../images/ecosystems/bnb.svg"; +import ETHEREUM_SVG from "../images/ecosystems/ethereum.svg"; +import FANTOM_SVG from "../images/ecosystems/fantom.svg"; +import KARURA_SVG from "../images/ecosystems/karura.svg"; +import KLAYTN_SVG from "../images/ecosystems/klaytn.svg"; +import OASIS_SVG from "../images/ecosystems/oasis.svg"; +import POLYGON_SVG from "../images/ecosystems/polygon.svg"; +import SOLANA_SVG from "../images/ecosystems/solana.svg"; +import TERRA_SVG from "../images/ecosystems/terra.svg"; + // We currently use this with Wormhole SDK’s getSignedVAAWithRetry function. // By default this function retries every 1 second. export const getWormholeRetries = (chainId: WormholeChainId): number => { @@ -14,3 +31,138 @@ export const getWormholeRetries = (chainId: WormholeChainId): number => { return 300; } }; + +export const enum WormholeEcosystemId { + Ethereum = "ethereum", + Bsc = "bsc", + Avalanche = "avalanche", + Polygon = "polygon", + Aurora = "aurora", + Fantom = "fantom", + Karura = "karura", + Acala = "acala", + Aptos = "aptos", + Celo = "celo", + Solana = "solana", + Klaytn = "klaytn", + Neon = "neon", + Oasis = "oasis", + Algorand = "algorand", + Terra = "terra", +} + +export interface WormholeEcosystem { + readonly id: ChainName; + readonly chainId: ChainId; + readonly displayName: string; + readonly logo: string; + readonly nativeTokenSymbol: string; +} + +export const WORMHOLE_ECOSYSTEM_LIST: readonly WormholeEcosystem[] = [ + { + id: WormholeEcosystemId.Aptos, + chainId: WormholeChainId.Aptos, + displayName: "Aptos", + logo: APTOS_SVG, + nativeTokenSymbol: "APT", + }, + { + id: WormholeEcosystemId.Solana, + chainId: WormholeChainId.Solana, + displayName: "Solana", + logo: SOLANA_SVG, + nativeTokenSymbol: "SOL", + }, + { + id: WormholeEcosystemId.Ethereum, + chainId: WormholeChainId.Ethereum, + displayName: "Ethereum", + logo: ETHEREUM_SVG, + nativeTokenSymbol: "ETH", + }, + { + id: WormholeEcosystemId.Bsc, + chainId: WormholeChainId.Bnb, + displayName: "BNB Chain", + logo: BNB_SVG, + nativeTokenSymbol: "BNB", + }, + { + id: WormholeEcosystemId.Avalanche, + chainId: WormholeChainId.Avalanche, + displayName: "Avalanche", + logo: AVALANCHE_SVG, + nativeTokenSymbol: "AVAX", + }, + { + id: WormholeEcosystemId.Polygon, + chainId: WormholeChainId.Polygon, + displayName: "Polygon", + logo: POLYGON_SVG, + nativeTokenSymbol: "MATIC", + }, + { + id: WormholeEcosystemId.Aurora, + chainId: WormholeChainId.Aurora, + displayName: "Aurora", + logo: AURORA_SVG, + nativeTokenSymbol: "ETH", + }, + { + id: WormholeEcosystemId.Fantom, + chainId: WormholeChainId.Fantom, + displayName: "Fantom", + logo: FANTOM_SVG, + nativeTokenSymbol: "FTM", + }, + { + id: WormholeEcosystemId.Karura, + chainId: WormholeChainId.Karura, + displayName: "Karura", + logo: KARURA_SVG, + nativeTokenSymbol: "KAR", + }, + { + id: WormholeEcosystemId.Acala, + chainId: WormholeChainId.Acala, + displayName: "Acala", + logo: ACALA_SVG, + nativeTokenSymbol: "ACA", + }, + { + id: WormholeEcosystemId.Algorand, + chainId: WormholeChainId.Algorand, + displayName: "Algorand", + logo: ALGORAND_SVG, + nativeTokenSymbol: "ALGO", + }, + { + id: WormholeEcosystemId.Klaytn, + chainId: WormholeChainId.Klaytn, + displayName: "Klaytn", + logo: KLAYTN_SVG, + nativeTokenSymbol: "KLAY", + }, + { + id: WormholeEcosystemId.Oasis, + chainId: WormholeChainId.Oasis, + displayName: "Oasis", + logo: OASIS_SVG, + nativeTokenSymbol: "ROSE", + }, + { + id: WormholeEcosystemId.Terra, + chainId: WormholeChainId.Terra, + displayName: "Terra", + logo: TERRA_SVG, + nativeTokenSymbol: "LUNA", + }, +]; + +export const WORMHOLE_ECOSYSTEMS: ReadonlyRecord< + WormholeEcosystemId, + WormholeEcosystem +> = Object.fromEntries( + WORMHOLE_ECOSYSTEM_LIST.map((ecosystem) => [ecosystem.id, ecosystem]), +) as ReadonlyRecord; diff --git a/apps/ui/src/config/wormholeTokens.json b/apps/ui/src/config/wormholeTokens.json index 208961037..e9063cc82 100644 --- a/apps/ui/src/config/wormholeTokens.json +++ b/apps/ui/src/config/wormholeTokens.json @@ -1 +1,2531 @@ -[{"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 +[ + { + "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 + } + ] + } +] diff --git a/apps/ui/src/images/ecosystems/algorand.svg b/apps/ui/src/images/ecosystems/algorand.svg new file mode 100644 index 000000000..8c7a3667b --- /dev/null +++ b/apps/ui/src/images/ecosystems/algorand.svg @@ -0,0 +1 @@ +ALGO_Logos_190320 \ No newline at end of file diff --git a/apps/ui/src/images/ecosystems/celo.svg b/apps/ui/src/images/ecosystems/celo.svg new file mode 100644 index 000000000..076bebe64 --- /dev/null +++ b/apps/ui/src/images/ecosystems/celo.svg @@ -0,0 +1,18 @@ + + + + +Artboard 1 + + + + diff --git a/apps/ui/src/images/ecosystems/klaytn.svg b/apps/ui/src/images/ecosystems/klaytn.svg new file mode 100644 index 000000000..cd2d4908e --- /dev/null +++ b/apps/ui/src/images/ecosystems/klaytn.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/apps/ui/src/images/ecosystems/neon.svg b/apps/ui/src/images/ecosystems/neon.svg new file mode 100644 index 000000000..fd1933868 --- /dev/null +++ b/apps/ui/src/images/ecosystems/neon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/ui/src/images/ecosystems/oasis.svg b/apps/ui/src/images/ecosystems/oasis.svg new file mode 100644 index 000000000..301d1cc5c --- /dev/null +++ b/apps/ui/src/images/ecosystems/oasis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/ui/src/images/ecosystems/terra.svg b/apps/ui/src/images/ecosystems/terra.svg new file mode 100644 index 000000000..83be6900d --- /dev/null +++ b/apps/ui/src/images/ecosystems/terra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/ui/src/locales/en/translation.json b/apps/ui/src/locales/en/translation.json index 794293776..f3db069ad 100644 --- a/apps/ui/src/locales/en/translation.json +++ b/apps/ui/src/locales/en/translation.json @@ -136,6 +136,7 @@ "nav.telegram": "Telegram", "nav.terms_of_service": "Terms of Service", "nav.twitter": "Twitter", + "nav.wormhole": "Wormhole", "new_version_alert.description": "There is a new version of the app available. Please reload the page to continue using the app.", "new_version_alert.title": "New version available", "not_found.body1": "The link you followed may be broken, or the page may have been removed.", @@ -272,5 +273,24 @@ "token_search_modal.search_tokens": "Search tokens", "token_search_modal.title": "Select a token", "token_select_modal.title": "Select chain and token", - "vaa_error.transfer_not_detected": "Transfer not detected by Wormhole guardians. This usually happens when the network is congested. Please retry later." + "vaa_error.transfer_not_detected": "Transfer not detected by Wormhole guardians. This usually happens when the network is congested. Please retry later.", + "wormhole_page.button.bridging": "Bridging ... ", + "wormhole_page.button.transfer": "Transfer", + "wormhole_page.confirm_modal.cancel": "Cancel", + "wormhole_page.confirm_modal.question": "Are you sure you want to transfer ?", + "wormhole_page.confirm_modal.title": "Confirm Transfer", + "wormhole_page.confirm_modal.transfer": "Transfer", + "wormhole_page.custom_token_address": "Custom token address", + "wormhole_page.error.message": "If your transfer was interrupted, check the relevant blockchain explorer for the transaction ID and use link to complete the transfer. ", + "wormhole_page.error.title": "Something went wrong", + "wormhole_page.message.bridged_tokens": "The tokens have entered to the bridge. View transaction: ", + "wormhole_page.message.fetching_vaa": "Fetching signed VAA...", + "wormhole_page.message.signed_vaa": "Fetched signed VAA !", + "wormhole_page.message.transfer_success": "Transfer is completed. View transactions: ", + "wormhole_page.receiving_amount": "You will receive {{amount}} {{token}}.", + "wormhole_page.source_chain": "Source chain", + "wormhole_page.target_chain": "Target chain", + "wormhole_page.title": "Wormhole", + "wormhole_page.token_select_modal.title": "Select token", + "wormhole_page.transfer_info": "Transfer information: " } diff --git a/apps/ui/src/pages/SwapPage/SwapPage.tsx b/apps/ui/src/pages/SwapPage/SwapPage.tsx index 2fe46d2ea..c8b5468c1 100644 --- a/apps/ui/src/pages/SwapPage/SwapPage.tsx +++ b/apps/ui/src/pages/SwapPage/SwapPage.tsx @@ -61,7 +61,7 @@ const SwapPage = (): ReactElement => { ); return ( - + diff --git a/apps/ui/src/pages/WormholePage.scss b/apps/ui/src/pages/WormholePage.scss deleted file mode 100644 index 010e88b94..000000000 --- a/apps/ui/src/pages/WormholePage.scss +++ /dev/null @@ -1,10 +0,0 @@ -.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 index 3d12a9f16..d68c45148 100644 --- a/apps/ui/src/pages/WormholePage.tsx +++ b/apps/ui/src/pages/WormholePage.tsx @@ -3,8 +3,6 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, - EuiSpacer, - EuiTitle, } from "@elastic/eui"; import type { ReactElement } from "react"; import { useTranslation } from "react-i18next"; @@ -12,24 +10,27 @@ 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"}

-
- + + - -
+
+
); From e6ffbe2964a81215ce236a0b6935edde3d433dda Mon Sep 17 00:00:00 2001 From: cypher Date: Thu, 20 Oct 2022 17:20:39 +0200 Subject: [PATCH 35/36] fix: chain select style --- apps/ui/src/components/WormholeForm/WormholeForm.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/ui/src/components/WormholeForm/WormholeForm.scss b/apps/ui/src/components/WormholeForm/WormholeForm.scss index 78c29ff05..f64503bc6 100644 --- a/apps/ui/src/components/WormholeForm/WormholeForm.scss +++ b/apps/ui/src/components/WormholeForm/WormholeForm.scss @@ -2,6 +2,13 @@ width: 100%; max-width: 500px; transition: all 0.5s ease; + + .euiSuperSelectControl { + .euiFlexGroup--responsive { + display: flex; + flex-wrap: nowrap; + } + } } .chainName { From 6a6f7691736cd4a9c155a1113919991816b7eaf5 Mon Sep 17 00:00:00 2001 From: cypher Date: Thu, 20 Oct 2022 19:10:20 +0200 Subject: [PATCH 36/36] chore: set color for chain select --- .../components/WormholeForm/WormholeChainSelect.tsx | 2 ++ .../ui/src/components/WormholeForm/WormholeForm.scss | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx b/apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx index 0f19bdc74..68f5e8b4a 100644 --- a/apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx +++ b/apps/ui/src/components/WormholeForm/WormholeChainSelect.tsx @@ -69,6 +69,8 @@ const WormholeChainSelect = ({ options={chainOptions} valueOfSelected={String(selectedChainId)} onChange={(value) => onSelectChain(Number(value) as ChainId)} + className="euiButton--primary" + itemClassName="chainSelectItem" hasDividers /> diff --git a/apps/ui/src/components/WormholeForm/WormholeForm.scss b/apps/ui/src/components/WormholeForm/WormholeForm.scss index f64503bc6..361a94f6a 100644 --- a/apps/ui/src/components/WormholeForm/WormholeForm.scss +++ b/apps/ui/src/components/WormholeForm/WormholeForm.scss @@ -21,8 +21,14 @@ transition: all 1s ease-out; } -@media (min-width: 768px) { - .wormholeForm { - min-width: 460px; +@media only screen and (max-width: 748px) { + .chainSelectItem { + .euiContextMenuItem__text { + max-width: 80%; + .euiFlexGroup--responsive { + display: flex; + flex-wrap: nowrap; + } + } } }