UNPKG

@robertprp/intents-sdk

Version:

Shogun Network Intent-based cross-chain swaps SDK

265 lines (221 loc) 9.94 kB
import { address, appendTransactionMessageInstructions, compileTransactionMessage, createNoopSigner, createSolanaRpc, createTransactionMessage, fetchEncodedAccount, getAddressEncoder, getCompiledTransactionMessageEncoder, getProgramDerivedAddress, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, type Address, type IInstruction, type ReadonlyUint8Array, type TransactionMessageBytes, } from '@solana/kit'; import { getDefaultSolanaRPC } from './client.js'; import { fetchMaybeLimitOrder, getCancelLimitOrderInstruction } from './generated/single-chain/index.js'; import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, fetchMint, getCreateAssociatedTokenInstructionAsync, } from '@solana-program/token'; import { CROSS_CHAIN_GUARD_ADDRESSES, SINGLE_CHAIN_GUARD_ADDRESSES, SOLANA_MINT_TOKEN } from '../../constants.js'; import { ChainID } from '../../chains.js'; import { fetchMaybeOrder, getCancelOrderInstruction } from './generated/cross-chain/index.js'; export async function cancelSingleChainOrderInstructions( orderAddress: string, options?: { rpcUrl?: string }, ): Promise<IInstruction[]> { const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC(); const orderId = address(orderAddress); const chainOrder = await fetchMaybeLimitOrder(rpc, orderId); if (!chainOrder.exists) { throw new Error(`Order with address ${orderAddress} not found`); } const instructions: IInstruction[] = []; const orderUserAddress = chainOrder.data.user; const tokenInMint = chainOrder.data.tokenInMint; const tokenMintProgram = await fetchMint(rpc, tokenInMint); const addressEncoder = getAddressEncoder(); const [tokenInProgramAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(orderUserAddress), addressEncoder.encode(tokenMintProgram.programAddress), addressEncoder.encode(tokenInMint), ], }); const guardAddress = SINGLE_CHAIN_GUARD_ADDRESSES[ChainID.Solana] as Address; const [guardProgramAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ // Owner addressEncoder.encode(guardAddress), // Token program addressEncoder.encode(tokenMintProgram.programAddress), // mint address addressEncoder.encode(tokenInMint), ], }); const userTokenInAccount = await fetchEncodedAccount(rpc, tokenInProgramAccount); if (!userTokenInAccount.exists) { const createTokenIx = await getCreateAssociatedTokenInstructionAsync({ mint: tokenInMint, owner: orderUserAddress, payer: createNoopSigner(orderUserAddress), tokenProgram: tokenMintProgram.programAddress, }); instructions.push(createTokenIx); } const cancelLimitOrderIx = getCancelLimitOrderInstruction({ user: createNoopSigner(orderUserAddress), order: orderId, guard: guardAddress, tokenInMint: chainOrder.data.tokenInMint, userTokenInAccount: userTokenInAccount.address, guardTokenInAccount: guardProgramAccount, tokenInProgram: tokenMintProgram.programAddress, }); instructions.push(cancelLimitOrderIx); return instructions; } export async function cancelCrossChainOrderInstructionsAsBytes( orderAddress: string, signerAddress: string, options?: { rpcUrl?: string }, ): Promise<{ versionedMessageBytes: ReadonlyUint8Array }> { const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC(); const instructions = await cancelCrossChainOrderInstructions(orderAddress, options); const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send(); const txMessage = pipe( createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayerSigner(createNoopSigner(address(signerAddress)), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions, tx), ); const compiledTxMessage = compileTransactionMessage(txMessage); const txBytes = getCompiledTransactionMessageEncoder().encode(compiledTxMessage); return { versionedMessageBytes: txBytes }; } export async function cancelSingleChainOrderInstructionsAsBytes( orderAddress: string, signerAddress: string, options?: { rpcUrl?: string }, ): Promise<{ versionedMessageBytes: ReadonlyUint8Array }> { const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC(); const instructions = await cancelSingleChainOrderInstructions(orderAddress, options); const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send(); const txMessage = pipe( createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayerSigner(createNoopSigner(address(signerAddress)), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions, tx), ); const compiledTxMessage = compileTransactionMessage(txMessage); const txBytes = getCompiledTransactionMessageEncoder().encode(compiledTxMessage) as TransactionMessageBytes; return { versionedMessageBytes: txBytes }; } export async function cancelCrossChainOrderInstructions( orderAddress: string, options?: { rpcUrl?: string }, ): Promise<IInstruction[]> { const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC(); const orderId = address(orderAddress); const chainOrder = await fetchMaybeOrder(rpc, orderId); if (!chainOrder.exists) { throw new Error(`Order with address ${orderAddress} not found`); } const instructions: IInstruction[] = []; const orderUserAddress = chainOrder.data.user; const guardAddress = CROSS_CHAIN_GUARD_ADDRESSES[ChainID.Solana] as Address; const addressEncoder = getAddressEncoder(); const isRecoveringTokenIn = chainOrder.data.lockedStablecoins === 0n; const recoverTokenMint = isRecoveringTokenIn ? chainOrder.data.tokenInMint : address(SOLANA_MINT_TOKEN.mint); const recoverTokenMintProgram = await fetchMint(rpc, recoverTokenMint); const [userRecoveredTokenAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(orderUserAddress), addressEncoder.encode(recoverTokenMintProgram.programAddress), addressEncoder.encode(recoverTokenMint), ], }); const [guardRecoveredTokenAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(guardAddress), addressEncoder.encode(recoverTokenMintProgram.programAddress), addressEncoder.encode(recoverTokenMint), ], }); // Check if user's recovered token account exists, create if needed const userRecoveredTokenAccountInfo = await fetchEncodedAccount(rpc, userRecoveredTokenAccount); if (!userRecoveredTokenAccountInfo.exists) { const createRecoveredTokenAccountIx = await getCreateAssociatedTokenInstructionAsync({ payer: createNoopSigner(orderUserAddress), ata: userRecoveredTokenAccount, owner: orderUserAddress, mint: recoverTokenMint, tokenProgram: recoverTokenMintProgram.programAddress, }); instructions.push(createRecoveredTokenAccountIx); } let userCollateralTokenAccount: Address | undefined; const willClaimCollateral = chainOrder.data.lockedCollateral > 0n; if (willClaimCollateral) { const collateralTokenMint = address(SOLANA_MINT_TOKEN.mint); const collateralTokenMintProgram = await fetchMint(rpc, collateralTokenMint); const [userCollateralAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(orderUserAddress), addressEncoder.encode(collateralTokenMintProgram.programAddress), addressEncoder.encode(collateralTokenMint), ], }); userCollateralTokenAccount = userCollateralAccount; // Check if user's collateral token account exists, create if needed const userCollateralTokenAccountInfo = await fetchEncodedAccount(rpc, userCollateralTokenAccount); if (!userCollateralTokenAccountInfo.exists) { const createCollateralTokenAccountIx = await getCreateAssociatedTokenInstructionAsync({ payer: createNoopSigner(orderUserAddress), ata: userCollateralTokenAccount, owner: orderUserAddress, mint: collateralTokenMint, tokenProgram: collateralTokenMintProgram.programAddress, }); instructions.push(createCollateralTokenAccountIx); } } const collateralTokenMint = address(SOLANA_MINT_TOKEN.mint); const collateralTokenMintProgram = await fetchMint(rpc, collateralTokenMint); const [guardCollateralTokenAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(guardAddress), addressEncoder.encode(collateralTokenMintProgram.programAddress), addressEncoder.encode(collateralTokenMint), ], }); // Create the cancel order instruction const cancelOrderIx = getCancelOrderInstruction({ user: createNoopSigner(orderUserAddress), order: orderId, guard: guardAddress, recoveredTokenMint: recoverTokenMint, userRecoveredTokenAccount: userRecoveredTokenAccount, guardRecoveredTokenAccount: guardRecoveredTokenAccount, recoveredTokenProgram: recoverTokenMintProgram.programAddress, collateralTokenMint: collateralTokenMint, userCollateralTokenAccount: userCollateralTokenAccount, guardCollateralTokenAccount: guardCollateralTokenAccount, collateralTokenProgram: collateralTokenMintProgram.programAddress, }); instructions.push(cancelOrderIx); return instructions; }