UNPKG

@robertprp/intents-sdk

Version:

Shogun Network Intent-based cross-chain swaps SDK

266 lines (217 loc) 8.68 kB
import { getTransferSolInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system'; import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, fetchMint, getCreateAssociatedTokenInstructionAsync, getSyncNativeInstruction, } from '@solana-program/token'; import { type Address, address, addSignersToTransactionMessage, appendTransactionMessageInstructions, createNoopSigner, createSolanaRpc, createTransactionMessage, fetchEncodedAccount, generateKeyPairSigner, getAddressEncoder, getProgramDerivedAddress, getTransactionCodec, type IInstruction, partiallySignTransactionMessageWithSigners, pipe, type ReadonlyUint8Array, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, } from '@solana/kit'; import { ChainID } from '../../chains.js'; import { CROSS_CHAIN_GUARD_ADDRESSES, NATIVE_SOLANA_TOKEN_ADDRESS, SINGLE_CHAIN_GUARD_ADDRESSES, WRAPPED_SOL_MINT_ADDRESS, } from '../../constants.js'; import type { CrossChainOrder } from '../orders/cross-chain.js'; import type { SingleChainOrder } from '../orders/single-chain.js'; import { getDefaultSolanaRPC } from './client.js'; import { getCreateOrderInstruction } from './generated/cross-chain/index.js'; import { getCreateLimitOrderInstructionAsync } from './generated/single-chain/index.js'; import { genSecretHashAndNumber } from './utils.js'; export type SolanaOrderInstructionResult = { // Order address orderAddress: string; // Encoded transaction bytes to be later deserialized into a transaction type txBytes: ReadonlyUint8Array; }; export async function getSolanaSingleChainOrderInstructions( order: SingleChainOrder, options?: { rpcUrl?: string }, ): Promise<SolanaOrderInstructionResult & { secretNumber: string }> { const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC(); const orderSigner = await generateKeyPairSigner(); const signer = createNoopSigner(order.user as Address); let tokenInMint = address(order.tokenIn); const { secretHash, secretNumber } = genSecretHashAndNumber(order); const orderUserAddress = address(order.user); const spendingNative = tokenInMint === NATIVE_SOLANA_TOKEN_ADDRESS; if (spendingNative) { tokenInMint = WRAPPED_SOL_MINT_ADDRESS; } const tokenMintProgram = await fetchMint(rpc, tokenInMint); const guardAddress = address(SINGLE_CHAIN_GUARD_ADDRESSES[ChainID.Solana]); const addressEncoder = getAddressEncoder(); const instructions: Array<IInstruction> = []; const [tokenInProgramAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(orderUserAddress), addressEncoder.encode(tokenMintProgram.programAddress), addressEncoder.encode(tokenInMint), ], }); const userTokenInAccount = await fetchEncodedAccount(rpc, tokenInProgramAccount); if (!userTokenInAccount.exists) { const createAccountIx = await getCreateAssociatedTokenInstructionAsync({ payer: signer, ata: tokenInProgramAccount, owner: orderUserAddress, mint: tokenMintProgram.address, tokenProgram: tokenMintProgram.programAddress, }); instructions.push(createAccountIx); } if (spendingNative) { const transferIx = getTransferSolInstruction({ amount: order.amountIn, destination: userTokenInAccount.address, source: signer, }); instructions.push(transferIx); const syncNativeIx = getSyncNativeInstruction({ account: userTokenInAccount.address, }); instructions.push(syncNativeIx); } const createSingleChainLimitOrderIx = await getCreateLimitOrderInstructionAsync({ user: signer, order: orderSigner, guard: guardAddress, tokenInMint: tokenInMint, userTokenInAccount: userTokenInAccount.address, tokenInProgram: tokenMintProgram.programAddress, amountIn: order.amountIn, deadline: order.deadline, amountOutMin: order.amountOutMin, secretHash: Uint8Array.from(secretHash), extraTransfersAmounts: [], // TODO }); instructions.push(createSingleChainLimitOrderIx); const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send(); const txMessage = pipe( createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayerSigner(signer, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions, tx), (tx) => addSignersToTransactionMessage([orderSigner], tx), ); const partiallySignedTransaction = await partiallySignTransactionMessageWithSigners(txMessage); const txBytes = getTransactionCodec().encode(partiallySignedTransaction); return { orderAddress: orderSigner.address, txBytes, secretNumber, }; } export async function getSolanaCrossChainOrderInstructions( order: CrossChainOrder, options?: { rpcUrl?: string }, ): Promise<SolanaOrderInstructionResult> { const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC(); const orderSigner = await generateKeyPairSigner(); const signer = createNoopSigner(order.user as Address); let tokenInMint = address(order.sourceTokenAddress); const orderUserAddress = address(order.user); const spendingNative = tokenInMint === NATIVE_SOLANA_TOKEN_ADDRESS; if (spendingNative) { tokenInMint = WRAPPED_SOL_MINT_ADDRESS; } const tokenMintProgram = await fetchMint(rpc, tokenInMint); const guardAddress = address(CROSS_CHAIN_GUARD_ADDRESSES[ChainID.Solana]); const addressEncoder = getAddressEncoder(); const [tokenInProgramAccount] = await getProgramDerivedAddress({ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, seeds: [ addressEncoder.encode(orderUserAddress), addressEncoder.encode(tokenMintProgram.programAddress), addressEncoder.encode(tokenInMint), ], }); 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 instructions: Array<IInstruction> = []; const userTokenInAccount = await fetchEncodedAccount(rpc, tokenInProgramAccount); if (spendingNative) { order.sourceTokenAddress = WRAPPED_SOL_MINT_ADDRESS; if (!userTokenInAccount.exists) { const createAccountIx = await getCreateAssociatedTokenInstructionAsync({ payer: signer, ata: tokenInProgramAccount, owner: orderUserAddress, mint: tokenMintProgram.address, tokenProgram: tokenMintProgram.programAddress, }); instructions.push(createAccountIx); } const transferIx = getTransferSolInstruction({ source: signer, destination: userTokenInAccount.address, amount: order.sourceTokenAmount, }); const syncNativeIx = getSyncNativeInstruction({ account: userTokenInAccount.address, }); instructions.push(transferIx); instructions.push(syncNativeIx); } const executionHashUint8Array = order.executionDetailsHashToBytes(); const createOrderIx = getCreateOrderInstruction({ user: signer, order: orderSigner, guard: address(CROSS_CHAIN_GUARD_ADDRESSES[ChainID.Solana]), systemProgram: SYSTEM_PROGRAM_ADDRESS, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, tokenInMint: tokenInMint, userTokenInAccount: userTokenInAccount.address, guardTokenInAccount: guardProgramAccount, tokenInProgram: tokenMintProgram.programAddress, amountIn: order.sourceTokenAmount, deadline: order.deadline, executionDetailsHash: executionHashUint8Array, minStablecoinsAmount: order.minStablecoinAmount, }); instructions.push(createOrderIx); const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send(); const txMessage = pipe( createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayerSigner(signer, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions, tx), (tx) => addSignersToTransactionMessage([orderSigner], tx), ); const partiallySignedTransaction = await partiallySignTransactionMessageWithSigners(txMessage); const txBytes = getTransactionCodec().encode(partiallySignedTransaction); return { orderAddress: orderSigner.address, txBytes: Uint8Array.from(txBytes), }; }