import type { u64 } from "@saberhq/token-utils"; import type { PublicKey, TransactionInstruction } from "@solana/web3.js"; import type { Fees, RawFees } from "../state/index.js"; import { encodeFees, ZERO_FEES } from "../state/index.js"; import type { StableSwapConfig } from "./common.js"; import { buildInstruction } from "./common.js"; import { DepositIXLayout, InitializeSwapIXLayout, SwapIXLayout, WithdrawIXLayout, WithdrawOneIXLayout, } from "./layouts.js"; /** * Instruction enum. */ export enum StableSwapInstruction { INITIALIZE = 0, SWAP = 1, DEPOSIT = 2, WITHDRAW = 3, WITHDRAW_ONE = 4, } /** * Info about the tokens to swap. */ export interface SwapTokenInfo { /** * The account that admin fees go to. */ adminFeeAccount: PublicKey; /** * Mint of the token. */ mint: PublicKey; /** * This swap's token reserves. */ reserve: PublicKey; } export interface InitializeSwapInstruction { config: StableSwapConfig; /** * Account that can manage the swap. */ adminAccount: PublicKey; tokenA: SwapTokenInfo; tokenB: SwapTokenInfo; poolTokenMint: PublicKey; /** * Destination account for the initial LP tokens. */ destinationPoolTokenAccount: PublicKey; nonce: number; ampFactor: u64; fees?: Fees; isPaused?: boolean; } export interface SwapInstruction { config: StableSwapConfig; /** * User source authority */ userAuthority: PublicKey; /** * User source token account */ userSource: PublicKey; /** * Swap source token account */ poolSource: PublicKey; /** * Swap destination token account */ poolDestination: PublicKey; /** * User destination token account */ userDestination: PublicKey; adminDestination: PublicKey; amountIn: u64; minimumAmountOut: u64; } export interface DepositInstruction { config: StableSwapConfig; /** * Authority for user account */ userAuthority: PublicKey; /** * Depositor account for token A */ sourceA: PublicKey; /** * Depositor account for token B */ sourceB: PublicKey; tokenAccountA: PublicKey; tokenAccountB: PublicKey; poolTokenMint: PublicKey; poolTokenAccount: PublicKey; tokenAmountA: u64; tokenAmountB: u64; minimumPoolTokenAmount: u64; } export interface WithdrawInstruction { config: StableSwapConfig; /** * User source authority */ userAuthority: PublicKey; poolMint: PublicKey; tokenAccountA: PublicKey; tokenAccountB: PublicKey; adminFeeAccountA: PublicKey; adminFeeAccountB: PublicKey; /** * Account which is the source of the pool tokens * that is; the user's pool token account */ sourceAccount: PublicKey; userAccountA: PublicKey; userAccountB: PublicKey; poolTokenAmount: u64; minimumTokenA: u64; minimumTokenB: u64; } export interface WithdrawOneInstruction { config: StableSwapConfig; /** * User source authority */ userAuthority: PublicKey; poolMint: PublicKey; /** * User account that holds the LP tokens */ sourceAccount: PublicKey; /** * Pool account that holds the tokens to withdraw */ baseTokenAccount: PublicKey; /** * Pool account that holds the other token */ quoteTokenAccount: PublicKey; /** * User base token account to withdraw to */ destinationAccount: PublicKey; /** * Admin base token account to send fees to */ adminDestinationAccount: PublicKey; /** * Amount of pool tokens to burn. User receives an output of token a * or b based on the percentage of the pool tokens that are returned. */ poolTokenAmount: u64; /** * Minimum amount of base tokens to receive, prevents excessive slippage */ minimumTokenAmount: u64; } export const initializeSwapInstructionRaw = ({ config, adminAccount, tokenA: { adminFeeAccount: adminFeeAccountA, mint: tokenMintA, reserve: tokenAccountA, }, tokenB: { adminFeeAccount: adminFeeAccountB, mint: tokenMintB, reserve: tokenAccountB, }, poolTokenMint, destinationPoolTokenAccount, nonce, ampFactor, fees, }: Omit & { fees: RawFees; }): TransactionInstruction => { const keys = [ { pubkey: config.swapAccount, isSigner: false, isWritable: false }, { pubkey: config.authority, isSigner: false, isWritable: false }, { pubkey: adminAccount, isSigner: false, isWritable: false }, { pubkey: adminFeeAccountA, isSigner: false, isWritable: false }, { pubkey: adminFeeAccountB, isSigner: false, isWritable: false }, { pubkey: tokenMintA, isSigner: false, isWritable: false }, { pubkey: tokenAccountA, isSigner: false, isWritable: false }, { pubkey: tokenMintB, isSigner: false, isWritable: false }, { pubkey: tokenAccountB, isSigner: false, isWritable: false }, { pubkey: poolTokenMint, isSigner: false, isWritable: true }, { pubkey: destinationPoolTokenAccount, isSigner: false, isWritable: true }, { pubkey: config.tokenProgramID, isSigner: false, isWritable: false }, ]; const data = Buffer.alloc(InitializeSwapIXLayout.span); InitializeSwapIXLayout.encode( { instruction: StableSwapInstruction.INITIALIZE, // InitializeSwap instruction nonce, ampFactor: ampFactor.toBuffer(), fees, }, data, ); return buildInstruction({ config, keys, data, }); }; export const initializeSwapInstruction = ({ fees = ZERO_FEES, ...args }: InitializeSwapInstruction): TransactionInstruction => { return initializeSwapInstructionRaw({ ...args, fees: encodeFees(fees) }); }; export const swapInstruction = ({ config, userAuthority, userSource, poolSource, poolDestination, userDestination, adminDestination, amountIn, minimumAmountOut, }: SwapInstruction): TransactionInstruction => { const data = Buffer.alloc(SwapIXLayout.span); SwapIXLayout.encode( { instruction: StableSwapInstruction.SWAP, // Swap instruction amountIn: amountIn.toBuffer(), minimumAmountOut: minimumAmountOut.toBuffer(), }, data, ); const keys = [ { pubkey: config.swapAccount, isSigner: false, isWritable: false }, { pubkey: config.authority, isSigner: false, isWritable: false }, { pubkey: userAuthority, isSigner: true, isWritable: false }, { pubkey: userSource, isSigner: false, isWritable: true }, { pubkey: poolSource, isSigner: false, isWritable: true }, { pubkey: poolDestination, isSigner: false, isWritable: true }, { pubkey: userDestination, isSigner: false, isWritable: true }, { pubkey: adminDestination, isSigner: false, isWritable: true }, { pubkey: config.tokenProgramID, isSigner: false, isWritable: false }, ]; return buildInstruction({ config, keys, data, }); }; export const depositInstruction = ({ config, userAuthority, sourceA, sourceB, tokenAccountA, tokenAccountB, poolTokenMint, poolTokenAccount, tokenAmountA, tokenAmountB, minimumPoolTokenAmount, }: DepositInstruction): TransactionInstruction => { const data = Buffer.alloc(DepositIXLayout.span); DepositIXLayout.encode( { instruction: StableSwapInstruction.DEPOSIT, // Deposit instruction tokenAmountA: tokenAmountA.toBuffer(), tokenAmountB: tokenAmountB.toBuffer(), minimumPoolTokenAmount: minimumPoolTokenAmount.toBuffer(), }, data, ); const keys = [ { pubkey: config.swapAccount, isSigner: false, isWritable: false }, { pubkey: config.authority, isSigner: false, isWritable: false }, { pubkey: userAuthority, isSigner: true, isWritable: false }, { pubkey: sourceA, isSigner: false, isWritable: true }, { pubkey: sourceB, isSigner: false, isWritable: true }, { pubkey: tokenAccountA, isSigner: false, isWritable: true }, { pubkey: tokenAccountB, isSigner: false, isWritable: true }, { pubkey: poolTokenMint, isSigner: false, isWritable: true }, { pubkey: poolTokenAccount, isSigner: false, isWritable: true }, { pubkey: config.tokenProgramID, isSigner: false, isWritable: false }, ]; return buildInstruction({ config, keys, data, }); }; export const withdrawInstruction = ({ config, userAuthority, poolMint, sourceAccount, tokenAccountA, tokenAccountB, userAccountA, userAccountB, adminFeeAccountA, adminFeeAccountB, poolTokenAmount, minimumTokenA, minimumTokenB, }: WithdrawInstruction): TransactionInstruction => { const data = Buffer.alloc(WithdrawIXLayout.span); WithdrawIXLayout.encode( { instruction: StableSwapInstruction.WITHDRAW, // Withdraw instruction poolTokenAmount: poolTokenAmount.toBuffer(), minimumTokenA: minimumTokenA.toBuffer(), minimumTokenB: minimumTokenB.toBuffer(), }, data, ); const keys = [ { pubkey: config.swapAccount, isSigner: false, isWritable: false }, { pubkey: config.authority, isSigner: false, isWritable: false }, { pubkey: userAuthority, isSigner: true, isWritable: false }, { pubkey: poolMint, isSigner: false, isWritable: true }, { pubkey: sourceAccount, isSigner: false, isWritable: true }, { pubkey: tokenAccountA, isSigner: false, isWritable: true }, { pubkey: tokenAccountB, isSigner: false, isWritable: true }, { pubkey: userAccountA, isSigner: false, isWritable: true }, { pubkey: userAccountB, isSigner: false, isWritable: true }, { pubkey: adminFeeAccountA, isSigner: false, isWritable: true }, { pubkey: adminFeeAccountB, isSigner: false, isWritable: true }, { pubkey: config.tokenProgramID, isSigner: false, isWritable: false }, ]; return buildInstruction({ config, keys, data, }); }; export const withdrawOneInstruction = ({ config, userAuthority, poolMint, sourceAccount, baseTokenAccount, quoteTokenAccount, destinationAccount, adminDestinationAccount, poolTokenAmount, minimumTokenAmount, }: WithdrawOneInstruction): TransactionInstruction => { const data = Buffer.alloc(WithdrawOneIXLayout.span); WithdrawOneIXLayout.encode( { instruction: StableSwapInstruction.WITHDRAW_ONE, // Withdraw instruction poolTokenAmount: poolTokenAmount.toBuffer(), minimumTokenAmount: minimumTokenAmount.toBuffer(), }, data, ); const keys = [ { pubkey: config.swapAccount, isSigner: false, isWritable: false }, { pubkey: config.authority, isSigner: false, isWritable: false }, { pubkey: userAuthority, isSigner: true, isWritable: false }, { pubkey: poolMint, isSigner: false, isWritable: true }, { pubkey: sourceAccount, isSigner: false, isWritable: true }, { pubkey: baseTokenAccount, isSigner: false, isWritable: true }, { pubkey: quoteTokenAccount, isSigner: false, isWritable: true }, { pubkey: destinationAccount, isSigner: false, isWritable: true }, { pubkey: adminDestinationAccount, isSigner: false, isWritable: true }, { pubkey: config.tokenProgramID, isSigner: false, isWritable: false }, ]; return buildInstruction({ config, keys, data, }); };