From 75a19f3538cb9bef5044b342b72a131e480587b5 Mon Sep 17 00:00:00 2001 From: Chiu Date: Wed, 26 Oct 2022 17:32:16 +0800 Subject: [PATCH] chore(ui): move postVaa to SDK --- ...ChainEvmToSolanaSwapInteractionMutation.ts | 102 ++++------------- apps/ui/src/models/swim/interactionStateV2.ts | 2 +- packages/solana/src/client.ts | 105 ++++++++++++++---- packages/solana/src/protocol.ts | 1 + 4 files changed, 105 insertions(+), 105 deletions(-) diff --git a/apps/ui/src/hooks/interaction/useCrossChainEvmToSolanaSwapInteractionMutation.ts b/apps/ui/src/hooks/interaction/useCrossChainEvmToSolanaSwapInteractionMutation.ts index 63825f4e9..263a06d31 100644 --- a/apps/ui/src/hooks/interaction/useCrossChainEvmToSolanaSwapInteractionMutation.ts +++ b/apps/ui/src/hooks/interaction/useCrossChainEvmToSolanaSwapInteractionMutation.ts @@ -6,13 +6,9 @@ import { Keypair } from "@solana/web3.js"; import { getTokenDetails } from "@swim-io/core"; import { EVM_ECOSYSTEMS, isEvmEcosystemId } from "@swim-io/evm"; import { Routing__factory } from "@swim-io/evm-contracts"; -import { - SOLANA_ECOSYSTEM_ID, - SolanaTxType, - findTokenAccountForMint, -} from "@swim-io/solana"; +import { SOLANA_ECOSYSTEM_ID, SolanaTxType } from "@swim-io/solana"; import { TOKEN_PROJECTS_BY_ID } from "@swim-io/token-projects"; -import { useMutation, useQueryClient } from "react-query"; +import { useMutation } from "react-query"; import shallow from "zustand/shallow.js"; import { getWormholeRetries } from "../../config"; @@ -22,19 +18,15 @@ import type { CrossChainEvmToSolanaSwapInteractionState } from "../../models"; import { InteractionType, SwapType, - findOrCreateSplTokenAccount, getSignedVaaWithRetry, humanDecimalToAtomicString, } from "../../models"; import { useWallets } from "../crossEcosystem"; import { useGetEvmClient } from "../evm"; -import { useSolanaClient, useUserSolanaTokenAccountsQuery } from "../solana"; +import { useSolanaClient } from "../solana"; import { useSwimUsd } from "../swim"; export const useCrossChainEvmToSolanaSwapInteractionMutation = () => { - const queryClient = useQueryClient(); - const { data: existingSplTokenAccounts = [] } = - useUserSolanaTokenAccountsQuery(); const { updateInteractionState } = useInteractionStateV2(); const wallets = useWallets(); const solanaClient = useSolanaClient(); @@ -52,7 +44,7 @@ export const useCrossChainEvmToSolanaSwapInteractionMutation = () => { if (wormhole === null) { throw new Error("No Wormhole RPC configured"); } - const { interaction, requiredSplTokenAccounts } = interactionState; + const { interaction } = interactionState; const { fromTokenData, toTokenData, firstMinimumOutputAmount } = interaction.params; if (firstMinimumOutputAmount === null) { @@ -172,73 +164,7 @@ export const useCrossChainEvmToSolanaSwapInteractionMutation = () => { undefined, retries, ); - const splTokenAccounts = await Promise.all( - Object.keys(requiredSplTokenAccounts).map(async (mint) => { - const { tokenAccount, creationTxId } = - await findOrCreateSplTokenAccount({ - env: interaction.env, - solanaClient, - wallet: toWallet, - queryClient, - splTokenMintAddress: mint, - splTokenAccounts: existingSplTokenAccounts, - }); - // Update interactionState - if (creationTxId !== null) { - updateInteractionState(interaction.id, (draft) => { - if ( - draft.interactionType !== InteractionType.SwapV2 || - draft.swapType !== SwapType.SingleChainSolana - ) { - throw new Error("Interaction type mismatch"); - } - draft.requiredSplTokenAccounts[mint].txId = creationTxId; - }); - } - return tokenAccount; - }), - ); - const swimUsdAccount = findTokenAccountForMint( - swimUsd.nativeDetails.address, - toWallet.address, - splTokenAccounts, - ); - if (swimUsdAccount === null) { - throw new Error("SwimUsd account not found"); - } - if (interactionState.postVaaOnSolanaTxId === null) { - const auxiliarySigner = Keypair.generate(); - const postVaaTxGenerator = - solanaClient.generateCompleteWormholeMessageTxs({ - interactionId: interaction.id, - vaa: signedVaa, - wallet: toWallet, - auxiliarySigner, - }); - for await (const result of postVaaTxGenerator) { - updateInteractionState(interaction.id, (draft) => { - if ( - draft.interactionType !== InteractionType.SwapV2 || - draft.swapType !== SwapType.CrossChainEvmToSolana - ) { - throw new Error("Interaction type mismatch"); - } - - switch (result.type) { - case SolanaTxType.WormholeVerifySignatures: - draft.verifySignaturesTxIds.push(result.tx.id); - break; - case SolanaTxType.WormholePostVaa: - draft.postVaaOnSolanaTxId = result.tx.id; - draft.auxiliarySignerPublicKey = - auxiliarySigner.publicKey.toBase58(); - break; - default: - throw new Error(`Unexpected transaction type: ${result.tx.id}`); - } - }); - } - } + const auxiliarySigner = Keypair.generate(); const tokenProject = TOKEN_PROJECTS_BY_ID[toTokenSpec.projectId]; if (tokenProject.tokenNumber === null) { throw new Error(`Token number for ${tokenProject.symbol} not found`); @@ -267,14 +193,28 @@ export const useCrossChainEvmToSolanaSwapInteractionMutation = () => { throw new Error("Interaction type mismatch"); } switch (result.type) { + case SolanaTxType.SwimCreateSplTokenAccount: { + const mint = result.tx.original.meta?.preTokenBalances?.[0].mint; + if (!mint) { + throw new Error("Token account mint not found"); + } + draft.requiredSplTokenAccounts[mint].txId = result.tx.id; + break; + } + case SolanaTxType.WormholeVerifySignatures: + draft.verifySignaturesTxIds.push(result.tx.id); + break; + case SolanaTxType.WormholePostVaa: + draft.postVaaOnSolanaTxId = result.tx.id; + draft.auxiliarySignerPublicKey = + auxiliarySigner.publicKey.toBase58(); + break; case SolanaTxType.SwimCompleteNativeWithPayload: draft.completeNativeWithPayloadTxId = result.tx.id; break; case SolanaTxType.SwimProcessSwimPayload: draft.processSwimPayloadTxId = result.tx.id; break; - default: - throw new Error(`Unexpected transaction type: ${result.tx.id}`); } }); } diff --git a/apps/ui/src/models/swim/interactionStateV2.ts b/apps/ui/src/models/swim/interactionStateV2.ts index 3913c9ec5..3f266d8d1 100644 --- a/apps/ui/src/models/swim/interactionStateV2.ts +++ b/apps/ui/src/models/swim/interactionStateV2.ts @@ -71,7 +71,7 @@ export interface CrossChainEvmToSolanaSwapInteractionState { readonly approvalTxIds: readonly EvmTx["id"][]; readonly crossChainInitiateTxId: EvmTx["id"] | null; readonly auxiliarySignerPublicKey: string | null; - readonly verifySignaturesTxIds: SolanaTx["id"][]; + readonly verifySignaturesTxIds: readonly SolanaTx["id"][]; readonly postVaaOnSolanaTxId: SolanaTx["id"] | null; readonly completeNativeWithPayloadTxId: SolanaTx["id"] | null; readonly processSwimPayloadTxId: SolanaTx["id"] | null; diff --git a/packages/solana/src/client.ts b/packages/solana/src/client.ts index b471c9ca3..1627e04f1 100644 --- a/packages/solana/src/client.ts +++ b/packages/solana/src/client.ts @@ -57,7 +57,10 @@ import { SOLANA_ECOSYSTEM_ID, SolanaTxType } from "./protocol"; import type { TokenAccount } from "./serialization"; import { deserializeTokenAccount } from "./serialization"; import type { SupportedTokenProjectId } from "./supportedTokenProjectIds"; -import { isSupportedTokenProjectId } from "./supportedTokenProjectIds"; +import { + SUPPORTED_TOKEN_PROJECT_IDS, + isSupportedTokenProjectId, +} from "./supportedTokenProjectIds"; import { createApproveAndRevokeIxs, createTx, @@ -370,7 +373,8 @@ export class SolanaClient extends Client< sourceChainConfig, targetTokenNumber, minimumOutputAmount, - }: { + auxiliarySigner = Keypair.generate(), + }: WithOptionalAuxiliarySigner<{ readonly wallet: SolanaWalletAdapter; readonly interactionId: string; readonly signedVaa: Buffer; @@ -378,19 +382,51 @@ export class SolanaClient extends Client< readonly sourceChainConfig: ChainConfig; readonly targetTokenNumber: number; readonly minimumOutputAmount: string; - }): AsyncGenerator< - TxGeneratorResult, + }>): AsyncGenerator< + TxGeneratorResult< + ParsedTransactionWithMeta, + SolanaTx, + | SolanaTxType.SwimCreateSplTokenAccount + | SolanaTxType.WormholeVerifySignatures + | SolanaTxType.WormholePostVaa + | SolanaTxType.SwimCompleteNativeWithPayload + | SolanaTxType.SwimProcessSwimPayload + >, any, unknown > { - const completeNativeWithPayloadTx = await this.completeNativeWithPayload({ - wallet, - interactionId, + const splTokenMintAddresses = SUPPORTED_TOKEN_PROJECT_IDS.map( + (tokenProjectId) => + getTokenDetails(this.chainConfig, tokenProjectId).address, + ); + const createSplTokenAccountsGenerator = + this.generateCreateSplTokenAccountTxs(wallet, splTokenMintAddresses); + for await (const result of createSplTokenAccountsGenerator) { + yield result; + } + const isTransferCompleted = await getIsTransferCompletedSolana( + this.chainConfig.wormhole.portal, signedVaa, - sourceWormholeChainId, - sourceChainConfig, - }); - if (completeNativeWithPayloadTx !== null) { + this.connection, + ); + if (!isTransferCompleted) { + const completeWormholeMessageGenerator = + this.generateCompleteWormholeMessageTxs({ + wallet, + interactionId, + vaa: signedVaa, + auxiliarySigner, + }); + for await (const result of completeWormholeMessageGenerator) { + yield result; + } + const completeNativeWithPayloadTx = await this.completeNativeWithPayload({ + wallet, + interactionId, + signedVaa, + sourceWormholeChainId, + sourceChainConfig, + }); yield { tx: completeNativeWithPayloadTx, type: SolanaTxType.SwimCompleteNativeWithPayload, @@ -431,9 +467,6 @@ export class SolanaClient extends Client< if (senderPublicKey === null) { throw new Error("Missing Solana wallet"); } - if (!isSupportedTokenProjectId(sourceTokenId)) { - throw new Error("Invalid source token id"); - } const sourceTokenDetails = getTokenDetails(this.chainConfig, sourceTokenId); const inputAmountAtomic = humanToAtomic( @@ -582,6 +615,40 @@ export class SolanaClient extends Client< return this.sendAndConfirmTx(wallet.signTransaction.bind(wallet), tx); } + public async *generateCreateSplTokenAccountTxs( + wallet: SolanaWalletAdapter, + splTokenMintAddresses: readonly string[], + ): AsyncGenerator< + TxGeneratorResult< + ParsedTransactionWithMeta, + SolanaTx, + SolanaTxType.SwimCreateSplTokenAccount + > + > { + if (!wallet.publicKey) { + throw new Error("No Solana wallet connected"); + } + for (const splTokenMintAddress of splTokenMintAddresses) { + const existingAccount = await this.connection.getTokenAccountsByOwner( + wallet.publicKey, + { + mint: new PublicKey(splTokenMintAddress), + }, + ); + if (existingAccount.value.length === 0) { + const txId = await this.createSplTokenAccount( + wallet, + splTokenMintAddress, + ); + const tx = await this.getTx(txId); + yield { + tx, + type: SolanaTxType.SwimCreateSplTokenAccount, + }; + } + } + } + public async getTokenAccountWithRetry( mint: string, owner: string, @@ -979,15 +1046,7 @@ export class SolanaClient extends Client< readonly sourceWormholeChainId: ChainId; readonly sourceChainConfig: ChainConfig; readonly signedVaa: Buffer; - }): Promise { - const completed = await getIsTransferCompletedSolana( - this.chainConfig.wormhole.portal, - signedVaa, - this.connection, - ); - if (completed) { - return null; - } + }): Promise { const walletPublicKey = wallet.publicKey; if (walletPublicKey === null) { throw new Error("Missing Solana wallet public key"); diff --git a/packages/solana/src/protocol.ts b/packages/solana/src/protocol.ts index 84e4fa96e..0bceff795 100644 --- a/packages/solana/src/protocol.ts +++ b/packages/solana/src/protocol.ts @@ -44,6 +44,7 @@ export enum SolanaTxType { WormholePostVaa = "wormhole:postVaa", SwimPropellerAdd = "swimPropeller:add", SwimPropellerTransfer = "swimPropeller:transfer", + SwimCreateSplTokenAccount = "swim:createSplTokenAccount", SwimCompleteNativeWithPayload = "swim:completeNativeWithPayload", SwimProcessSwimPayload = "swim:processSwimPayload", }