Skip to content

Commit

Permalink
chore(ui): move postVaa to SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
ramenuncle committed Oct 26, 2022
1 parent 2b137da commit 75a19f3
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -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`);
Expand Down Expand Up @@ -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}`);
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/models/swim/interactionStateV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
105 changes: 82 additions & 23 deletions packages/solana/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -370,27 +373,60 @@ export class SolanaClient extends Client<
sourceChainConfig,
targetTokenNumber,
minimumOutputAmount,
}: {
auxiliarySigner = Keypair.generate(),
}: WithOptionalAuxiliarySigner<{
readonly wallet: SolanaWalletAdapter;
readonly interactionId: string;
readonly signedVaa: Buffer;
readonly sourceWormholeChainId: ChainId;
readonly sourceChainConfig: ChainConfig;
readonly targetTokenNumber: number;
readonly minimumOutputAmount: string;
}): AsyncGenerator<
TxGeneratorResult<ParsedTransactionWithMeta, SolanaTx, SolanaTxType>,
}>): 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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -979,15 +1046,7 @@ export class SolanaClient extends Client<
readonly sourceWormholeChainId: ChainId;
readonly sourceChainConfig: ChainConfig;
readonly signedVaa: Buffer;
}): Promise<SolanaTx | null> {
const completed = await getIsTransferCompletedSolana(
this.chainConfig.wormhole.portal,
signedVaa,
this.connection,
);
if (completed) {
return null;
}
}): Promise<SolanaTx> {
const walletPublicKey = wallet.publicKey;
if (walletPublicKey === null) {
throw new Error("Missing Solana wallet public key");
Expand Down
1 change: 1 addition & 0 deletions packages/solana/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
Expand Down

0 comments on commit 75a19f3

Please sign in to comment.