@mercurial-finance/dynamic-amm-sdk
Version:
Mercurial Vaults SDK is a typescript library that allows you to interact with Mercurial v2's AMM.
648 lines • 33 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { getAmountByShare, calculateWithdrawableAmount, getUnmintAmount, IDL as VaultIDL, PROGRAM_ID as VAULT_PROGRAM_ID, } from '@mercurial-finance/vault-sdk';
import { STAKE_FOR_FEE_PROGRAM_ID, IDL as StakeForFeeIDL, } from '@meteora-ag/m3m3';
import { AnchorProvider, BN, Program } from '@coral-xyz/anchor';
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, AccountLayout, NATIVE_MINT, createAssociatedTokenAccountInstruction, getAssociatedTokenAddressSync, createCloseAccountInstruction, getMinimumBalanceForRentExemptMint, MintLayout, createInitializeMintInstruction, } from '@solana/spl-token';
import { PublicKey, SystemProgram, SYSVAR_CLOCK_PUBKEY, Transaction, TransactionInstruction, } from '@solana/web3.js';
import invariant from 'invariant';
import { CURVE_TYPE_ACCOUNTS, ERROR, PROGRAM_ID, VIRTUAL_PRICE_PRECISION, PERMISSIONLESS_AMP, STABLE_SWAP_DEFAULT_TRADE_FEE_BPS, CONSTANT_PRODUCT_DEFAULT_TRADE_FEE_BPS, METAPLEX_PROGRAM, SEEDS, } from './constants';
import { ConstantProductSwap, StableSwap, TradeDirection } from './curve';
import { ActivationType, } from './types';
import { IDL as AmmIDL } from './idl';
import Decimal from 'decimal.js';
import { createCreateMetadataAccountV3Instruction, PROGRAM_ID as PROGRAM_ID_META, } from '@metaplex-foundation/mpl-token-metadata';
export const createProgram = (connection, programId) => {
const provider = new AnchorProvider(connection, {}, AnchorProvider.defaultOptions());
const ammProgram = new Program(AmmIDL, programId !== null && programId !== void 0 ? programId : PROGRAM_ID, provider);
const vaultProgram = new Program(VaultIDL, VAULT_PROGRAM_ID, provider);
const stakeForFeeProgram = new Program(StakeForFeeIDL, STAKE_FOR_FEE_PROGRAM_ID, provider);
return { provider, ammProgram, vaultProgram, stakeForFeeProgram };
};
/**
* 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.
*/
export const getMaxAmountWithSlippage = (amount, slippageRate) => {
const slippage = ((100 + slippageRate) / 100) * 10000;
return amount.mul(new BN(slippage)).div(new BN(10000));
};
/**
* 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.
*/
export const getMinAmountWithSlippage = (amount, slippageRate) => {
const slippage = ((100 - slippageRate) / 100) * 10000;
return amount.mul(new BN(slippage)).div(new BN(10000));
};
export const getAssociatedTokenAccount = (tokenMint, owner) => {
return getAssociatedTokenAddressSync(tokenMint, owner, true, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
};
export const getOrCreateATAInstruction = (tokenMint, owner, connection, payer) => __awaiter(void 0, void 0, void 0, function* () {
let toAccount;
try {
toAccount = yield getAssociatedTokenAccount(tokenMint, owner);
const account = yield connection.getAccountInfo(toAccount);
if (!account) {
const ix = createAssociatedTokenAccountInstruction(payer || owner, toAccount, owner, tokenMint, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
return [toAccount, ix];
}
return [toAccount, undefined];
}
catch (e) {
/* handle error */
console.error('Error::getOrCreateATAInstruction', e);
throw e;
}
});
export const deriveLockEscrowPda = (pool, owner, ammProgram) => {
return PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LOCK_ESCROW), pool.toBuffer(), owner.toBuffer()], ammProgram);
};
export const wrapSOLInstruction = (from, to, amount) => {
return [
SystemProgram.transfer({
fromPubkey: from,
toPubkey: to,
lamports: amount,
}),
new TransactionInstruction({
keys: [
{
pubkey: to,
isSigner: false,
isWritable: true,
},
],
data: Buffer.from(new Uint8Array([17])),
programId: TOKEN_PROGRAM_ID,
}),
];
};
export const unwrapSOLInstruction = (owner) => __awaiter(void 0, void 0, void 0, function* () {
const wSolATAAccount = yield getAssociatedTokenAccount(NATIVE_MINT, owner);
if (wSolATAAccount) {
const closedWrappedSolInstruction = createCloseAccountInstruction(wSolATAAccount, owner, owner, []);
return closedWrappedSolInstruction;
}
return null;
});
export const deserializeAccount = (data) => {
if (data == undefined || data.length == 0) {
return undefined;
}
const accountInfo = AccountLayout.decode(data);
return accountInfo;
};
export const getOnchainTime = (connection) => __awaiter(void 0, void 0, void 0, function* () {
const parsedClock = yield connection.getParsedAccountInfo(SYSVAR_CLOCK_PUBKEY);
const parsedClockAccount = parsedClock.value.data.parsed;
const currentTime = parsedClockAccount.info.unixTimestamp;
return currentTime;
});
/**
* Compute "actual" amount deposited to vault (precision loss)
* @param depositAmount
* @param beforeAmount
* @param vaultLpBalance
* @param vaultLpSupply
* @param vaultTotalAmount
* @returns
*/
export const computeActualDepositAmount = (depositAmount, beforeAmount, vaultLpBalance, vaultLpSupply, vaultTotalAmount) => {
if (depositAmount.eq(new 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);
};
/**
* 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.
*/
export const calculatePoolInfo = (currentTimestamp, poolVaultALp, poolVaultBLp, vaultALpSupply, vaultBLpSupply, poolLpSupply, swapCurve, vaultA, vaultB) => {
const vaultAWithdrawableAmount = calculateWithdrawableAmount(currentTimestamp.toNumber(), vaultA);
const vaultBWithdrawableAmount = calculateWithdrawableAmount(currentTimestamp.toNumber(), vaultB);
const tokenAAmount = getAmountByShare(poolVaultALp, vaultAWithdrawableAmount, vaultALpSupply);
const tokenBAmount = getAmountByShare(poolVaultBLp, vaultBWithdrawableAmount, vaultBLpSupply);
const d = swapCurve.computeD(tokenAAmount, tokenBAmount);
const virtualPriceBigNum = poolLpSupply.isZero() ? new BN(0) : d.mul(VIRTUAL_PRICE_PRECISION).div(poolLpSupply);
const virtualPrice = new Decimal(virtualPriceBigNum.toString()).div(VIRTUAL_PRICE_PRECISION.toString()).toNumber();
const virtualPriceRaw = poolLpSupply.isZero() ? new BN(0) : new BN(1).shln(64).mul(d).div(poolLpSupply);
const poolInformation = {
tokenAAmount,
tokenBAmount,
virtualPrice,
virtualPriceRaw,
};
return poolInformation;
};
export const calculateProtocolTradingFee = (amount, poolState) => {
const { protocolTradeFeeDenominator, protocolTradeFeeNumerator } = poolState.fees;
return amount.mul(protocolTradeFeeNumerator).div(protocolTradeFeeDenominator);
};
export const calculateTradingFee = (amount, poolState) => {
const { tradeFeeDenominator, tradeFeeNumerator } = poolState.fees;
return amount.mul(tradeFeeNumerator).div(tradeFeeDenominator);
};
export const calculateUnclaimedLockEscrowFee = (totalLockedAmount, lpPerToken, unclaimedFeePending, currentVirtualPrice) => {
if (currentVirtualPrice.isZero()) {
return new BN(0);
}
let newFee = totalLockedAmount.mul(currentVirtualPrice.sub(lpPerToken)).div(currentVirtualPrice);
return newFee.add(unclaimedFeePending);
};
/**
* "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.
*/
export const calculateMaxSwapOutAmount = (tokenMint, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, vaultAReserve, vaultBReserve) => {
invariant(tokenMint.equals(tokenAMint) || tokenMint.equals(tokenBMint), ERROR.INVALID_MINT);
const [outTotalAmount, outReserveBalance] = tokenMint.equals(tokenAMint)
? [tokenAAmount, vaultAReserve]
: [tokenBAmount, vaultBReserve];
return outTotalAmount.gt(outReserveBalance) ? outReserveBalance : outTotalAmount;
};
export 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 CURVE_TYPE_ACCOUNTS.marinade;
}
else if (depegType['lido']) {
return CURVE_TYPE_ACCOUNTS.lido;
}
else if (depegType['splStake']) {
return poolState.stake;
}
}
return null;
};
/**
* 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.
*/
export const getDepegAccounts = (connection, poolsState) => __awaiter(void 0, void 0, void 0, function* () {
const stakePoolPubkeys = new Set();
for (const p of poolsState) {
const stakePubkey = getStakePubkey(p);
if (stakePubkey != null) {
stakePoolPubkeys.add(stakePubkey);
}
}
const depegAccounts = new Map();
const stakePoolKeys = [...stakePoolPubkeys];
const accountBuffers = yield chunkedGetMultipleAccountInfos(connection, stakePoolKeys);
for (const [i, key] of stakePoolKeys.entries()) {
if (accountBuffers[i] != null) {
depegAccounts.set(key.toBase58(), accountBuffers[i]);
}
}
return depegAccounts;
});
export const calculateSwapQuoteForGoingToCreateMemecoinPool = (inAmountLamport, tokenADepositAmount, tokenBDepositAmount, aToB, fees, params) => {
const { currentTime } = params;
const vaultA = params.vaultA
? { vaultStates: params.vaultA, poolVaultLp: new BN(0) }
: undefined;
const vaultB = params.vaultB
? { vaultStates: params.vaultB, poolVaultLp: new BN(0) }
: undefined;
invariant(vaultA || vaultB, 'Must one side have vault');
invariant(!vaultA || !vaultB, 'Must one side have vault');
const getTokenAmountAfterDepositVault = (amount, states) => {
// No vault
if (!states) {
return amount;
}
const vaultWithdrawableAmount = calculateWithdrawableAmount(currentTime, states.vaultStates.vault);
const lpMinted = 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 getAmountByShare(states.poolVaultLp, calculateWithdrawableAmount(currentTime, states.vaultStates.vault), states.vaultStates.lpSupply);
};
const getTokenAmountAfterWithdrawVault = (amount, states) => {
// No vault
if (!states) {
return amount;
}
const vaultWithdrawableAmount = calculateWithdrawableAmount(currentTime, states.vaultStates.vault);
const lpBurned = 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 getAmountByShare(states.poolVaultLp, 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 ConstantProductSwap();
const { outAmount: destinationAmount } = curve.computeOutAmount(sourceAmountLessFee, swapSourceAmount, swapDestinationAmount, aToB ? TradeDirection.AToB : TradeDirection.BToA);
const afterDestinationAmount = destinationVault
? getTokenAmountAfterWithdrawVault(destinationAmount, destinationVault)
: destinationAmount;
return {
amountOut: afterDestinationAmount,
fee: sourceAmountLessProtocolFee,
};
};
/**
* 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.
*/
export const calculateSwapQuote = (inTokenMint, inAmountLamport, params, swapInitiator) => {
const { vaultA, vaultB, vaultALpSupply, vaultBLpSupply, poolState, poolVaultALp, poolVaultBLp, currentTime, depegAccounts, vaultAReserve, vaultBReserve, currentSlot, } = params;
const { tokenAMint, tokenBMint } = poolState;
invariant(inTokenMint.equals(tokenAMint) || inTokenMint.equals(tokenBMint), ERROR.INVALID_MINT);
invariant(poolState.enabled, 'Pool disabled');
let swapCurve;
if ('stable' in poolState.curveType) {
const { amp, depeg, tokenMultiplier } = poolState.curveType['stable'];
swapCurve = new StableSwap(amp.toNumber(), tokenMultiplier, depeg, depegAccounts, new BN(currentTime), poolState.stake);
}
else {
// Bootstrapping pool
const activationType = poolState.bootstrapping.activationType;
const currentPoint = activationType == ActivationType.Timestamp ? new BN(currentTime) : new BN(currentSlot);
const canQuoteEarlier = swapInitiator ? swapInitiator.equals(poolState.bootstrapping.whitelistedVault) : false;
if (!canQuoteEarlier) {
invariant(currentPoint.gte(poolState.bootstrapping.activationPoint), 'Swap is disabled');
}
swapCurve = new ConstantProductSwap();
}
const vaultAWithdrawableAmount = calculateWithdrawableAmount(currentTime, vaultA);
const vaultBWithdrawableAmount = calculateWithdrawableAmount(currentTime, vaultB);
const tokenAAmount = getAmountByShare(poolVaultALp, vaultAWithdrawableAmount, vaultALpSupply);
const tokenBAmount = 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,
TradeDirection.AToB,
]
: [
inAmountLamport,
poolVaultBLp,
tokenBAmount,
tokenAAmount,
vaultB,
vaultA,
vaultBLpSupply,
vaultALpSupply,
TradeDirection.BToA,
];
const tradeFee = calculateTradingFee(sourceAmount, poolState);
// Protocol fee is a cut of trade fee
const protocolFee = calculateProtocolTradingFee(tradeFee, poolState);
const tradeFeeAfterProtocolFee = tradeFee.sub(protocolFee);
const sourceVaultWithdrawableAmount = calculateWithdrawableAmount(currentTime, swapSourceVault);
const beforeSwapSourceAmount = swapSourceAmount;
const sourceAmountLessProtocolFee = sourceAmount.sub(protocolFee);
// Get vault lp minted when deposit to the vault
const sourceVaultLp = getUnmintAmount(sourceAmountLessProtocolFee, sourceVaultWithdrawableAmount, swapSourceVaultLpSupply);
const sourceVaultTotalAmount = sourceVaultWithdrawableAmount.add(sourceAmountLessProtocolFee);
const afterSwapSourceAmount = 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 = calculateWithdrawableAmount(currentTime, swapDestinationVault);
// Get vault lp to burn when withdraw from the vault
const destinationVaultLp = getUnmintAmount(destinationAmount, destinationVaultWithdrawableAmount, swapDestinationVaultLpSupply);
let actualDestinationAmount = getAmountByShare(destinationVaultLp, destinationVaultWithdrawableAmount, swapDestinationVaultLpSupply);
const maxSwapOutAmount = calculateMaxSwapOutAmount(tradeDirection == TradeDirection.AToB ? tokenBMint : tokenAMint, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, vaultAReserve, vaultBReserve);
invariant(actualDestinationAmount.lt(maxSwapOutAmount), 'Out amount > vault reserve');
return {
amountOut: actualDestinationAmount,
fee: tradeFeeAfterProtocolFee,
priceImpact,
};
};
/**
* 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
*/
export const computeTokenMultiplier = (decimalA, decimalB) => {
const precisionFactor = Math.max(decimalA, decimalB);
const tokenAMultiplier = new BN(Math.pow(10, (precisionFactor - decimalA)));
const tokenBMultiplier = new BN(Math.pow(10, (precisionFactor - decimalB)));
return {
tokenAMultiplier,
tokenBMultiplier,
precisionFactor,
};
};
/**
* 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.
*/
export function getTokensMintFromPoolAddress(connection, poolAddress, opt) {
return __awaiter(this, void 0, void 0, function* () {
const { ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const poolAccount = yield ammProgram.account.pool.fetchNullable(new PublicKey(poolAddress));
if (!poolAccount)
return;
return {
tokenAMint: poolAccount.tokenAMint,
tokenBMint: poolAccount.tokenBMint,
};
});
}
export function deriveMintMetadata(lpMint) {
return PublicKey.findProgramAddressSync([Buffer.from('metadata'), METAPLEX_PROGRAM.toBuffer(), lpMint.toBuffer()], METAPLEX_PROGRAM);
}
export function deriveCustomizablePermissionlessConstantProductPoolAddress(tokenA, tokenB, programId) {
const [poolPubkey] = PublicKey.findProgramAddressSync([Buffer.from('pool'), getFirstKey(tokenA, tokenB), getSecondKey(tokenA, tokenB)], programId);
return poolPubkey;
}
export function derivePoolAddressWithConfig(tokenA, tokenB, config, programId) {
const [poolPubkey] = PublicKey.findProgramAddressSync([getFirstKey(tokenA, tokenB), getSecondKey(tokenA, tokenB), config.toBuffer()], programId);
return poolPubkey;
}
export const deriveConfigPda = (index, programId) => {
const [configPda] = PublicKey.findProgramAddressSync([Buffer.from('config'), index.toBuffer('le', 8)], programId);
return configPda;
};
export const deriveProtocolTokenFee = (poolAddress, tokenMint, programId) => {
const [protocolTokenFee] = PublicKey.findProgramAddressSync([Buffer.from('fee'), tokenMint.toBuffer(), poolAddress.toBuffer()], programId);
return protocolTokenFee;
};
export function derivePoolAddress(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, opt) {
const { ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const curveType = generateCurveType(tokenInfoA, tokenInfoB, isStable);
const tokenAMint = new PublicKey(tokenInfoA.address);
const tokenBMint = new PublicKey(tokenInfoB.address);
const [poolPubkey] = 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.
*/
export function checkPoolExists(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, opt) {
return __awaiter(this, void 0, void 0, function* () {
const { ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const poolPubkey = derivePoolAddress(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, {
programId: opt === null || opt === void 0 ? void 0 : opt.programId,
});
const poolAccount = yield 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.
*/
export function checkPoolWithConfigsExists(connection, tokenA, tokenB, configs, opt) {
return __awaiter(this, void 0, void 0, function* () {
const { ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const poolsPubkey = configs.map((config) => derivePoolAddressWithConfig(tokenA, tokenB, config, ammProgram.programId));
const poolsAccount = yield ammProgram.account.pool.fetchMultiple(poolsPubkey);
if (poolsAccount.every((account) => account === null))
return;
const poolAccountIndex = poolsAccount.findIndex((account) => account !== null);
return poolsPubkey[poolAccountIndex];
});
}
export function chunks(array, size) {
return Array.apply(0, new Array(Math.ceil(array.length / size))).map((_, index) => array.slice(index * size, (index + 1) * size));
}
export function chunkedFetchMultiplePoolAccount(program_1, pks_1) {
return __awaiter(this, arguments, void 0, function* (program, pks, chunkSize = 100) {
const accounts = (yield Promise.all(chunks(pks, chunkSize).map((chunk) => program.account.pool.fetchMultiple(chunk)))).flat();
return accounts.filter(Boolean);
});
}
export function chunkedGetMultipleAccountInfos(connection_1, pks_1) {
return __awaiter(this, arguments, void 0, function* (connection, pks, chunkSize = 100) {
const accountInfos = (yield Promise.all(chunks(pks, chunkSize).map((chunk) => connection.getMultipleAccountsInfo(chunk)))).flat();
return accountInfos;
});
}
export function encodeCurveType(curve) {
if (curve['constantProduct']) {
return 0;
}
else if (curve['stable']) {
return 1;
}
else {
throw new Error('Unknown curve type');
}
}
export function getSecondKey(key1, key2) {
const buf1 = key1.toBuffer();
const buf2 = key2.toBuffer();
// Buf1 > buf2
if (Buffer.compare(buf1, buf2) === 1) {
return buf2;
}
return buf1;
}
export function getFirstKey(key1, key2) {
const buf1 = key1.toBuffer();
const buf2 = key2.toBuffer();
// Buf1 > buf2
if (Buffer.compare(buf1, buf2) === 1) {
return buf1;
}
return buf2;
}
export function getTradeFeeBpsBuffer(curve, tradeFeeBps) {
let defaultFeeBps;
if (curve['stable']) {
defaultFeeBps = new BN(STABLE_SWAP_DEFAULT_TRADE_FEE_BPS);
}
else {
defaultFeeBps = new BN(CONSTANT_PRODUCT_DEFAULT_TRADE_FEE_BPS);
}
if (tradeFeeBps.eq(defaultFeeBps)) {
return new Uint8Array();
}
return new Uint8Array(tradeFeeBps.toBuffer('le', 8));
}
export const DepegType = {
none: () => {
return {
none: {},
};
},
marinade: () => {
return {
marinade: {},
};
},
lido: () => {
return {
lido: {},
};
},
splStake: () => {
return {
splStake: {},
};
},
};
export function generateCurveType(tokenInfoA, tokenInfoB, isStable) {
return isStable
? {
stable: {
amp: PERMISSIONLESS_AMP,
tokenMultiplier: computeTokenMultiplier(tokenInfoA.decimals, tokenInfoB.decimals),
depeg: { baseVirtualPrice: new BN(0), baseCacheUpdated: new BN(0), depegType: DepegType.none() },
lastAmpUpdatedTimestamp: new BN(0),
},
}
: { constantProduct: {} };
}
export function createMint(connection, mintAccount, payer, assetData, mintAuthority, freezeAuthority, decimals, programId) {
return __awaiter(this, void 0, void 0, function* () {
// Allocate memory for the account
const balanceNeeded = yield getMinimumBalanceForRentExemptMint(connection);
const transaction = new Transaction();
transaction.add(SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: mintAccount.publicKey,
lamports: balanceNeeded,
space: MintLayout.span,
programId,
}));
transaction.add(createInitializeMintInstruction(mintAccount.publicKey, decimals, mintAuthority, freezeAuthority, programId));
const [metadata] = PublicKey.findProgramAddressSync([Buffer.from('metadata'), PROGRAM_ID_META.toBuffer(), mintAccount.publicKey.toBuffer()], PROGRAM_ID_META);
const accounts = {
metadata,
mint: mintAccount.publicKey,
mintAuthority: payer,
payer,
updateAuthority: payer,
};
const args = {
createMetadataAccountArgsV3: {
data: assetData,
isMutable: false,
collectionDetails: null,
},
};
transaction.add(createCreateMetadataAccountV3Instruction(accounts, args));
return { tx: transaction, mintAccount };
});
}
export const calculateLockAmounts = (amount, feeWrapperRatio = new Decimal(0)) => {
if (feeWrapperRatio.lt(0) || feeWrapperRatio.gt(1)) {
throw new Error('Fee wrapper ratio should be between 0 and 1');
}
const feeWrapperLockAmount = new BN(new Decimal(amount.toString()).mul(feeWrapperRatio).toFixed(0, Decimal.ROUND_DOWN));
const userLockAmount = amount.sub(feeWrapperLockAmount);
return {
feeWrapperLockAmount,
userLockAmount,
};
};
export function createTransactions(connection, ixs, payer) {
return __awaiter(this, void 0, void 0, function* () {
const latestBlockHash = yield connection.getLatestBlockhash();
const resultTx = [];
for (const instruction of ixs) {
const tx = new Transaction(Object.assign({ feePayer: payer }, latestBlockHash));
if (Array.isArray(instruction)) {
tx.add(...instruction);
}
else {
tx.add(instruction);
}
resultTx.push(tx);
}
return resultTx;
});
}
//# sourceMappingURL=utils.js.map