@mercurial-finance/dynamic-amm-sdk
Version:
Mercurial Vaults SDK is a typescript library that allows you to interact with Mercurial v2's AMM.
866 lines • 112 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 { BN } from '@coral-xyz/anchor';
import { PublicKey, Transaction, SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, SystemProgram, ComputeBudgetProgram, } from '@solana/web3.js';
import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, createMintToInstruction, createSetAuthorityInstruction, MintLayout, NATIVE_MINT, TOKEN_PROGRAM_ID, } from '@solana/spl-token';
import VaultImpl, { calculateWithdrawableAmount, getVaultPdas } from '@mercurial-finance/vault-sdk';
import StakeForFee, { deriveFeeVault, STAKE_FOR_FEE_PROGRAM_ID } from '@meteora-ag/m3m3';
import invariant from 'invariant';
import { AccountType, ClockLayout, } from './types';
import { ERROR, SEEDS, UNLOCK_AMOUNT_BUFFER, FEE_OWNER, METAPLEX_PROGRAM, U64_MAX } from './constants';
import { StableSwap, TradeDirection } from './curve';
import { ConstantProductSwap } from './curve/constant-product';
import { calculateMaxSwapOutAmount, calculateSwapQuote, computeActualDepositAmount, calculatePoolInfo, getMaxAmountWithSlippage, getMinAmountWithSlippage, getOrCreateATAInstruction, unwrapSOLInstruction, wrapSOLInstruction, getDepegAccounts, createProgram, getAssociatedTokenAccount, deserializeAccount, chunkedGetMultipleAccountInfos, generateCurveType, derivePoolAddress, chunkedFetchMultiplePoolAccount, deriveMintMetadata, deriveLockEscrowPda, calculateUnclaimedLockEscrowFee, derivePoolAddressWithConfig as deriveConstantProductPoolAddressWithConfig, deriveConfigPda, deriveProtocolTokenFee, createMint, calculateLockAmounts, createTransactions, deriveCustomizablePermissionlessConstantProductPoolAddress, } from './utils';
import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
import sqrt from 'bn-sqrt';
const getAllPoolState = (poolMints, program) => __awaiter(void 0, void 0, void 0, function* () {
const poolStates = (yield chunkedFetchMultiplePoolAccount(program, poolMints));
invariant(poolStates.length === poolMints.length, 'Some of the pool state not found');
const poolLpMints = poolStates.map((poolState) => poolState.lpMint);
const lpMintAccounts = yield chunkedGetMultipleAccountInfos(program.provider.connection, poolLpMints);
return poolStates.map((poolState, idx) => {
const lpMintAccount = lpMintAccounts[idx];
invariant(lpMintAccount, ERROR.INVALID_ACCOUNT);
const lpSupply = new BN(MintLayout.decode(lpMintAccount.data).supply.toString());
return Object.assign(Object.assign({}, poolState), { lpSupply });
});
});
const getPoolState = (poolMint, program) => __awaiter(void 0, void 0, void 0, function* () {
const poolState = (yield program.account.pool.fetchNullable(poolMint));
invariant(poolState, `Pool ${poolMint.toBase58()} not found`);
const account = yield program.provider.connection.getTokenSupply(poolState.lpMint);
invariant(account.value.amount, ERROR.INVALID_ACCOUNT);
return Object.assign(Object.assign({}, poolState), { lpSupply: new BN(account.value.amount) });
});
const getFeeVaultState = (publicKey, program) => __awaiter(void 0, void 0, void 0, function* () {
const feeVaultState = yield program.account.feeVault.fetchNullable(publicKey);
return feeVaultState;
});
const decodeAccountTypeMapper = (type) => {
const decoder = {
[AccountType.VAULT_A_RESERVE]: (accountData) => new BN(AccountLayout.decode(accountData).amount.toString()),
[AccountType.VAULT_B_RESERVE]: (accountData) => new BN(AccountLayout.decode(accountData).amount.toString()),
[AccountType.VAULT_A_LP]: (accountData) => new BN(MintLayout.decode(accountData).supply.toString()),
[AccountType.VAULT_B_LP]: (accountData) => new BN(MintLayout.decode(accountData).supply.toString()),
[AccountType.POOL_VAULT_A_LP]: (accountData) => new BN(AccountLayout.decode(accountData).amount.toString()),
[AccountType.POOL_VAULT_B_LP]: (accountData) => new BN(AccountLayout.decode(accountData).amount.toString()),
[AccountType.POOL_LP_MINT]: (accountData) => new BN(MintLayout.decode(accountData).supply.toString()),
[AccountType.SYSVAR_CLOCK]: (accountData) => new BN(accountData.readBigInt64LE(32).toString()),
};
return decoder[type];
};
const getAccountsBuffer = (connection, accountsToFetch) => __awaiter(void 0, void 0, void 0, function* () {
const accounts = yield chunkedGetMultipleAccountInfos(connection, accountsToFetch.map((account) => account.pubkey));
return accountsToFetch.reduce((accMap, account, index) => {
const accountInfo = accounts[index];
accMap.set(account.pubkey.toBase58(), {
type: account.type,
account: accountInfo,
});
return accMap;
}, new Map());
});
const deserializeAccountsBuffer = (accountInfoMap) => {
return Array.from(accountInfoMap).reduce((accValue, [publicKey, { type, account }]) => {
const decodedAccountInfo = decodeAccountTypeMapper(type);
accValue.set(publicKey, decodedAccountInfo(account.data));
return accValue;
}, new Map());
};
export default class AmmImpl {
constructor(address, program, vaultProgram, tokenAMint, tokenBMint, poolState, poolInfo, vaultA, vaultB, accountsInfo, swapCurve, depegAccounts, opt) {
this.address = address;
this.program = program;
this.vaultProgram = vaultProgram;
this.tokenAMint = tokenAMint;
this.tokenBMint = tokenBMint;
this.poolState = poolState;
this.poolInfo = poolInfo;
this.vaultA = vaultA;
this.vaultB = vaultB;
this.accountsInfo = accountsInfo;
this.swapCurve = swapCurve;
this.depegAccounts = depegAccounts;
this.opt = {
cluster: 'mainnet-beta',
};
this.opt = Object.assign(Object.assign({}, this.opt), opt);
}
static createConfig(connection, payer, tradeFeeBps, protocolFeeBps, vaultConfigKey, activationDuration, poolCreatorAuthority, activationType, partnerFeeNumerator, opt) {
return __awaiter(this, void 0, void 0, function* () {
const { ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const configs = yield this.getFeeConfigurations(connection, opt);
let index = 0;
while (true) {
const configPda = deriveConfigPda(new BN(index), ammProgram.programId);
if (!configs.find((c) => c.publicKey.equals(configPda))) {
const createConfigTx = yield ammProgram.methods
.createConfig({
// Default fee denominator is 100_000
tradeFeeNumerator: tradeFeeBps.mul(new BN(10)),
protocolTradeFeeNumerator: protocolFeeBps.mul(new BN(10)),
vaultConfigKey,
activationDuration,
poolCreatorAuthority,
index: new BN(index),
activationType,
partnerFeeNumerator,
})
.accounts({
config: configPda,
systemProgram: SystemProgram.programId,
admin: payer,
})
.transaction();
return new Transaction(Object.assign({ feePayer: payer }, (yield ammProgram.provider.connection.getLatestBlockhash(ammProgram.provider.connection.commitment)))).add(createConfigTx);
}
else {
index++;
}
}
});
}
static searchPoolsByToken(connection, tokenMint) {
return __awaiter(this, void 0, void 0, function* () {
const { ammProgram } = createProgram(connection);
const [poolsForTokenAMint, poolsForTokenBMint] = yield Promise.all([
ammProgram.account.pool.all([
{
memcmp: {
offset: 8 + 32,
bytes: tokenMint.toBase58(),
},
},
]),
ammProgram.account.pool.all([
{
memcmp: {
offset: 8 + 32 + 32,
bytes: tokenMint.toBase58(),
},
},
]),
]);
return [...poolsForTokenAMint, ...poolsForTokenBMint];
});
}
partnerClaimFees(partnerAddress, maxAmountA, maxAmountB) {
return __awaiter(this, void 0, void 0, function* () {
let preInstructions = [];
const [[partnerTokenA, createPartnerTokenAIx], [partnerTokenB, createPartnerTokenBIx]] = yield this.createATAPreInstructions(partnerAddress, [this.poolState.tokenAMint, this.poolState.tokenBMint]);
createPartnerTokenAIx && preInstructions.push(createPartnerTokenAIx);
createPartnerTokenBIx && preInstructions.push(createPartnerTokenBIx);
const claimTx = yield this.program.methods
.partnerClaimFee(maxAmountA, maxAmountB)
.accounts({
pool: this.address,
aVaultLp: this.poolState.aVaultLp,
protocolTokenAFee: this.poolState.protocolTokenAFee,
protocolTokenBFee: this.poolState.protocolTokenBFee,
partnerTokenA,
partnerTokenB,
tokenProgram: TOKEN_PROGRAM_ID,
partnerAuthority: partnerAddress,
})
.preInstructions(preInstructions)
.transaction();
return new Transaction(Object.assign({ feePayer: partnerAddress }, (yield this.program.provider.connection.getLatestBlockhash(this.program.provider.connection.commitment)))).add(claimTx);
});
}
static createCustomizablePermissionlessConstantProductPool(connection, payer, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, customizableParams, opt) {
return __awaiter(this, void 0, void 0, function* () {
const { vaultProgram, ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const [{ vaultPda: aVault, tokenVaultPda: aTokenVault, lpMintPda: aLpMintPda }, { vaultPda: bVault, tokenVaultPda: bTokenVault, lpMintPda: bLpMintPda },] = [getVaultPdas(tokenAMint, vaultProgram.programId), getVaultPdas(tokenBMint, vaultProgram.programId)];
const [aVaultAccount, bVaultAccount] = yield Promise.all([
vaultProgram.account.vault.fetchNullable(aVault),
vaultProgram.account.vault.fetchNullable(bVault),
]);
let aVaultLpMint = aLpMintPda;
let bVaultLpMint = bLpMintPda;
let preInstructions = [];
if (!aVaultAccount) {
const createVaultAIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenAMint);
createVaultAIx && preInstructions.push(createVaultAIx);
}
else {
aVaultLpMint = aVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
if (!bVaultAccount) {
const createVaultBIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenBMint);
createVaultBIx && preInstructions.push(createVaultBIx);
}
else {
bVaultLpMint = bVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
const poolPubkey = deriveCustomizablePermissionlessConstantProductPoolAddress(tokenAMint, tokenBMint, ammProgram.programId);
const [lpMint] = PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LP_MINT), poolPubkey.toBuffer()], ammProgram.programId);
const [[aVaultLp], [bVaultLp]] = [
PublicKey.findProgramAddressSync([aVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([bVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const [[payerTokenA, createPayerTokenAIx], [payerTokenB, createPayerTokenBIx]] = yield Promise.all([
getOrCreateATAInstruction(tokenAMint, payer, connection),
getOrCreateATAInstruction(tokenBMint, payer, connection),
]);
createPayerTokenAIx && preInstructions.push(createPayerTokenAIx);
createPayerTokenBIx && preInstructions.push(createPayerTokenBIx);
const [[protocolTokenAFee], [protocolTokenBFee]] = [
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenAMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenBMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const payerPoolLp = yield getAssociatedTokenAccount(lpMint, payer);
if (tokenAMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenA, BigInt(tokenAAmount.toString())));
}
if (tokenBMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenB, BigInt(tokenBAmount.toString())));
}
const [mintMetadata, _mintMetadataBump] = deriveMintMetadata(lpMint);
const createPermissionlessPoolTx = yield ammProgram.methods
.initializeCustomizablePermissionlessConstantProductPool(tokenAAmount, tokenBAmount, customizableParams)
.accounts({
pool: poolPubkey,
tokenAMint,
tokenBMint,
aVault,
bVault,
aVaultLpMint,
bVaultLpMint,
aVaultLp,
bVaultLp,
lpMint,
payerTokenA,
payerTokenB,
protocolTokenAFee,
protocolTokenBFee,
payerPoolLp,
aTokenVault,
bTokenVault,
mintMetadata,
metadataProgram: METAPLEX_PROGRAM,
payer,
rent: SYSVAR_RENT_PUBKEY,
vaultProgram: vaultProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
})
.preInstructions(preInstructions)
.transaction();
const latestBlockHash = yield ammProgram.provider.connection.getLatestBlockhash(ammProgram.provider.connection.commitment);
return new Transaction(Object.assign({ feePayer: payer }, latestBlockHash)).add(createPermissionlessPoolTx);
});
}
static createPermissionlessConstantProductPoolWithConfig2(connection, payer, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, config, opt) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const { vaultProgram, ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const [{ vaultPda: aVault, tokenVaultPda: aTokenVault, lpMintPda: aLpMintPda }, { vaultPda: bVault, tokenVaultPda: bTokenVault, lpMintPda: bLpMintPda },] = [getVaultPdas(tokenAMint, vaultProgram.programId), getVaultPdas(tokenBMint, vaultProgram.programId)];
const [aVaultAccount, bVaultAccount] = yield Promise.all([
vaultProgram.account.vault.fetchNullable(aVault),
vaultProgram.account.vault.fetchNullable(bVault),
]);
let aVaultLpMint = aLpMintPda;
let bVaultLpMint = bLpMintPda;
let preInstructions = [];
if (!aVaultAccount) {
const createVaultAIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenAMint);
createVaultAIx && preInstructions.push(createVaultAIx);
}
else {
aVaultLpMint = aVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
if (!bVaultAccount) {
const createVaultBIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenBMint);
createVaultBIx && preInstructions.push(createVaultBIx);
}
else {
bVaultLpMint = bVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
const poolPubkey = deriveConstantProductPoolAddressWithConfig(tokenAMint, tokenBMint, config, ammProgram.programId);
const [lpMint] = PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LP_MINT), poolPubkey.toBuffer()], ammProgram.programId);
const [[aVaultLp], [bVaultLp]] = [
PublicKey.findProgramAddressSync([aVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([bVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const [[payerTokenA, createPayerTokenAIx], [payerTokenB, createPayerTokenBIx]] = yield Promise.all([
getOrCreateATAInstruction(tokenAMint, payer, connection),
getOrCreateATAInstruction(tokenBMint, payer, connection),
]);
createPayerTokenAIx && preInstructions.push(createPayerTokenAIx);
createPayerTokenBIx && preInstructions.push(createPayerTokenBIx);
const [[protocolTokenAFee], [protocolTokenBFee]] = [
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenAMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenBMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const payerPoolLp = yield getAssociatedTokenAccount(lpMint, payer);
if (tokenAMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenA, BigInt(tokenAAmount.toString())));
}
if (tokenBMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenB, BigInt(tokenBAmount.toString())));
}
const [mintMetadata, _mintMetadataBump] = deriveMintMetadata(lpMint);
const activationPoint = (opt === null || opt === void 0 ? void 0 : opt.activationPoint) || null;
const lpAmount = sqrt(tokenAAmount.mul(tokenBAmount));
const { userLockAmount, feeWrapperLockAmount } = calculateLockAmounts(lpAmount, (_a = opt === null || opt === void 0 ? void 0 : opt.stakeLiquidity) === null || _a === void 0 ? void 0 : _a.ratio);
const createPoolPostInstructions = [];
if ((opt === null || opt === void 0 ? void 0 : opt.lockLiquidity) && userLockAmount.gt(new BN(0))) {
const [lockEscrowPK] = deriveLockEscrowPda(poolPubkey, payer, ammProgram.programId);
const createLockEscrowIx = yield ammProgram.methods
.createLockEscrow()
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowPK,
owner: payer,
lpMint,
payer,
systemProgram: SystemProgram.programId,
})
.instruction();
createPoolPostInstructions.push(createLockEscrowIx);
const [escrowAta, createEscrowAtaIx] = yield getOrCreateATAInstruction(lpMint, lockEscrowPK, connection, payer);
createEscrowAtaIx && createPoolPostInstructions.push(createEscrowAtaIx);
const lockIx = yield ammProgram.methods
.lock(userLockAmount)
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowPK,
owner: payer,
lpMint,
sourceTokens: payerPoolLp,
escrowVault: escrowAta,
tokenProgram: TOKEN_PROGRAM_ID,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
})
.instruction();
createPoolPostInstructions.push(lockIx);
}
const createPermissionlessPoolTx = yield ammProgram.methods
.initializePermissionlessConstantProductPoolWithConfig2(tokenAAmount, tokenBAmount, activationPoint)
.accounts({
pool: poolPubkey,
tokenAMint,
tokenBMint,
aVault,
bVault,
aVaultLpMint,
bVaultLpMint,
aVaultLp,
bVaultLp,
lpMint,
payerTokenA,
payerTokenB,
protocolTokenAFee,
protocolTokenBFee,
payerPoolLp,
aTokenVault,
bTokenVault,
mintMetadata,
metadataProgram: METAPLEX_PROGRAM,
payer,
config,
rent: SYSVAR_RENT_PUBKEY,
vaultProgram: vaultProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
})
.postInstructions(createPoolPostInstructions)
.transaction();
const ixs = [];
if (preInstructions.length) {
// https://explorer.solana.com/tx/3G95TWMmTLbZ7aAgyGmZd8JFk19ye8whVB5cXhYCCsUWSUsnuVMqPMXbPiY5WaF4zE2Sz7CG5e4jTj8NQbCnUG14?cluster=devnet
// Create 2 dynamic vault consume around 190k CU. Each create ATA + Wrap SOL around 23k
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 300000,
});
ixs.push([setComputeUnitLimitIx, ...preInstructions]);
}
// https://explorer.solana.com/tx/4X37hBoUNwpmHKNGpQB3M72xDA7yhFKhbYrkBQUK4skBGD7P9LdWgb2WpvFGHcxYi13e9bdwhetsqmULeW7nUDbW?cluster=devnet
// https://explorer.solana.com/tx/3xMGCQ9GAqSMZH1uhXXc1quZit61DXr1KsbVxdBN1zCkQy1GTXBGuWgxzWfMEdecdRi859m3mYQKuLyemvR18VHS?cluster=devnet
// Create dynamic pool consume around 287k, create lock escrow + lock liquidity around 84k
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 450000,
});
ixs.push([setComputeUnitLimitIx, createPermissionlessPoolTx]);
if (feeWrapperLockAmount.gt(new BN(0))) {
const preInstructions = [];
const createFeeVaultIxs = yield StakeForFee.createFeeVaultInstructions(connection, poolPubkey, payer, tokenAMint, tokenBMint);
preInstructions.push(...createFeeVaultIxs);
const vaultKey = deriveFeeVault(poolPubkey, STAKE_FOR_FEE_PROGRAM_ID);
const [lockEscrowFeeVaultPK] = deriveLockEscrowPda(poolPubkey, vaultKey, ammProgram.programId);
const [escrowFeeVaultAta, createEscrowFeeVaultAtaIx] = yield getOrCreateATAInstruction(lpMint, lockEscrowFeeVaultPK, connection, payer);
createEscrowFeeVaultAtaIx && preInstructions.push(createEscrowFeeVaultAtaIx);
const lockTx = yield ammProgram.methods
.lock(feeWrapperLockAmount)
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowFeeVaultPK,
owner: payer,
lpMint,
sourceTokens: payerPoolLp,
escrowVault: escrowFeeVaultAta,
tokenProgram: TOKEN_PROGRAM_ID,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
})
.preInstructions(preInstructions)
.transaction();
ixs.push(lockTx);
}
if (opt === null || opt === void 0 ? void 0 : opt.swapLiquidity) {
const protocolTokenFee = deriveProtocolTokenFee(poolPubkey, tokenBMint, ammProgram.programId);
const swapTx = yield ammProgram.methods
.swap(opt.swapLiquidity.inAmount, opt.swapLiquidity.minAmountOut)
.accounts({
aTokenVault,
bTokenVault,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
userSourceToken: payerTokenB,
userDestinationToken: payerTokenA,
user: payer,
protocolTokenFee,
pool: poolPubkey,
tokenProgram: TOKEN_PROGRAM_ID,
vaultProgram: vaultProgram.programId,
})
.transaction();
ixs.push(swapTx);
}
const resultTx = yield createTransactions(connection, ixs, payer);
return resultTx;
});
}
static createPermissionlessConstantProductPoolWithConfig(connection, payer, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, config, opt) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const { vaultProgram, ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const [{ vaultPda: aVault, tokenVaultPda: aTokenVault, lpMintPda: aLpMintPda }, { vaultPda: bVault, tokenVaultPda: bTokenVault, lpMintPda: bLpMintPda },] = [getVaultPdas(tokenAMint, vaultProgram.programId), getVaultPdas(tokenBMint, vaultProgram.programId)];
const [aVaultAccount, bVaultAccount] = yield Promise.all([
vaultProgram.account.vault.fetchNullable(aVault),
vaultProgram.account.vault.fetchNullable(bVault),
]);
let aVaultLpMint = aLpMintPda;
let bVaultLpMint = bLpMintPda;
let preInstructions = [];
if (!aVaultAccount) {
const createVaultAIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenAMint);
createVaultAIx && preInstructions.push(createVaultAIx);
}
else {
aVaultLpMint = aVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
if (!bVaultAccount) {
const createVaultBIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenBMint);
createVaultBIx && preInstructions.push(createVaultBIx);
}
else {
bVaultLpMint = bVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
const poolPubkey = deriveConstantProductPoolAddressWithConfig(tokenAMint, tokenBMint, config, ammProgram.programId);
const [lpMint] = PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LP_MINT), poolPubkey.toBuffer()], ammProgram.programId);
const [[aVaultLp], [bVaultLp]] = [
PublicKey.findProgramAddressSync([aVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([bVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const [[payerTokenA, createPayerTokenAIx], [payerTokenB, createPayerTokenBIx]] = yield Promise.all([
getOrCreateATAInstruction(tokenAMint, payer, connection),
getOrCreateATAInstruction(tokenBMint, payer, connection),
]);
createPayerTokenAIx && !(opt === null || opt === void 0 ? void 0 : opt.skipAAta) && preInstructions.push(createPayerTokenAIx);
createPayerTokenBIx && !(opt === null || opt === void 0 ? void 0 : opt.skipBAta) && preInstructions.push(createPayerTokenBIx);
const [[protocolTokenAFee], [protocolTokenBFee]] = [
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenAMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenBMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const payerPoolLp = getAssociatedTokenAccount(lpMint, payer);
if (tokenAMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenA, BigInt(tokenAAmount.toString())));
}
if (tokenBMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenB, BigInt(tokenBAmount.add((_b = (_a = opt === null || opt === void 0 ? void 0 : opt.swapLiquidity) === null || _a === void 0 ? void 0 : _a.inAmount) !== null && _b !== void 0 ? _b : new BN(0)).toString())));
}
const [mintMetadata, _mintMetadataBump] = deriveMintMetadata(lpMint);
const createPoolPostInstructions = [];
if (opt === null || opt === void 0 ? void 0 : opt.lockLiquidity) {
const [lockEscrowPK] = deriveLockEscrowPda(poolPubkey, payer, ammProgram.programId);
const createLockEscrowIx = yield ammProgram.methods
.createLockEscrow()
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowPK,
owner: payer,
lpMint,
payer,
systemProgram: SystemProgram.programId,
})
.instruction();
createPoolPostInstructions.push(createLockEscrowIx);
const [escrowAta, createEscrowAtaIx] = yield getOrCreateATAInstruction(lpMint, lockEscrowPK, connection, payer);
createEscrowAtaIx && createPoolPostInstructions.push(createEscrowAtaIx);
const lockIx = yield ammProgram.methods
.lock(U64_MAX)
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowPK,
owner: payer,
lpMint,
sourceTokens: payerPoolLp,
escrowVault: escrowAta,
tokenProgram: TOKEN_PROGRAM_ID,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
})
.instruction();
createPoolPostInstructions.push(lockIx);
}
const createPermissionlessPoolTx = yield ammProgram.methods
.initializePermissionlessConstantProductPoolWithConfig(tokenAAmount, tokenBAmount)
.accounts({
pool: poolPubkey,
tokenAMint,
tokenBMint,
aVault,
bVault,
aVaultLpMint,
bVaultLpMint,
aVaultLp,
bVaultLp,
lpMint,
payerTokenA,
payerTokenB,
protocolTokenAFee,
protocolTokenBFee,
payerPoolLp,
aTokenVault,
bTokenVault,
mintMetadata,
metadataProgram: METAPLEX_PROGRAM,
payer,
config,
rent: SYSVAR_RENT_PUBKEY,
vaultProgram: vaultProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
})
.postInstructions(createPoolPostInstructions)
.transaction();
const ixs = [];
if (preInstructions.length) {
// Create 2 dynamic vault consume around 190k CU. Each create ATA + Wrap SOL around 23k
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 300000,
});
ixs.push([setComputeUnitLimitIx, ...preInstructions]);
}
// Create dynamic pool consume around 287k, create lock escrow + lock liquidity around 84k
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 450000,
});
ixs.push([setComputeUnitLimitIx, createPermissionlessPoolTx]);
if (opt === null || opt === void 0 ? void 0 : opt.swapLiquidity) {
const protocolTokenFee = deriveProtocolTokenFee(poolPubkey, tokenBMint, ammProgram.programId);
const swapTx = yield ammProgram.methods
.swap(opt.swapLiquidity.inAmount, opt.swapLiquidity.minAmountOut)
.accounts({
aTokenVault,
bTokenVault,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
userSourceToken: payerTokenB,
userDestinationToken: payerTokenA,
user: payer,
protocolTokenFee,
pool: poolPubkey,
tokenProgram: TOKEN_PROGRAM_ID,
vaultProgram: vaultProgram.programId,
})
.transaction();
ixs.push(swapTx);
}
const resultTx = yield createTransactions(connection, ixs, payer);
return resultTx;
});
}
static createPermissionlessConstantProductMemecoinPoolWithConfig(connection, payer, tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, config, memecoinInfo, opt) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
const { vaultProgram, ammProgram } = createProgram(connection, opt === null || opt === void 0 ? void 0 : opt.programId);
const createTokenIxs = [];
if (!memecoinInfo.isMinted) {
if (!memecoinInfo.keypair ||
!memecoinInfo.payer ||
!memecoinInfo.mintAuthority ||
!memecoinInfo.mintAmount ||
!memecoinInfo.assetData ||
memecoinInfo.freezeAuthority === undefined) {
throw new Error('Missing required fields for minting Memecoin.');
}
const { tx: mintTx, mintAccount } = yield createMint(connection, memecoinInfo.keypair, memecoinInfo.payer, memecoinInfo.assetData, memecoinInfo.mintAuthority, memecoinInfo.freezeAuthority, memecoinInfo.decimals || 0, TOKEN_PROGRAM_ID);
createTokenIxs.push(...mintTx.instructions);
const [ata, createAtaIx] = yield getOrCreateATAInstruction(mintAccount.publicKey, memecoinInfo.mintAuthority, connection, memecoinInfo.payer);
createAtaIx && createTokenIxs.push(createAtaIx);
const mintToIx = createMintToInstruction(mintAccount.publicKey, ata, memecoinInfo.mintAuthority, BigInt(memecoinInfo.mintAmount.toString()));
createTokenIxs.push(mintToIx);
const revokeMintAuthorityIx = createSetAuthorityInstruction(mintAccount.publicKey, memecoinInfo.mintAuthority, 0, null);
createTokenIxs.push(revokeMintAuthorityIx);
}
let preInstructions = [...createTokenIxs];
const [{ vaultPda: aVault, tokenVaultPda: aTokenVault, lpMintPda: aLpMintPda }, { vaultPda: bVault, tokenVaultPda: bTokenVault, lpMintPda: bLpMintPda },] = [getVaultPdas(tokenAMint, vaultProgram.programId), getVaultPdas(tokenBMint, vaultProgram.programId)];
const [aVaultAccount, bVaultAccount] = yield Promise.all([
vaultProgram.account.vault.fetchNullable(aVault),
vaultProgram.account.vault.fetchNullable(bVault),
]);
let aVaultLpMint = aLpMintPda;
let bVaultLpMint = bLpMintPda;
if (!aVaultAccount) {
const createVaultAIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenAMint);
createVaultAIx && preInstructions.push(createVaultAIx);
}
else {
aVaultLpMint = aVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
if (!bVaultAccount) {
const createVaultBIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, tokenBMint);
createVaultBIx && preInstructions.push(createVaultBIx);
}
else {
bVaultLpMint = bVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
const poolPubkey = deriveConstantProductPoolAddressWithConfig(tokenAMint, tokenBMint, config, ammProgram.programId);
const [lpMint] = PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LP_MINT), poolPubkey.toBuffer()], ammProgram.programId);
const [[aVaultLp], [bVaultLp]] = [
PublicKey.findProgramAddressSync([aVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([bVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const [[payerTokenA, createPayerTokenAIx], [payerTokenB, createPayerTokenBIx]] = yield Promise.all([
getOrCreateATAInstruction(tokenAMint, payer, connection),
getOrCreateATAInstruction(tokenBMint, payer, connection),
]);
createPayerTokenAIx && !(opt === null || opt === void 0 ? void 0 : opt.skipAAta) && preInstructions.push(createPayerTokenAIx);
createPayerTokenBIx && !(opt === null || opt === void 0 ? void 0 : opt.skipBAta) && preInstructions.push(createPayerTokenBIx);
const [[protocolTokenAFee], [protocolTokenBFee]] = [
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenAMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([Buffer.from(SEEDS.FEE), tokenBMint.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const payerPoolLp = getAssociatedTokenAccount(lpMint, payer);
if (tokenAMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenA, BigInt(tokenAAmount.toString())));
}
if (tokenBMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(payer, payerTokenB, BigInt(tokenBAmount.add((_b = (_a = opt === null || opt === void 0 ? void 0 : opt.swapLiquidity) === null || _a === void 0 ? void 0 : _a.inAmount) !== null && _b !== void 0 ? _b : new BN(0)).toString())));
}
const [mintMetadata, _mintMetadataBump] = deriveMintMetadata(lpMint);
const lpAmount = sqrt(tokenAAmount.mul(tokenBAmount));
const { userLockAmount, feeWrapperLockAmount } = calculateLockAmounts(lpAmount, (_c = opt === null || opt === void 0 ? void 0 : opt.stakeLiquidity) === null || _c === void 0 ? void 0 : _c.ratio);
const createPoolPostInstructions = [];
if ((opt === null || opt === void 0 ? void 0 : opt.lockLiquidity) && userLockAmount.gt(new BN(0))) {
const [lockEscrowPK] = deriveLockEscrowPda(poolPubkey, payer, ammProgram.programId);
const createLockEscrowIx = yield ammProgram.methods
.createLockEscrow()
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowPK,
owner: payer,
lpMint,
payer,
systemProgram: SystemProgram.programId,
})
.instruction();
createPoolPostInstructions.push(createLockEscrowIx);
const [escrowAta, createEscrowAtaIx] = yield getOrCreateATAInstruction(lpMint, lockEscrowPK, connection, payer);
createEscrowAtaIx && createPoolPostInstructions.push(createEscrowAtaIx);
const lockIx = yield ammProgram.methods
.lock(userLockAmount)
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowPK,
owner: payer,
lpMint,
sourceTokens: payerPoolLp,
escrowVault: escrowAta,
tokenProgram: TOKEN_PROGRAM_ID,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
})
.instruction();
createPoolPostInstructions.push(lockIx);
}
const createPermissionlessPoolTx = yield ammProgram.methods
.initializePermissionlessConstantProductPoolWithConfig(tokenAAmount, tokenBAmount)
.accounts({
pool: poolPubkey,
tokenAMint,
tokenBMint,
aVault,
bVault,
aVaultLpMint,
bVaultLpMint,
aVaultLp,
bVaultLp,
lpMint,
payerTokenA,
payerTokenB,
protocolTokenAFee,
protocolTokenBFee,
payerPoolLp,
aTokenVault,
bTokenVault,
mintMetadata,
metadataProgram: METAPLEX_PROGRAM,
payer,
config,
rent: SYSVAR_RENT_PUBKEY,
vaultProgram: vaultProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
})
.postInstructions(createPoolPostInstructions)
.transaction();
const ixs = [];
if (preInstructions.length) {
// Create 2 dynamic vault consume around 190k CU. Each create ATA + Wrap SOL around 23k
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 300000,
});
ixs.push([setComputeUnitLimitIx, ...preInstructions]);
}
// Create dynamic pool consume around 287k, create lock escrow + lock liquidity around 84k
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 450000,
});
ixs.push([setComputeUnitLimitIx, createPermissionlessPoolTx]);
if (feeWrapperLockAmount.gt(new BN(0))) {
const preInstructions = [];
const initFeeVaultParams = (opt === null || opt === void 0 ? void 0 : opt.feeVault) ? Object.assign(Object.assign({}, opt.feeVault), { padding: new Array(64).fill(0) }) : undefined;
const createFeeVaultIxs = yield StakeForFee.createFeeVaultInstructions(connection, poolPubkey, payer, tokenAMint, tokenBMint, initFeeVaultParams);
preInstructions.push(...createFeeVaultIxs);
const vaultKey = deriveFeeVault(poolPubkey, STAKE_FOR_FEE_PROGRAM_ID);
const [lockEscrowFeeVaultPK] = deriveLockEscrowPda(poolPubkey, vaultKey, ammProgram.programId);
const [escrowFeeVaultAta, createEscrowFeeVaultAtaIx] = yield getOrCreateATAInstruction(lpMint, lockEscrowFeeVaultPK, connection, payer);
createEscrowFeeVaultAtaIx && preInstructions.push(createEscrowFeeVaultAtaIx);
const lockTx = yield ammProgram.methods
.lock(feeWrapperLockAmount)
.accounts({
pool: poolPubkey,
lockEscrow: lockEscrowFeeVaultPK,
owner: payer,
lpMint,
sourceTokens: payerPoolLp,
escrowVault: escrowFeeVaultAta,
tokenProgram: TOKEN_PROGRAM_ID,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
})
.preInstructions(preInstructions)
.transaction();
ixs.push(lockTx);
}
if (opt === null || opt === void 0 ? void 0 : opt.swapLiquidity) {
const protocolTokenFee = deriveProtocolTokenFee(poolPubkey, tokenBMint, ammProgram.programId);
const swapTx = yield ammProgram.methods
.swap(opt.swapLiquidity.inAmount, opt.swapLiquidity.minAmountOut)
.accounts({
aTokenVault,
bTokenVault,
aVault,
bVault,
aVaultLp,
bVaultLp,
aVaultLpMint,
bVaultLpMint,
userSourceToken: payerTokenB,
userDestinationToken: payerTokenA,
user: payer,
protocolTokenFee,
pool: poolPubkey,
tokenProgram: TOKEN_PROGRAM_ID,
vaultProgram: vaultProgram.programId,
})
.transaction();
ixs.push(swapTx);
}
const resultTx = yield createTransactions(connection, ixs, payer);
return resultTx;
});
}
static createPermissionlessPool(connection, payer, tokenInfoA, tokenInfoB, tokenAAmount, tokenBAmount, isStable, tradeFeeBps, opt) {
return __awaiter(this, void 0, void 0, function* () {
const { vaultProgram, 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 [{ vaultPda: aVault, tokenVaultPda: aTokenVault, lpMintPda: aLpMintPda }, { vaultPda: bVault, tokenVaultPda: bTokenVault, lpMintPda: bLpMintPda },] = [getVaultPdas(tokenAMint, vaultProgram.programId), getVaultPdas(tokenBMint, vaultProgram.programId)];
const [aVaultAccount, bVaultAccount] = yield Promise.all([
vaultProgram.account.vault.fetchNullable(aVault),
vaultProgram.account.vault.fetchNullable(bVault),
]);
let aVaultLpMint = aLpMintPda;
let bVaultLpMint = bLpMintPda;
let preInstructions = [];
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 600000,
});
preInstructions.push(setComputeUnitLimitIx);
if (!aVaultAccount) {
const createVaultAIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, new PublicKey(tokenInfoA.address));
createVaultAIx && preInstructions.push(createVaultAIx);
}
else {
aVaultLpMint = aVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
if (!bVaultAccount) {
const createVaultBIx = yield VaultImpl.createPermissionlessVaultInstruction(connection, payer, new PublicKey(tokenInfoB.address));
createVaultBIx && preInstructions.push(createVaultBIx);
}
else {
bVaultLpMint = bVaultAccount.lpMint; // Old vault doesn't have lp mint pda
}
const poolPubkey = derivePoolAddress(connection, tokenInfoA, tokenInfoB, isStable, tradeFeeBps, {
programId: opt === null || opt === void 0 ? void 0 : opt.programId,
});
const [[aVaultLp], [bVaultLp]] = [
PublicKey.findProgramAddressSync([aVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
PublicKey.findProgramAddressSync([bVault.toBuffer(), poolPubkey.toBuffer()], ammProgram.programId),
];
const [[payerTokenA, createPayerTokenAIx], [payerTokenB