UNPKG

@mercurial-finance/dynamic-amm-sdk

Version:

Mercurial Vaults SDK is a typescript library that allows you to interact with Mercurial v2's AMM.

675 lines 35.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateLockAmounts = exports.DepegType = exports.deriveProtocolTokenFee = exports.deriveConfigPda = exports.computeTokenMultiplier = exports.calculateSwapQuote = exports.calculateSwapQuoteForGoingToCreateMemecoinPool = exports.getDepegAccounts = exports.getStakePubkey = exports.calculateMaxSwapOutAmount = exports.calculateUnclaimedLockEscrowFee = exports.calculateTradingFee = exports.calculateProtocolTradingFee = exports.calculatePoolInfo = exports.computeActualDepositAmount = exports.getOnchainTime = exports.deserializeAccount = exports.unwrapSOLInstruction = exports.wrapSOLInstruction = exports.deriveLockEscrowPda = exports.getOrCreateATAInstruction = exports.getAssociatedTokenAccount = exports.getMinAmountWithSlippage = exports.getMaxAmountWithSlippage = exports.createProgram = void 0; exports.getTokensMintFromPoolAddress = getTokensMintFromPoolAddress; exports.deriveMintMetadata = deriveMintMetadata; exports.deriveCustomizablePermissionlessConstantProductPoolAddress = deriveCustomizablePermissionlessConstantProductPoolAddress; exports.derivePoolAddressWithConfig = derivePoolAddressWithConfig; exports.derivePoolAddress = derivePoolAddress; exports.checkPoolExists = checkPoolExists; exports.checkPoolWithConfigsExists = checkPoolWithConfigsExists; exports.chunks = chunks; exports.chunkedFetchMultiplePoolAccount = chunkedFetchMultiplePoolAccount; exports.chunkedGetMultipleAccountInfos = chunkedGetMultipleAccountInfos; exports.encodeCurveType = encodeCurveType; exports.getSecondKey = getSecondKey; exports.getFirstKey = getFirstKey; exports.getTradeFeeBpsBuffer = getTradeFeeBpsBuffer; exports.generateCurveType = generateCurveType; exports.createMint = createMint; exports.createTransactions = createTransactions; const vault_sdk_1 = require("@mercurial-finance/vault-sdk"); const m3m3_1 = require("@meteora-ag/m3m3"); const anchor_1 = require("@coral-xyz/anchor"); const spl_token_1 = require("@solana/spl-token"); const web3_js_1 = require("@solana/web3.js"); const invariant_1 = __importDefault(require("invariant")); const constants_1 = require("./constants"); const curve_1 = require("./curve"); const types_1 = require("./types"); const idl_1 = require("./idl"); const decimal_js_1 = __importDefault(require("decimal.js")); const mpl_token_metadata_1 = require("@metaplex-foundation/mpl-token-metadata"); const createProgram = (connection, programId) => { const provider = new anchor_1.AnchorProvider(connection, {}, anchor_1.AnchorProvider.defaultOptions()); const ammProgram = new anchor_1.Program(idl_1.IDL, programId ?? constants_1.PROGRAM_ID, provider); const vaultProgram = new anchor_1.Program(vault_sdk_1.IDL, vault_sdk_1.PROGRAM_ID, provider); const stakeForFeeProgram = new anchor_1.Program(m3m3_1.IDL, m3m3_1.STAKE_FOR_FEE_PROGRAM_ID, provider); return { provider, ammProgram, vaultProgram, stakeForFeeProgram }; }; exports.createProgram = createProgram; /** * It takes an amount and a slippage rate, and returns the maximum amount that can be received with * that slippage rate * @param {BN} amount - The amount of tokens you want to buy. * @param {number} slippageRate - The maximum percentage of slippage you're willing to accept. (Max to 2 decimal place) * @returns The maximum amount of tokens that can be bought with the given amount of ETH, given the * slippage rate. */ const getMaxAmountWithSlippage = (amount, slippageRate) => { const slippage = ((100 + slippageRate) / 100) * 10000; return amount.mul(new anchor_1.BN(slippage)).div(new anchor_1.BN(10000)); }; exports.getMaxAmountWithSlippage = getMaxAmountWithSlippage; /** * It takes an amount and a slippage rate, and returns the minimum amount that will be received after * slippage * @param {BN} amount - The amount of tokens you want to sell. * @param {number} slippageRate - The percentage of slippage you're willing to accept. (Max to 2 decimal place) * @returns The minimum amount that can be received after slippage is applied. */ const getMinAmountWithSlippage = (amount, slippageRate) => { const slippage = ((100 - slippageRate) / 100) * 10000; return amount.mul(new anchor_1.BN(slippage)).div(new anchor_1.BN(10000)); }; exports.getMinAmountWithSlippage = getMinAmountWithSlippage; const getAssociatedTokenAccount = (tokenMint, owner) => { return (0, spl_token_1.getAssociatedTokenAddressSync)(tokenMint, owner, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); }; exports.getAssociatedTokenAccount = getAssociatedTokenAccount; const getOrCreateATAInstruction = async (tokenMint, owner, connection, payer) => { let toAccount; try { toAccount = await (0, exports.getAssociatedTokenAccount)(tokenMint, owner); const account = await connection.getAccountInfo(toAccount); if (!account) { const ix = (0, spl_token_1.createAssociatedTokenAccountInstruction)(payer || owner, toAccount, owner, tokenMint, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); return [toAccount, ix]; } return [toAccount, undefined]; } catch (e) { /* handle error */ console.error('Error::getOrCreateATAInstruction', e); throw e; } }; exports.getOrCreateATAInstruction = getOrCreateATAInstruction; const deriveLockEscrowPda = (pool, owner, ammProgram) => { return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.SEEDS.LOCK_ESCROW), pool.toBuffer(), owner.toBuffer()], ammProgram); }; exports.deriveLockEscrowPda = deriveLockEscrowPda; const wrapSOLInstruction = (from, to, amount) => { return [ web3_js_1.SystemProgram.transfer({ fromPubkey: from, toPubkey: to, lamports: amount, }), new web3_js_1.TransactionInstruction({ keys: [ { pubkey: to, isSigner: false, isWritable: true, }, ], data: Buffer.from(new Uint8Array([17])), programId: spl_token_1.TOKEN_PROGRAM_ID, }), ]; }; exports.wrapSOLInstruction = wrapSOLInstruction; const unwrapSOLInstruction = async (owner) => { const wSolATAAccount = await (0, exports.getAssociatedTokenAccount)(spl_token_1.NATIVE_MINT, owner); if (wSolATAAccount) { const closedWrappedSolInstruction = (0, spl_token_1.createCloseAccountInstruction)(wSolATAAccount, owner, owner, []); return closedWrappedSolInstruction; } return null; }; exports.unwrapSOLInstruction = unwrapSOLInstruction; const deserializeAccount = (data) => { if (data == undefined || data.length == 0) { return undefined; } const accountInfo = spl_token_1.AccountLayout.decode(data); return accountInfo; }; exports.deserializeAccount = deserializeAccount; const getOnchainTime = async (connection) => { const parsedClock = await connection.getParsedAccountInfo(web3_js_1.SYSVAR_CLOCK_PUBKEY); const parsedClockAccount = parsedClock.value.data.parsed; const currentTime = parsedClockAccount.info.unixTimestamp; return currentTime; }; exports.getOnchainTime = getOnchainTime; /** * Compute "actual" amount deposited to vault (precision loss) * @param depositAmount * @param beforeAmount * @param vaultLpBalance * @param vaultLpSupply * @param vaultTotalAmount * @returns */ const computeActualDepositAmount = (depositAmount, beforeAmount, vaultLpBalance, vaultLpSupply, vaultTotalAmount) => { if (depositAmount.eq(new anchor_1.BN(0))) return depositAmount; const vaultLpMinted = depositAmount.mul(vaultLpSupply).div(vaultTotalAmount); vaultLpSupply = vaultLpSupply.add(vaultLpMinted); vaultTotalAmount = vaultTotalAmount.add(depositAmount); vaultLpBalance = vaultLpBalance.add(vaultLpMinted); const afterAmount = vaultLpBalance.mul(vaultTotalAmount).div(vaultLpSupply); return afterAmount.sub(beforeAmount); }; exports.computeActualDepositAmount = computeActualDepositAmount; /** * Compute pool information, Typescript implementation of https://github.com/mercurial-finance/mercurial-dynamic-amm/blob/main/programs/amm/src/lib.rs#L960 * @param {number} currentTime - the on solana chain time in seconds (SYSVAR_CLOCK_PUBKEY) * @param {BN} poolVaultALp - The amount of LP tokens in the pool for token A * @param {BN} poolVaultBLp - The amount of Lp tokens in the pool for token B, * @param {BN} vaultALpSupply - The total amount of Vault A LP tokens in the pool. * @param {BN} vaultBLpSupply - The total amount of Vault B LP token in the pool. * @param {BN} poolLpSupply - The total amount of LP tokens in the pool. * @param {SwapCurve} swapCurve - SwapCurve - the swap curve used to calculate the virtual price * @param {VaultState} vaultA - VaultState of vault A * @param {VaultState} vaultB - VaultState of Vault B * @returns an object of type PoolInformation. */ const calculatePoolInfo = (currentTimestamp, poolVaultALp, poolVaultBLp, vaultALpSupply, vaultBLpSupply, poolLpSupply, swapCurve, vaultA, vaultB) => { const vaultAWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTimestamp.toNumber(), vaultA); const vaultBWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTimestamp.toNumber(), vaultB); const tokenAAmount = (0, vault_sdk_1.getAmountByShare)(poolVaultALp, vaultAWithdrawableAmount, vaultALpSupply); const tokenBAmount = (0, vault_sdk_1.getAmountByShare)(poolVaultBLp, vaultBWithdrawableAmount, vaultBLpSupply); const d = swapCurve.computeD(tokenAAmount, tokenBAmount); const virtualPriceBigNum = poolLpSupply.isZero() ? new anchor_1.BN(0) : d.mul(constants_1.VIRTUAL_PRICE_PRECISION).div(poolLpSupply); const virtualPrice = new decimal_js_1.default(virtualPriceBigNum.toString()).div(constants_1.VIRTUAL_PRICE_PRECISION.toString()).toNumber(); const virtualPriceRaw = poolLpSupply.isZero() ? new anchor_1.BN(0) : new anchor_1.BN(1).shln(64).mul(d).div(poolLpSupply); const poolInformation = { tokenAAmount, tokenBAmount, virtualPrice, virtualPriceRaw, }; return poolInformation; }; exports.calculatePoolInfo = calculatePoolInfo; const calculateProtocolTradingFee = (amount, poolState) => { const { protocolTradeFeeDenominator, protocolTradeFeeNumerator } = poolState.fees; return amount.mul(protocolTradeFeeNumerator).div(protocolTradeFeeDenominator); }; exports.calculateProtocolTradingFee = calculateProtocolTradingFee; const calculateTradingFee = (amount, poolState) => { const { tradeFeeDenominator, tradeFeeNumerator } = poolState.fees; return amount.mul(tradeFeeNumerator).div(tradeFeeDenominator); }; exports.calculateTradingFee = calculateTradingFee; const calculateUnclaimedLockEscrowFee = (totalLockedAmount, lpPerToken, unclaimedFeePending, currentVirtualPrice) => { if (currentVirtualPrice.isZero()) { return new anchor_1.BN(0); } let newFee = totalLockedAmount.mul(currentVirtualPrice.sub(lpPerToken)).div(currentVirtualPrice); return newFee.add(unclaimedFeePending); }; exports.calculateUnclaimedLockEscrowFee = calculateUnclaimedLockEscrowFee; /** * "Calculate the maximum amount of tokens that can be swapped out of a pool." * * @param {PublicKey} tokenMint - The mint that want to swap out * @param {PublicKey} tokenAMint - The public key of the token A mint. * @param {PublicKey} tokenBMint - The public key of the token B mint. * @param {BN} tokenAAmount - The amount of token A that the user wants to swap out. * @param {BN} tokenBAmount - The amount of token B that the user wants to swap out. * @param {BN} vaultAReserve - The amount of tokenA that the vault has in reserve. * @param {BN} vaultBReserve - The amount of tokenB that the vault has in reserve. * @returns The max amount of tokens that can be swapped out. */ const calculateMaxSwapOutAmount = (tokenMint, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, vaultAReserve, vaultBReserve) => { (0, invariant_1.default)(tokenMint.equals(tokenAMint) || tokenMint.equals(tokenBMint), constants_1.ERROR.INVALID_MINT); const [outTotalAmount, outReserveBalance] = tokenMint.equals(tokenAMint) ? [tokenAAmount, vaultAReserve] : [tokenBAmount, vaultBReserve]; return outTotalAmount.gt(outReserveBalance) ? outReserveBalance : outTotalAmount; }; exports.calculateMaxSwapOutAmount = calculateMaxSwapOutAmount; const getStakePubkey = (poolState) => { // Stable swap curve, and depeg type is not "none" if ('stable' in poolState.curveType && !('none' in poolState.curveType['stable'].depeg.depegType)) { const depegType = poolState.curveType['stable'].depeg.depegType; if (depegType['marinade']) { return constants_1.CURVE_TYPE_ACCOUNTS.marinade; } else if (depegType['lido']) { return constants_1.CURVE_TYPE_ACCOUNTS.lido; } else if (depegType['splStake']) { return poolState.stake; } } return null; }; exports.getStakePubkey = getStakePubkey; /** * It gets the account info that are used in depeg Pool * @param {Connection} connection - Connection - The connection to the Solana cluster * @param {PoolState[]} poolsState - Array of PoolState * @returns A map of the depeg accounts. */ const getDepegAccounts = async (connection, poolsState) => { const stakePoolPubkeys = new Set(); for (const p of poolsState) { const stakePubkey = (0, exports.getStakePubkey)(p); if (stakePubkey != null) { stakePoolPubkeys.add(stakePubkey); } } const depegAccounts = new Map(); const stakePoolKeys = [...stakePoolPubkeys]; const accountBuffers = await chunkedGetMultipleAccountInfos(connection, stakePoolKeys); for (const [i, key] of stakePoolKeys.entries()) { if (accountBuffers[i] != null) { depegAccounts.set(key.toBase58(), accountBuffers[i]); } } return depegAccounts; }; exports.getDepegAccounts = getDepegAccounts; const calculateSwapQuoteForGoingToCreateMemecoinPool = (inAmountLamport, tokenADepositAmount, tokenBDepositAmount, aToB, fees, params) => { const { currentTime } = params; const vaultA = params.vaultA ? { vaultStates: params.vaultA, poolVaultLp: new anchor_1.BN(0) } : undefined; const vaultB = params.vaultB ? { vaultStates: params.vaultB, poolVaultLp: new anchor_1.BN(0) } : undefined; (0, invariant_1.default)(vaultA || vaultB, 'Must one side have vault'); (0, invariant_1.default)(!vaultA || !vaultB, 'Must one side have vault'); const getTokenAmountAfterDepositVault = (amount, states) => { // No vault if (!states) { return amount; } const vaultWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, states.vaultStates.vault); const lpMinted = (0, vault_sdk_1.getUnmintAmount)(amount, vaultWithdrawableAmount, states.vaultStates.lpSupply); states.vaultStates.lpSupply = states.vaultStates.lpSupply.add(lpMinted); states.vaultStates.vault.totalAmount = states.vaultStates.vault.totalAmount.add(amount); states.poolVaultLp = states.poolVaultLp.add(lpMinted); return (0, vault_sdk_1.getAmountByShare)(states.poolVaultLp, (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, states.vaultStates.vault), states.vaultStates.lpSupply); }; const getTokenAmountAfterWithdrawVault = (amount, states) => { // No vault if (!states) { return amount; } const vaultWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, states.vaultStates.vault); const lpBurned = (0, vault_sdk_1.getUnmintAmount)(amount, vaultWithdrawableAmount, states.vaultStates.lpSupply); states.vaultStates.lpSupply = states.vaultStates.lpSupply.sub(lpBurned); states.vaultStates.vault.totalAmount = states.vaultStates.vault.totalAmount.sub(amount); states.poolVaultLp = states.poolVaultLp.sub(lpBurned); return (0, vault_sdk_1.getAmountByShare)(states.poolVaultLp, (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, states.vaultStates.vault), states.vaultStates.lpSupply); }; const tokenAAmount = getTokenAmountAfterDepositVault(tokenADepositAmount, vaultA); const tokenBAmount = getTokenAmountAfterDepositVault(tokenBDepositAmount, vaultB); const [sourceAmount, swapSourceAmount, swapDestinationAmount, sourceVault, destinationVault] = aToB ? [inAmountLamport, tokenAAmount, tokenBAmount, vaultA, vaultB] : [inAmountLamport, tokenBAmount, tokenAAmount, vaultB, vaultA]; const tradeFee = sourceAmount.mul(fees.tradeFeeNumerator).div(fees.tradeFeeDenominator); const protocolFee = tradeFee.mul(fees.protocolTradeFeeNumerator).div(fees.protocolTradeFeeDenominator); const sourceAmountLessProtocolFee = sourceAmount.sub(protocolFee); const beforeSwapSourceAmount = swapSourceAmount; const afterSwapSourceAmount = sourceVault ? getTokenAmountAfterDepositVault(sourceAmountLessProtocolFee, sourceVault) : sourceAmountLessProtocolFee; const actualSourceAmount = afterSwapSourceAmount.sub(beforeSwapSourceAmount); const sourceAmountLessFee = actualSourceAmount.sub(tradeFee.sub(protocolFee)); const curve = new curve_1.ConstantProductSwap(); const { outAmount: destinationAmount } = curve.computeOutAmount(sourceAmountLessFee, swapSourceAmount, swapDestinationAmount, aToB ? curve_1.TradeDirection.AToB : curve_1.TradeDirection.BToA); const afterDestinationAmount = destinationVault ? getTokenAmountAfterWithdrawVault(destinationAmount, destinationVault) : destinationAmount; return { amountOut: afterDestinationAmount, fee: sourceAmountLessProtocolFee, }; }; exports.calculateSwapQuoteForGoingToCreateMemecoinPool = calculateSwapQuoteForGoingToCreateMemecoinPool; /** * It calculates the amount of tokens you will receive after swapping your tokens * @param {PublicKey} inTokenMint - The mint of the token you're swapping in. * @param {BN} inAmountLamport - The amount of the input token you want to swap. * @param {SwapQuoteParam} params - SwapQuoteParam * @param {PoolState} params.poolState - pool state that fetch from program * @param {VaultState} params.vaultA - vault A state that fetch from vault program * @param {VaultState} params.vaultB - vault B state that fetch from vault program * @param {BN} params.poolVaultALp - The amount of LP tokens in the pool for token A (`PoolState.aVaultLp` accountInfo) * @param {BN} params.poolVaultBLp - The amount of LP tokens in the pool for token B (`PoolState.bVaultLp` accountInfo) * @param {BN} params.vaultALpSupply - vault A lp supply (`VaultState.lpMint` accountInfo) * @param {BN} params.vaultBLpSupply - vault B lp supply (`VaultState.lpMint` accountInfo) * @param {BN} params.vaultAReserve - vault A reserve (`VaultState.tokenVault` accountInfo) * @param {BN} params.vaultBReserve - vault B reserve (`VaultState.tokenVault` accountInfo) * @param {BN} params.currentTime - on chain time (use `SYSVAR_CLOCK_PUBKEY`) * @param {BN} params.currentSlot - on chain slot (use `SYSVAR_CLOCK_PUBKEY`) * @param {BN} params.depegAccounts - A map of the depeg accounts. (get from `getDepegAccounts` util) * @returns The amount of tokens that will be received after the swap. */ const calculateSwapQuote = (inTokenMint, inAmountLamport, params, swapInitiator) => { const { vaultA, vaultB, vaultALpSupply, vaultBLpSupply, poolState, poolVaultALp, poolVaultBLp, currentTime, depegAccounts, vaultAReserve, vaultBReserve, currentSlot, } = params; const { tokenAMint, tokenBMint } = poolState; (0, invariant_1.default)(inTokenMint.equals(tokenAMint) || inTokenMint.equals(tokenBMint), constants_1.ERROR.INVALID_MINT); (0, invariant_1.default)(poolState.enabled, 'Pool disabled'); let swapCurve; if ('stable' in poolState.curveType) { const { amp, depeg, tokenMultiplier } = poolState.curveType['stable']; swapCurve = new curve_1.StableSwap(amp.toNumber(), tokenMultiplier, depeg, depegAccounts, new anchor_1.BN(currentTime), poolState.stake); } else { // Bootstrapping pool const activationType = poolState.bootstrapping.activationType; const currentPoint = activationType == types_1.ActivationType.Timestamp ? new anchor_1.BN(currentTime) : new anchor_1.BN(currentSlot); const canQuoteEarlier = swapInitiator ? swapInitiator.equals(poolState.bootstrapping.whitelistedVault) : false; if (!canQuoteEarlier) { (0, invariant_1.default)(currentPoint.gte(poolState.bootstrapping.activationPoint), 'Swap is disabled'); } swapCurve = new curve_1.ConstantProductSwap(); } const vaultAWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, vaultA); const vaultBWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, vaultB); const tokenAAmount = (0, vault_sdk_1.getAmountByShare)(poolVaultALp, vaultAWithdrawableAmount, vaultALpSupply); const tokenBAmount = (0, vault_sdk_1.getAmountByShare)(poolVaultBLp, vaultBWithdrawableAmount, vaultBLpSupply); const isFromAToB = inTokenMint.equals(tokenAMint); const [sourceAmount, swapSourceVaultLpAmount, swapSourceAmount, swapDestinationAmount, swapSourceVault, swapDestinationVault, swapSourceVaultLpSupply, swapDestinationVaultLpSupply, tradeDirection,] = isFromAToB ? [ inAmountLamport, poolVaultALp, tokenAAmount, tokenBAmount, vaultA, vaultB, vaultALpSupply, vaultBLpSupply, curve_1.TradeDirection.AToB, ] : [ inAmountLamport, poolVaultBLp, tokenBAmount, tokenAAmount, vaultB, vaultA, vaultBLpSupply, vaultALpSupply, curve_1.TradeDirection.BToA, ]; const tradeFee = (0, exports.calculateTradingFee)(sourceAmount, poolState); // Protocol fee is a cut of trade fee const protocolFee = (0, exports.calculateProtocolTradingFee)(tradeFee, poolState); const tradeFeeAfterProtocolFee = tradeFee.sub(protocolFee); const sourceVaultWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, swapSourceVault); const beforeSwapSourceAmount = swapSourceAmount; const sourceAmountLessProtocolFee = sourceAmount.sub(protocolFee); // Get vault lp minted when deposit to the vault const sourceVaultLp = (0, vault_sdk_1.getUnmintAmount)(sourceAmountLessProtocolFee, sourceVaultWithdrawableAmount, swapSourceVaultLpSupply); const sourceVaultTotalAmount = sourceVaultWithdrawableAmount.add(sourceAmountLessProtocolFee); const afterSwapSourceAmount = (0, vault_sdk_1.getAmountByShare)(sourceVaultLp.add(swapSourceVaultLpAmount), sourceVaultTotalAmount, swapSourceVaultLpSupply.add(sourceVaultLp)); const actualSourceAmount = afterSwapSourceAmount.sub(beforeSwapSourceAmount); let sourceAmountWithFee = actualSourceAmount.sub(tradeFeeAfterProtocolFee); const { outAmount: destinationAmount, priceImpact } = swapCurve.computeOutAmount(sourceAmountWithFee, swapSourceAmount, swapDestinationAmount, tradeDirection); const destinationVaultWithdrawableAmount = (0, vault_sdk_1.calculateWithdrawableAmount)(currentTime, swapDestinationVault); // Get vault lp to burn when withdraw from the vault const destinationVaultLp = (0, vault_sdk_1.getUnmintAmount)(destinationAmount, destinationVaultWithdrawableAmount, swapDestinationVaultLpSupply); let actualDestinationAmount = (0, vault_sdk_1.getAmountByShare)(destinationVaultLp, destinationVaultWithdrawableAmount, swapDestinationVaultLpSupply); const maxSwapOutAmount = (0, exports.calculateMaxSwapOutAmount)(tradeDirection == curve_1.TradeDirection.AToB ? tokenBMint : tokenAMint, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, vaultAReserve, vaultBReserve); (0, invariant_1.default)(actualDestinationAmount.lt(maxSwapOutAmount), 'Out amount > vault reserve'); return { amountOut: actualDestinationAmount, fee: tradeFeeAfterProtocolFee, priceImpact, }; }; exports.calculateSwapQuote = calculateSwapQuote; /** * It takes two numbers, and returns three numbers * @param {number} decimalA - The number of decimal places for token A. * @param {number} decimalB - The number of decimal places for token B. * @returns A TokenMultiplier object with the following properties: * - tokenAMultiplier * - tokenBMultiplier * - precisionFactor */ const computeTokenMultiplier = (decimalA, decimalB) => { const precisionFactor = Math.max(decimalA, decimalB); const tokenAMultiplier = new anchor_1.BN(10 ** (precisionFactor - decimalA)); const tokenBMultiplier = new anchor_1.BN(10 ** (precisionFactor - decimalB)); return { tokenAMultiplier, tokenBMultiplier, precisionFactor, }; }; exports.computeTokenMultiplier = computeTokenMultiplier; /** * It fetches the pool account from the AMM program, and returns the mint addresses for the two tokens * @param {Connection} connection - Connection - The connection to the Solana cluster * @param {string} poolAddress - The address of the pool account. * @returns The tokenAMint and tokenBMint addresses for the pool. */ async function getTokensMintFromPoolAddress(connection, poolAddress, opt) { const { ammProgram } = (0, exports.createProgram)(connection, opt?.programId); const poolAccount = await ammProgram.account.pool.fetchNullable(new web3_js_1.PublicKey(poolAddress)); if (!poolAccount) return; return { tokenAMint: poolAccount.tokenAMint, tokenBMint: poolAccount.tokenBMint, }; } function deriveMintMetadata(lpMint) { return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('metadata'), constants_1.METAPLEX_PROGRAM.toBuffer(), lpMint.toBuffer()], constants_1.METAPLEX_PROGRAM); } function deriveCustomizablePermissionlessConstantProductPoolAddress(tokenA, tokenB, programId) { const [poolPubkey] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('pool'), getFirstKey(tokenA, tokenB), getSecondKey(tokenA, tokenB)], programId); return poolPubkey; } function derivePoolAddressWithConfig(tokenA, tokenB, config, programId) { const [poolPubkey] = web3_js_1.PublicKey.findProgramAddressSync([getFirstKey(tokenA, tokenB), getSecondKey(tokenA, tokenB), config.toBuffer()], programId); return poolPubkey; } const deriveConfigPda = (index, programId) => { const [configPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('config'), index.toBuffer('le', 8)], programId); return configPda; }; exports.deriveConfigPda = deriveConfigPda; const deriveProtocolTokenFee = (poolAddress, tokenMint, programId) => { const [protocolTokenFee] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('fee'), tokenMint.toBuffer(), poolAddress.toBuffer()], programId); return protocolTokenFee; }; exports.deriveProtocolTokenFee = deriveProtocolTokenFee; function derivePoolAddress(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, opt) { const { ammProgram } = (0, exports.createProgram)(connection, opt?.programId); const curveType = generateCurveType(tokenInfoA, tokenInfoB, isStable); const tokenAMint = new web3_js_1.PublicKey(tokenInfoA.address); const tokenBMint = new web3_js_1.PublicKey(tokenInfoB.address); const [poolPubkey] = web3_js_1.PublicKey.findProgramAddressSync([ Buffer.from([encodeCurveType(curveType)]), getFirstKey(tokenAMint, tokenBMint), getSecondKey(tokenAMint, tokenBMint), getTradeFeeBpsBuffer(curveType, tradeFeeBps), ], ammProgram.programId); return poolPubkey; } /** * It checks if a pool exists by checking if the pool account exists * @param {Connection} connection - Connection - the connection to the Solana cluster * @param {TokenInfo} tokenInfoA - TokenInfo * @param {TokenInfo} tokenInfoB - TokenInfo * @param {boolean} isStable - boolean - whether the pool is stable or not * @returns A boolean value. */ async function checkPoolExists(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, opt) { const { ammProgram } = (0, exports.createProgram)(connection, opt?.programId); const poolPubkey = derivePoolAddress(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, { programId: opt?.programId, }); const poolAccount = await ammProgram.account.pool.fetchNullable(poolPubkey); if (!poolAccount) return; return poolPubkey; } /** * It checks if a pool with config exists by checking if the pool account exists * @param {Connection} connection - Connection - the connection to the Solana cluster * @param {PublicKey} tokenA - TokenInfo * @param {PublicKey} tokenB - TokenInfo * @returns A PublicKey value or undefined. */ async function checkPoolWithConfigsExists(connection, tokenA, tokenB, configs, opt) { const { ammProgram } = (0, exports.createProgram)(connection, opt?.programId); const poolsPubkey = configs.map((config) => derivePoolAddressWithConfig(tokenA, tokenB, config, ammProgram.programId)); const poolsAccount = await ammProgram.account.pool.fetchMultiple(poolsPubkey); if (poolsAccount.every((account) => account === null)) return; const poolAccountIndex = poolsAccount.findIndex((account) => account !== null); return poolsPubkey[poolAccountIndex]; } function chunks(array, size) { return Array.apply(0, new Array(Math.ceil(array.length / size))).map((_, index) => array.slice(index * size, (index + 1) * size)); } async function chunkedFetchMultiplePoolAccount(program, pks, chunkSize = 100) { const accounts = (await Promise.all(chunks(pks, chunkSize).map((chunk) => program.account.pool.fetchMultiple(chunk)))).flat(); return accounts.filter(Boolean); } async function chunkedGetMultipleAccountInfos(connection, pks, chunkSize = 100) { const accountInfos = (await Promise.all(chunks(pks, chunkSize).map((chunk) => connection.getMultipleAccountsInfo(chunk)))).flat(); return accountInfos; } function encodeCurveType(curve) { if (curve['constantProduct']) { return 0; } else if (curve['stable']) { return 1; } else { throw new Error('Unknown curve type'); } } function getSecondKey(key1, key2) { const buf1 = key1.toBuffer(); const buf2 = key2.toBuffer(); // Buf1 > buf2 if (Buffer.compare(buf1, buf2) === 1) { return buf2; } return buf1; } function getFirstKey(key1, key2) { const buf1 = key1.toBuffer(); const buf2 = key2.toBuffer(); // Buf1 > buf2 if (Buffer.compare(buf1, buf2) === 1) { return buf1; } return buf2; } function getTradeFeeBpsBuffer(curve, tradeFeeBps) { let defaultFeeBps; if (curve['stable']) { defaultFeeBps = new anchor_1.BN(constants_1.STABLE_SWAP_DEFAULT_TRADE_FEE_BPS); } else { defaultFeeBps = new anchor_1.BN(constants_1.CONSTANT_PRODUCT_DEFAULT_TRADE_FEE_BPS); } if (tradeFeeBps.eq(defaultFeeBps)) { return new Uint8Array(); } return new Uint8Array(tradeFeeBps.toBuffer('le', 8)); } exports.DepegType = { none: () => { return { none: {}, }; }, marinade: () => { return { marinade: {}, }; }, lido: () => { return { lido: {}, }; }, splStake: () => { return { splStake: {}, }; }, }; function generateCurveType(tokenInfoA, tokenInfoB, isStable) { return isStable ? { stable: { amp: constants_1.PERMISSIONLESS_AMP, tokenMultiplier: (0, exports.computeTokenMultiplier)(tokenInfoA.decimals, tokenInfoB.decimals), depeg: { baseVirtualPrice: new anchor_1.BN(0), baseCacheUpdated: new anchor_1.BN(0), depegType: exports.DepegType.none() }, lastAmpUpdatedTimestamp: new anchor_1.BN(0), }, } : { constantProduct: {} }; } async function createMint(connection, mintAccount, payer, assetData, mintAuthority, freezeAuthority, decimals, programId) { // Allocate memory for the account const balanceNeeded = await (0, spl_token_1.getMinimumBalanceForRentExemptMint)(connection); const transaction = new web3_js_1.Transaction(); transaction.add(web3_js_1.SystemProgram.createAccount({ fromPubkey: payer, newAccountPubkey: mintAccount.publicKey, lamports: balanceNeeded, space: spl_token_1.MintLayout.span, programId, })); transaction.add((0, spl_token_1.createInitializeMintInstruction)(mintAccount.publicKey, decimals, mintAuthority, freezeAuthority, programId)); const [metadata] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('metadata'), mpl_token_metadata_1.PROGRAM_ID.toBuffer(), mintAccount.publicKey.toBuffer()], mpl_token_metadata_1.PROGRAM_ID); const accounts = { metadata, mint: mintAccount.publicKey, mintAuthority: payer, payer, updateAuthority: payer, }; const args = { createMetadataAccountArgsV3: { data: assetData, isMutable: false, collectionDetails: null, }, }; transaction.add((0, mpl_token_metadata_1.createCreateMetadataAccountV3Instruction)(accounts, args)); return { tx: transaction, mintAccount }; } const calculateLockAmounts = (amount, feeWrapperRatio = new decimal_js_1.default(0)) => { if (feeWrapperRatio.lt(0) || feeWrapperRatio.gt(1)) { throw new Error('Fee wrapper ratio should be between 0 and 1'); } const feeWrapperLockAmount = new anchor_1.BN(new decimal_js_1.default(amount.toString()).mul(feeWrapperRatio).toFixed(0, decimal_js_1.default.ROUND_DOWN)); const userLockAmount = amount.sub(feeWrapperLockAmount); return { feeWrapperLockAmount, userLockAmount, }; }; exports.calculateLockAmounts = calculateLockAmounts; async function createTransactions(connection, ixs, payer) { const latestBlockHash = await connection.getLatestBlockhash(); const resultTx = []; for (const instruction of ixs) { const tx = new web3_js_1.Transaction({ feePayer: payer, ...latestBlockHash, }); if (Array.isArray(instruction)) { tx.add(...instruction); } else { tx.add(instruction); } resultTx.push(tx); } return resultTx; } //# sourceMappingURL=utils.js.map