diff --git a/bun.lockb b/bun.lockb index 65cabfc..65764a1 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/config/create_dynamic_amm_pool_with_permissioned_authority_vault.json b/config/create_dynamic_amm_pool_with_permissioned_authority_vault.json new file mode 100644 index 0000000..f7d6312 --- /dev/null +++ b/config/create_dynamic_amm_pool_with_permissioned_authority_vault.json @@ -0,0 +1,28 @@ +{ + "rpcUrl": "https://api.mainnet-beta.solana.com", + "dryRun": true, + "keypairFilePath": "keypair.json", + "computeUnitPriceMicroLamports": 100000, + "baseMint": "3B5wuUrMEi5yATD7on46hKfej3pfmd7t1RKgrsN3pump", + "quoteSymbol": "SOL", + "dynamicAmm": { + "baseAmount": "100", + "quoteAmount": "0.001", + "tradeFeeNumerator": 2500, + "activationType": "timestamp", + "activationPoint": null, + "hasAlphaVault": true + }, + "alphaVault": { + "poolType": "dynamic", + "alphaVaultType": "fcfs", + "depositingPoint": 1733547099, + "startVestingPoint": 1733548099, + "endVestingPoint": 1733549099, + "maxDepositCap": 100, + "individualDepositingCap": 1, + "escrowFee": 0, + "whitelistMode": "permissioned_with_authority", + "whitelistFilepath": "./config/whitelist_wallet.csv" + } +} \ No newline at end of file diff --git a/config/create_dynamic_amm_pool_with_permissioned_with_merkle_proof_vault.json b/config/create_dynamic_amm_pool_with_permissioned_with_merkle_proof_vault.json new file mode 100644 index 0000000..309f1a3 --- /dev/null +++ b/config/create_dynamic_amm_pool_with_permissioned_with_merkle_proof_vault.json @@ -0,0 +1,28 @@ +{ + "rpcUrl": "https://api.mainnet-beta.solana.com", + "dryRun": true, + "keypairFilePath": "keypair.json", + "computeUnitPriceMicroLamports": 100000, + "baseMint": "3B5wuUrMEi5yATD7on46hKfej3pfmd7t1RKgrsN3pump", + "quoteSymbol": "SOL", + "dynamicAmm": { + "baseAmount": "100", + "quoteAmount": "0.001", + "tradeFeeNumerator": 2500, + "activationType": "timestamp", + "activationPoint": null, + "hasAlphaVault": true + }, + "alphaVault": { + "poolType": "dynamic", + "alphaVaultType": "fcfs", + "depositingPoint": 1733547099, + "startVestingPoint": 1733548099, + "endVestingPoint": 1733549099, + "maxDepositCap": 100, + "individualDepositingCap": 1, + "escrowFee": 0, + "whitelistMode": "permissioned_with_merkle_proof", + "whitelistFilepath": "./config/whitelist_wallet.csv" + } +} \ No newline at end of file diff --git a/config/whitelist_wallet.csv b/config/whitelist_wallet.csv new file mode 100644 index 0000000..ab07f42 --- /dev/null +++ b/config/whitelist_wallet.csv @@ -0,0 +1,2 @@ +address,maxAmount +ETp2wTykSe3iGYzPCn6upP8NVB7a1pe6WTZidGNJH7KC,0.5 \ No newline at end of file diff --git a/package.json b/package.json index d8391c1..db7157f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "bun test", "create-dynamic-amm-pool": "bun run src/create_pool.ts --config ./config/create_dynamic_amm_pool.json", "create-dlmm-pool": "bun run src/create_pool.ts --config ./config/create_dlmm_pool.json", - "start-test-validator": "solana-test-validator --bind-address 0.0.0.0 --account-dir ./src/tests/artifacts/accounts --bpf-program LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ ./src/tests/artifacts/lb_clmm.so --bpf-program Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB ./src/tests/artifacts/dynamic_amm.so --bpf-program 24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi ./src/tests/artifacts/dynamic_vault.so --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./src/tests/artifacts/metaplex.so --mint bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1 --reset" + "start-test-validator": "solana-test-validator --bind-address 0.0.0.0 --account-dir ./src/tests/artifacts/accounts --bpf-program LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ ./src/tests/artifacts/lb_clmm.so --bpf-program Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB ./src/tests/artifacts/dynamic_amm.so --bpf-program 24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi ./src/tests/artifacts/dynamic_vault.so --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./src/tests/artifacts/metaplex.so --bpf-program SNPmGgnywBvvrAKMLundzG6StojyHTHDLu7T4sdhP4k ./src/tests/artifacts/alpha_vault.so --mint bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1 --reset" }, "dependencies": { "@coral-xyz/anchor": "^0.28.0", @@ -20,6 +20,7 @@ "@types/jest": "^29.5.14", "ajv": "^8.17.1", "bn.js": "^5.2.1", + "csv-parse": "^5.6.0", "decimal.js": "^10.4.3" }, "author": "", diff --git a/src/create_alpha_vault.ts b/src/create_alpha_vault.ts index 5eec1e9..0237522 100644 --- a/src/create_alpha_vault.ts +++ b/src/create_alpha_vault.ts @@ -18,11 +18,19 @@ import { parseConfigFromCli, getAlphaVaultPoolType, modifyComputeUnitPriceIx, + AlphaVaultTypeConfig, + PoolTypeConfig, + toAlphaVaulSdkPoolType, + WhitelistModeConfig, + parseCsv, } from "."; -import { Wallet } from "@coral-xyz/anchor"; +import { AnchorProvider, Wallet } from "@coral-xyz/anchor"; import { BN } from "bn.js"; -import AlphaVault, { PoolType } from "@meteora-ag/alpha-vault"; +import AlphaVault, { + PoolType, + WalletDepositCap, +} from "@meteora-ag/alpha-vault"; import { LBCLMM_PROGRAM_IDS, deriveCustomizablePermissionlessLbPair, @@ -31,6 +39,12 @@ import { deriveCustomizablePermissionlessConstantProductPoolAddress, createProgram, } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils"; +import { + createFcfsAlphaVault, + createPermissionedAlphaVaultWithAuthority, + createPermissionedAlphaVaultWithMerkleProof, + createProrataAlphaVault, +} from "./libs/create_alpha_vault_utils"; async function main() { let config: MeteoraConfig = parseConfigFromCli(); @@ -45,6 +59,9 @@ async function main() { const connection = new Connection(config.rpcUrl, DEFAULT_COMMITMENT_LEVEL); const wallet = new Wallet(keypair); + const provider = new AnchorProvider(connection, wallet, { + commitment: connection.commitment, + }); if (!config.baseMint) { throw new Error("Missing baseMint in configuration"); @@ -59,17 +76,16 @@ async function main() { if (!config.alphaVault) { throw new Error("Missing alpha vault in configuration"); } - - const poolType = getAlphaVaultPoolType(config.alphaVault.poolType); + const poolType = config.alphaVault.poolType; let poolKey: PublicKey; - if (poolType == PoolType.DYNAMIC) { + if (poolType == PoolTypeConfig.Dynamic) { poolKey = deriveCustomizablePermissionlessConstantProductPoolAddress( baseMint, quoteMint, createProgram(connection).ammProgram.programId, ); - } else if (poolType == PoolType.DLMM) { + } else if (poolType == PoolTypeConfig.Dlmm) { [poolKey] = deriveCustomizablePermissionlessLbPair( baseMint, quoteMint, @@ -79,181 +95,120 @@ async function main() { throw new Error(`Invalid pool type ${poolType}`); } - console.log( - `\n> Pool address: ${poolKey}, pool type ${config.alphaVault.poolType}`, - ); + console.log(`\n> Pool address: ${poolKey}, pool type ${poolType}`); - let initAlphaVaultTx: Transaction | null = null; - if (config.alphaVault.alphaVaultType == "fcfs") { - initAlphaVaultTx = await createFcfsAlphaVault( + // create permissioned alpha vault with authority + if ( + config.alphaVault.whitelistMode == + WhitelistModeConfig.PermissionedWithAuthority + ) { + if (!config.alphaVault.whitelistFilepath) { + throw new Error("Missing whitelist filepath in configuration"); + } + + const whitelistList = await parseWhitelistListFromCsv( + config.alphaVault.whitelistFilepath, + quoteDecimals, + ); + + await createPermissionedAlphaVaultWithAuthority( connection, wallet, - poolType, + config.alphaVault.alphaVaultType, + toAlphaVaulSdkPoolType(poolType), poolKey, baseMint, quoteMint, quoteDecimals, - config.alphaVault as FcfsAlphaVaultConfig, + config.alphaVault, + whitelistList, + config.dryRun, + config.computeUnitPriceMicroLamports, + ); + } else if ( + config.alphaVault.whitelistMode == + WhitelistModeConfig.PermissionedWithMerkleProof + ) { + if (!config.alphaVault.whitelistFilepath) { + throw new Error("Missing whitelist filepath in configuration"); + } + + const whitelistList = await parseWhitelistListFromCsv( + config.alphaVault.whitelistFilepath, + quoteDecimals, ); - } else if (config.alphaVault.alphaVaultType == "prorata") { - initAlphaVaultTx = await createProrataAlphaVault( + await createPermissionedAlphaVaultWithMerkleProof( connection, wallet, - poolType, + config.alphaVault.alphaVaultType, + toAlphaVaulSdkPoolType(poolType), poolKey, baseMint, quoteMint, quoteDecimals, - config.alphaVault as ProrataAlphaVaultConfig, - ); - } else { - throw new Error( - `Invalid alpha vault type ${config.alphaVault.alphaVaultType}`, - ); - } - modifyComputeUnitPriceIx( - initAlphaVaultTx, - config.computeUnitPriceMicroLamports, - ); - - if (config.dryRun) { - console.log(`\n> Simulating init alpha vault tx...`); - await runSimulateTransaction(connection, [wallet.payer], wallet.publicKey, [ - initAlphaVaultTx, - ]); - } else { - console.log(`>> Sending init alpha vault transaction...`); - const initAlphaVaulTxHash = await sendAndConfirmTransaction( - connection, - initAlphaVaultTx, - [wallet.payer], - ).catch((err) => { - console.error(err); - throw err; - }); - console.log( - `>>> Alpha vault initialized successfully with tx hash: ${initAlphaVaulTxHash}`, + config.alphaVault, + whitelistList, + config.dryRun, + config.computeUnitPriceMicroLamports, ); + } else if ( + config.alphaVault.whitelistMode == WhitelistModeConfig.Permissionless + ) { + if (config.alphaVault.alphaVaultType == AlphaVaultTypeConfig.Fcfs) { + await createFcfsAlphaVault( + connection, + wallet, + toAlphaVaulSdkPoolType(poolType), + poolKey, + baseMint, + quoteMint, + quoteDecimals, + config.alphaVault as FcfsAlphaVaultConfig, + config.dryRun, + config.computeUnitPriceMicroLamports, + ); + } else if ( + config.alphaVault.alphaVaultType == AlphaVaultTypeConfig.Prorata + ) { + await createProrataAlphaVault( + connection, + wallet, + toAlphaVaulSdkPoolType(poolType), + poolKey, + baseMint, + quoteMint, + quoteDecimals, + config.alphaVault as ProrataAlphaVaultConfig, + config.dryRun, + config.computeUnitPriceMicroLamports, + ); + } else { + throw new Error( + `Invalid alpha vault type ${config.alphaVault.alphaVaultType}`, + ); + } } } -async function createFcfsAlphaVault( - connection: Connection, - wallet: Wallet, - poolType: PoolType, - poolAddress: PublicKey, - baseMint: PublicKey, - quoteMint: PublicKey, - quoteDecimals: number, - params: FcfsAlphaVaultConfig, -): Promise { - let maxDepositingCap = getAmountInLamports( - params.maxDepositCap, - quoteDecimals, - ); - let individualDepositingCap = getAmountInLamports( - params.individualDepositingCap, - quoteDecimals, - ); - let escrowFee = getAmountInLamports(params.escrowFee, quoteDecimals); - let whitelistMode = getAlphaVaultWhitelistMode(params.whitelistMode); - - console.log(`\n> Initializing FcfsAlphaVault...`); - console.log(`- Using poolType: ${poolType}`); - console.log(`- Using poolMint ${poolAddress}`); - console.log(`- Using baseMint ${baseMint}`); - console.log(`- Using quoteMint ${quoteMint}`); - console.log(`- Using depositingPoint ${params.depositingPoint}`); - console.log(`- Using startVestingPoint ${params.startVestingPoint}`); - console.log(`- Using endVestingPoint ${params.endVestingPoint}`); - console.log( - `- Using maxDepositingCap ${params.maxDepositCap}. In lamports ${maxDepositingCap}`, - ); - console.log( - `- Using individualDepositingCap ${params.individualDepositingCap}. In lamports ${individualDepositingCap}`, - ); - console.log( - `- Using escrowFee ${params.escrowFee}. In lamports ${escrowFee}`, - ); - console.log( - `- Using whitelistMode ${params.whitelistMode}. In value ${whitelistMode}`, - ); - - const tx = await AlphaVault.createCustomizableFcfsVault( - connection, - { - quoteMint, - baseMint, - poolAddress, - poolType, - depositingPoint: new BN(params.depositingPoint), - startVestingPoint: new BN(params.startVestingPoint), - endVestingPoint: new BN(params.endVestingPoint), - maxDepositingCap, - individualDepositingCap, - escrowFee, - whitelistMode, - }, - wallet.publicKey, - { - cluster: "mainnet-beta", - }, - ); - return tx; +interface WhitelistCsv { + address: string; + maxAmount: string; } - -async function createProrataAlphaVault( - connection: Connection, - wallet: Wallet, - poolType: PoolType, - poolAddress: PublicKey, - baseMint: PublicKey, - quoteMint: PublicKey, +async function parseWhitelistListFromCsv( + csvFilepath: string, quoteDecimals: number, - params: ProrataAlphaVaultConfig, -): Promise { - let maxBuyingCap = getAmountInLamports(params.maxBuyingCap, quoteDecimals); - let escrowFee = getAmountInLamports(params.escrowFee, quoteDecimals); - let whitelistMode = getAlphaVaultWhitelistMode(params.whitelistMode); - - console.log(`\n> Initializing ProrataAlphaVault...`); - console.log(`- Using poolType: ${poolType}`); - console.log(`- Using poolMint ${poolAddress}`); - console.log(`- Using baseMint ${baseMint}`); - console.log(`- Using quoteMint ${quoteMint}`); - console.log(`- Using depositingPoint ${params.depositingPoint}`); - console.log(`- Using startVestingPoint ${params.startVestingPoint}`); - console.log(`- Using endVestingPoint ${params.endVestingPoint}`); - console.log( - `- Using maxBuyingCap ${params.maxBuyingCap}. In lamports ${maxBuyingCap}`, - ); - console.log( - `- Using escrowFee ${params.escrowFee}. In lamports ${escrowFee}`, - ); - console.log( - `- Using whitelistMode ${params.whitelistMode}. In value ${whitelistMode}`, - ); +): Promise> { + const whitelistListCsv: Array = await parseCsv(csvFilepath); + + const whitelistList: Array = new Array(0); + for (const item of whitelistListCsv) { + whitelistList.push({ + address: new PublicKey(item.address), + maxAmount: getAmountInLamports(item.maxAmount, quoteDecimals), + }); + } - const tx = await AlphaVault.createCustomizableProrataVault( - connection, - { - quoteMint, - baseMint, - poolAddress, - poolType, - depositingPoint: new BN(params.depositingPoint), - startVestingPoint: new BN(params.startVestingPoint), - endVestingPoint: new BN(params.endVestingPoint), - maxBuyingCap, - escrowFee, - whitelistMode, - }, - wallet.publicKey, - { - cluster: "mainnet-beta", - }, - ); - return tx; + return whitelistList; } main(); diff --git a/src/libs/config.ts b/src/libs/config.ts index 73c8e3b..e887f49 100644 --- a/src/libs/config.ts +++ b/src/libs/config.ts @@ -136,6 +136,7 @@ const CONFIG_SCHEMA: JSONSchemaType = { "permissioned_with_authority", ], }, + whitelistFilepath: { type: "string", nullable: true }, }, required: [ "poolType", @@ -293,6 +294,7 @@ export interface FcfsAlphaVaultConfig { escrowFee: number; // whitelist mode: permissionless / permission_with_merkle_proof / permission_with_authority whitelistMode: WhitelistModeConfig; + whitelistFilepath?: string; } export interface ProrataAlphaVaultConfig { @@ -310,6 +312,7 @@ export interface ProrataAlphaVaultConfig { escrowFee: number; // whitelist mode: permissionless / permission_with_merkle_proof / permission_with_authority whitelistMode: WhitelistModeConfig; + whitelistFilepath?: string; } export interface LockLiquidityConfig { diff --git a/src/libs/constants.ts b/src/libs/constants.ts index 66e1d74..ce806f4 100644 --- a/src/libs/constants.ts +++ b/src/libs/constants.ts @@ -1,6 +1,12 @@ +import { PublicKey } from "@solana/web3.js"; + export const DEFAULT_COMMITMENT_LEVEL = "confirmed"; -export const SOL_TOKEN_MINT = "So11111111111111111111111111111111111111112"; -export const USDC_TOKEN_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; +export const SOL_TOKEN_MINT = new PublicKey( + "So11111111111111111111111111111111111111112", +); +export const USDC_TOKEN_MINT = new PublicKey( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", +); export const SOL_TOKEN_DECIMALS = 9; export const USDC_TOKEN_DECIMALS = 6; @@ -15,3 +21,9 @@ export const DYNAMIC_AMM_PROGRAM_IDS = { localhost: "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB", "mainnet-beta": "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB", }; + +export const ALPHA_VAULT_PROGRAM_IDS = { + devnet: "SNPmGgnywBvvrAKMLundzG6StojyHTHDLu7T4sdhP4k", + localhost: "SNPmGgnywBvvrAKMLundzG6StojyHTHDLu7T4sdhP4k", + "mainnet-beta": "vaU6kP7iNEGkbmPkLmZfGwiGxd4Mob24QQCie5R9kd2", +}; diff --git a/src/libs/create_alpha_vault_utils.ts b/src/libs/create_alpha_vault_utils.ts new file mode 100644 index 0000000..87559ba --- /dev/null +++ b/src/libs/create_alpha_vault_utils.ts @@ -0,0 +1,609 @@ +import AlphaVault, { + CustomizableFcfsVaultParams, + CustomizableProrataVaultParams, + IDL, + PoolType, + SEED, + VaultMode, + WalletDepositCap, +} from "@meteora-ag/alpha-vault"; +import { + ComputeBudgetProgram, + Connection, + Keypair, + PublicKey, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { ALPHA_VAULT_PROGRAM_IDS } from "./constants"; +import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor"; +import { + getAlphaVaultWhitelistMode, + getAmountInLamports, + runSimulateTransaction, +} from "./utils"; +import { + AlphaVaultTypeConfig, + FcfsAlphaVaultConfig, + ProrataAlphaVaultConfig, + WhitelistModeConfig, +} from "./config"; +import { BN } from "bn.js"; +import { createMerkleTree } from "./merkle_tree"; + +export async function createFcfsAlphaVault( + connection: Connection, + wallet: Wallet, + poolType: PoolType, + poolAddress: PublicKey, + baseMint: PublicKey, + quoteMint: PublicKey, + quoteDecimals: number, + params: FcfsAlphaVaultConfig, + dryRun: boolean, + computeUnitPriceMicroLamports: number, + opts?: { + alphaVaultProgramId: PublicKey; + }, +): Promise { + let maxDepositingCap = getAmountInLamports( + params.maxDepositCap, + quoteDecimals, + ); + let individualDepositingCap = getAmountInLamports( + params.individualDepositingCap, + quoteDecimals, + ); + let escrowFee = getAmountInLamports(params.escrowFee, quoteDecimals); + let whitelistMode = getAlphaVaultWhitelistMode(params.whitelistMode); + + console.log(`\n> Initializing FcfsAlphaVault...`); + console.log(`- Using poolType: ${poolType}`); + console.log(`- Using poolMint ${poolAddress}`); + console.log(`- Using baseMint ${baseMint}`); + console.log(`- Using quoteMint ${quoteMint}`); + console.log(`- Using depositingPoint ${params.depositingPoint}`); + console.log(`- Using startVestingPoint ${params.startVestingPoint}`); + console.log(`- Using endVestingPoint ${params.endVestingPoint}`); + console.log( + `- Using maxDepositingCap ${params.maxDepositCap}. In lamports ${maxDepositingCap}`, + ); + console.log( + `- Using individualDepositingCap ${params.individualDepositingCap}. In lamports ${individualDepositingCap}`, + ); + console.log( + `- Using escrowFee ${params.escrowFee}. In lamports ${escrowFee}`, + ); + console.log( + `- Using whitelistMode ${params.whitelistMode}. In value ${whitelistMode}`, + ); + + const initAlphaVaultTx = (await createCustomizableFcfsVault( + connection, + { + quoteMint, + baseMint, + poolAddress, + poolType, + depositingPoint: new BN(params.depositingPoint), + startVestingPoint: new BN(params.startVestingPoint), + endVestingPoint: new BN(params.endVestingPoint), + maxDepositingCap, + individualDepositingCap, + escrowFee, + whitelistMode, + }, + wallet.publicKey, + computeUnitPriceMicroLamports, + opts, + )) as Transaction; + + if (dryRun) { + console.log(`\n> Simulating init alpha vault tx...`); + await runSimulateTransaction(connection, [wallet.payer], wallet.publicKey, [ + initAlphaVaultTx, + ]); + } else { + console.log(`>> Sending init alpha vault transaction...`); + const initAlphaVaulTxHash = await sendAndConfirmTransaction( + connection, + initAlphaVaultTx, + [wallet.payer], + ).catch((err) => { + console.error(err); + throw err; + }); + console.log( + `>>> Alpha vault initialized successfully with tx hash: ${initAlphaVaulTxHash}`, + ); + } +} + +export async function createProrataAlphaVault( + connection: Connection, + wallet: Wallet, + poolType: PoolType, + poolAddress: PublicKey, + baseMint: PublicKey, + quoteMint: PublicKey, + quoteDecimals: number, + params: ProrataAlphaVaultConfig, + dryRun: boolean, + computeUnitPriceMicroLamports: number, + opts?: { + alphaVaultProgramId: PublicKey; + }, +) { + let maxBuyingCap = getAmountInLamports(params.maxBuyingCap, quoteDecimals); + let escrowFee = getAmountInLamports(params.escrowFee, quoteDecimals); + let whitelistMode = getAlphaVaultWhitelistMode(params.whitelistMode); + + console.log(`\n> Initializing ProrataAlphaVault...`); + console.log(`- Using poolType: ${poolType}`); + console.log(`- Using poolMint ${poolAddress}`); + console.log(`- Using baseMint ${baseMint}`); + console.log(`- Using quoteMint ${quoteMint}`); + console.log(`- Using depositingPoint ${params.depositingPoint}`); + console.log(`- Using startVestingPoint ${params.startVestingPoint}`); + console.log(`- Using endVestingPoint ${params.endVestingPoint}`); + console.log( + `- Using maxBuyingCap ${params.maxBuyingCap}. In lamports ${maxBuyingCap}`, + ); + console.log( + `- Using escrowFee ${params.escrowFee}. In lamports ${escrowFee}`, + ); + console.log( + `- Using whitelistMode ${params.whitelistMode}. In value ${whitelistMode}`, + ); + + const initAlphaVaultTx = (await createCustomizableProrataVault( + connection, + { + quoteMint, + baseMint, + poolAddress, + poolType, + depositingPoint: new BN(params.depositingPoint), + startVestingPoint: new BN(params.startVestingPoint), + endVestingPoint: new BN(params.endVestingPoint), + maxBuyingCap, + escrowFee, + whitelistMode, + }, + wallet.publicKey, + computeUnitPriceMicroLamports, + opts, + )) as Transaction; + + if (dryRun) { + console.log(`\n> Simulating init alpha vault tx...`); + await runSimulateTransaction(connection, [wallet.payer], wallet.publicKey, [ + initAlphaVaultTx, + ]); + } else { + console.log(`>> Sending init alpha vault transaction...`); + const initAlphaVaulTxHash = await sendAndConfirmTransaction( + connection, + initAlphaVaultTx, + [wallet.payer], + ).catch((err) => { + console.error(err); + throw err; + }); + console.log( + `>>> Alpha vault initialized successfully with tx hash: ${initAlphaVaulTxHash}`, + ); + } +} + +export async function createPermissionedAlphaVaultWithAuthority( + connection: Connection, + wallet: Wallet, + alphaVaultType: AlphaVaultTypeConfig, + poolType: PoolType, + poolAddress: PublicKey, + baseMint: PublicKey, + quoteMint: PublicKey, + quoteDecimals: number, + params: FcfsAlphaVaultConfig | ProrataAlphaVaultConfig, + whitelistList: WalletDepositCap[], + dryRun: boolean, + computeUnitPriceMicroLamports: number, + opts?: { + alphaVaultProgramId: PublicKey; + }, +): Promise { + if (params.whitelistMode != WhitelistModeConfig.PermissionedWithAuthority) { + throw new Error(`Invalid whitelist mode ${params.whitelistMode}. Only permissioned_with_authority is allowed + `); + } + + // 1. Create alpha vault + if (alphaVaultType == AlphaVaultTypeConfig.Fcfs) { + await createFcfsAlphaVault( + connection, + wallet, + poolType, + poolAddress, + baseMint, + quoteMint, + quoteDecimals, + params as FcfsAlphaVaultConfig, + dryRun, + computeUnitPriceMicroLamports, + opts, + ); + } else if (alphaVaultType == AlphaVaultTypeConfig.Prorata) { + await createProrataAlphaVault( + connection, + wallet, + poolType, + poolAddress, + baseMint, + quoteMint, + quoteDecimals, + params as ProrataAlphaVaultConfig, + dryRun, + computeUnitPriceMicroLamports, + opts, + ); + } + + // 2. Create StakeEscrow account for each whitelisted wallet + const alphaVaultProgramId = new PublicKey( + opts?.alphaVaultProgramId ?? ALPHA_VAULT_PROGRAM_IDS["mainnet-beta"], + ); + + const [alphaVaultPubkey] = deriveAlphaVault( + wallet.publicKey, + poolAddress, + alphaVaultProgramId, + ); + + console.log("Creating stake escrow accounts..."); + + const alphaVault = await createAlphaVaultInstance( + connection, + alphaVaultProgramId, + alphaVaultPubkey, + ); + + // Create StakeEscrow accounts for whitelist list + const instructions = + await alphaVault.createMultipleStakeEscrowByAuthorityInstructions( + whitelistList, + wallet.publicKey, + ); + + const { blockhash, lastValidBlockHeight } = + await connection.getLatestBlockhash("confirmed"); + const setPriorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: computeUnitPriceMicroLamports, + }); + + const createStakeEscrowAccountsTx = new Transaction({ + blockhash, + lastValidBlockHeight, + feePayer: wallet.publicKey, + }) + .add(...instructions) + .add(setPriorityFeeIx); + + if (dryRun) { + console.log(`\n> Simulating create stake escrow accounts tx...`); + await runSimulateTransaction(connection, [wallet.payer], wallet.publicKey, [ + createStakeEscrowAccountsTx, + ]); + } else { + console.log(`>> Sending create stake escrow accounts transaction...`); + const createStakeEscrowAccountTxHash = await sendAndConfirmTransaction( + connection, + createStakeEscrowAccountsTx, + [wallet.payer], + ).catch((err) => { + console.error(err); + throw err; + }); + console.log( + `>>> Create stake escrow accounts successfully with tx hash: ${createStakeEscrowAccountTxHash}`, + ); + } +} + +export async function createPermissionedAlphaVaultWithMerkleProof( + connection: Connection, + wallet: Wallet, + alphaVaultType: AlphaVaultTypeConfig, + poolType: PoolType, + poolAddress: PublicKey, + baseMint: PublicKey, + quoteMint: PublicKey, + quoteDecimals: number, + params: FcfsAlphaVaultConfig | ProrataAlphaVaultConfig, + whitelistList: WalletDepositCap[], + dryRun: boolean, + computeUnitPriceMicroLamports: number, + opts?: { + alphaVaultProgramId: PublicKey; + }, +): Promise { + if (params.whitelistMode != WhitelistModeConfig.PermissionedWithMerkleProof) { + throw new Error(`Invalid whitelist mode ${params.whitelistMode}. Only permissioned_with_merkle_proof is allowed + `); + } + + // 1. Create alpha vault + if (alphaVaultType == AlphaVaultTypeConfig.Fcfs) { + await createFcfsAlphaVault( + connection, + wallet, + poolType, + poolAddress, + baseMint, + quoteMint, + quoteDecimals, + params as FcfsAlphaVaultConfig, + dryRun, + computeUnitPriceMicroLamports, + opts, + ); + } else if (alphaVaultType == AlphaVaultTypeConfig.Prorata) { + await createProrataAlphaVault( + connection, + wallet, + poolType, + poolAddress, + baseMint, + quoteMint, + quoteDecimals, + params as ProrataAlphaVaultConfig, + dryRun, + computeUnitPriceMicroLamports, + opts, + ); + } + + const alphaVaultProgramId = new PublicKey( + opts?.alphaVaultProgramId ?? ALPHA_VAULT_PROGRAM_IDS["mainnet-beta"], + ); + + const [alphaVaultPubkey] = deriveAlphaVault( + wallet.publicKey, + poolAddress, + alphaVaultProgramId, + ); + + const alphaVault = await createAlphaVaultInstance( + connection, + alphaVaultProgramId, + alphaVaultPubkey, + ); + + // 2. Create merkle tree + const tree = await createMerkleTree(whitelistList); + + // 3. Create merkle root config + // If the tree grew too large, one can partition it into multiple tree by setting different version + const version = new BN(0); + const createMerkleRootConfigTx = await alphaVault.createMerkleRootConfig( + tree.getRoot(), + version, + wallet.publicKey, + ); + + // 4. Send transaction + if (dryRun) { + console.log(`\n> Simulating create merkle root config tx...`); + await runSimulateTransaction(connection, [wallet.payer], wallet.publicKey, [ + createMerkleRootConfigTx, + ]); + } else { + console.log(`>> Sending create merkle root config transaction...`); + const createStakeEscrowAccountTxHash = await sendAndConfirmTransaction( + connection, + createMerkleRootConfigTx, + [wallet.payer], + ).catch((err) => { + console.error(err); + throw err; + }); + console.log( + `>>> Create merkle root config successfully with tx hash: ${createStakeEscrowAccountTxHash}`, + ); + } +} + +async function createCustomizableFcfsVault( + connection: Connection, + vaultParam: CustomizableFcfsVaultParams, + owner: PublicKey, + computeUnitPriceMicroLamports: number, + opts?: { + alphaVaultProgramId: PublicKey; + }, +) { + const alphaVaultProgramId = + opts?.alphaVaultProgramId || + new PublicKey(ALPHA_VAULT_PROGRAM_IDS["mainnet-beta"]); + const { + poolAddress, + poolType, + baseMint, + quoteMint, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxDepositingCap, + individualDepositingCap, + escrowFee, + whitelistMode, + } = vaultParam; + + const [alphaVaultPubkey] = deriveAlphaVault( + owner, + poolAddress, + alphaVaultProgramId, + ); + + const provider = new AnchorProvider( + connection, + {} as any, + AnchorProvider.defaultOptions(), + ); + const program = new Program(IDL, alphaVaultProgramId, provider); + + const createTx = await program.methods + .initializeFcfsVault({ + poolType, + baseMint, + quoteMint, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxDepositingCap, + individualDepositingCap, + escrowFee, + whitelistMode, + }) + .accounts({ + base: owner, + vault: alphaVaultPubkey, + pool: poolAddress, + funder: owner, + program: alphaVaultProgramId, + systemProgram: SystemProgram.programId, + }) + .transaction(); + + const setPriorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: computeUnitPriceMicroLamports, + }); + createTx.add(setPriorityFeeIx); + + const { blockhash, lastValidBlockHeight } = + await connection.getLatestBlockhash("confirmed"); + return new Transaction({ + blockhash, + lastValidBlockHeight, + feePayer: owner, + }).add(createTx); +} + +async function createCustomizableProrataVault( + connection: Connection, + vaultParam: CustomizableProrataVaultParams, + owner: PublicKey, + computeUnitPriceMicroLamports: number, + opts?: { + alphaVaultProgramId: PublicKey; + }, +) { + const alphaVaultProgramId = + opts?.alphaVaultProgramId || + new PublicKey(ALPHA_VAULT_PROGRAM_IDS["mainnet-beta"]); + const { + poolAddress, + poolType, + baseMint, + quoteMint, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxBuyingCap, + escrowFee, + whitelistMode, + } = vaultParam; + + const [alphaVaultPubkey] = deriveAlphaVault( + owner, + poolAddress, + alphaVaultProgramId, + ); + + const provider = new AnchorProvider( + connection, + {} as any, + AnchorProvider.defaultOptions(), + ); + const program = new Program(IDL, alphaVaultProgramId, provider); + + const createTx = await program.methods + .initializeProrataVault({ + poolType, + baseMint, + quoteMint, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxBuyingCap, + escrowFee, + whitelistMode, + }) + .accounts({ + base: owner, + vault: alphaVaultPubkey, + pool: poolAddress, + funder: owner, + program: alphaVaultProgramId, + systemProgram: SystemProgram.programId, + }) + .transaction(); + const setPriorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: computeUnitPriceMicroLamports, + }); + createTx.add(setPriorityFeeIx); + + const { blockhash, lastValidBlockHeight } = + await connection.getLatestBlockhash("confirmed"); + return new Transaction({ + blockhash, + lastValidBlockHeight, + feePayer: owner, + }).add(createTx); +} + +// Derive alpha vault public key +export function deriveAlphaVault( + base: PublicKey, + lbPair: PublicKey, + alphaVaultProgramId: PublicKey, +) { + return PublicKey.findProgramAddressSync( + [Buffer.from(SEED.vault), base.toBuffer(), lbPair.toBuffer()], + alphaVaultProgramId, + ); +} + +export function deriveMerkleRootConfig( + alphaVault: PublicKey, + version: BN, + programId: PublicKey, +) { + return PublicKey.findProgramAddressSync( + [ + Buffer.from(SEED.merkleRoot), + alphaVault.toBuffer(), + new Uint8Array(version.toArrayLike(Buffer, "le", 8)), + ], + programId, + ); +} + +export async function createAlphaVaultInstance( + connection: Connection, + alphaVaultProgramId: PublicKey, + vaultAddress: PublicKey, +): Promise { + const provider = new AnchorProvider( + connection, + {} as any, + AnchorProvider.defaultOptions(), + ); + const program = new Program(IDL, alphaVaultProgramId, provider); + + const vault = await program.account.vault.fetch(vaultAddress); + const vaultMode = vault.vaultMode === 0 ? VaultMode.PRORATA : VaultMode.FCFS; + + return new AlphaVault(program, vaultAddress, vault, vaultMode); +} diff --git a/src/libs/merkle_tree/balance_tree.ts b/src/libs/merkle_tree/balance_tree.ts new file mode 100644 index 0000000..e461f93 --- /dev/null +++ b/src/libs/merkle_tree/balance_tree.ts @@ -0,0 +1,59 @@ +import { BN, web3 } from "@coral-xyz/anchor"; +import { sha256 } from "js-sha256"; + +import { MerkleTree } from "./merkle_tree"; + +export class BalanceTree { + private readonly _tree: MerkleTree; + constructor(balances: { account: web3.PublicKey; maxCap: BN }[]) { + this._tree = new MerkleTree( + balances.map(({ account, maxCap }, index) => { + return BalanceTree.toNode(account, maxCap); + }), + ); + } + + static verifyProof( + account: web3.PublicKey, + maxCap: BN, + proof: Buffer[], + root: Buffer, + ): boolean { + let pair = BalanceTree.toNode(account, maxCap); + for (const item of proof) { + pair = MerkleTree.combinedHash(pair, item); + } + + return pair.equals(root); + } + + // keccak256(abi.encode(index, account, amount)) + static toNode(account: web3.PublicKey, maxCap: BN): Buffer { + const buf = Buffer.concat([ + account.toBuffer(), + new BN(maxCap).toArrayLike(Buffer, "le", 8), + ]); + + const hashedBuff = Buffer.from(sha256(buf), "hex"); + const bufWithPrefix = Buffer.concat([Buffer.from([0]), hashedBuff]); + + return Buffer.from(sha256(bufWithPrefix), "hex"); + } + + getHexRoot(): string { + return this._tree.getHexRoot(); + } + + // returns the hex bytes32 values of the proof + getHexProof(account: web3.PublicKey, maxCap: BN): string[] { + return this._tree.getHexProof(BalanceTree.toNode(account, maxCap)); + } + + getRoot(): Buffer { + return this._tree.getRoot(); + } + + getProof(account: web3.PublicKey, maxCap: BN): Buffer[] { + return this._tree.getProof(BalanceTree.toNode(account, maxCap)); + } +} diff --git a/src/libs/merkle_tree/index.ts b/src/libs/merkle_tree/index.ts new file mode 100644 index 0000000..934d9b5 --- /dev/null +++ b/src/libs/merkle_tree/index.ts @@ -0,0 +1,20 @@ +import { WalletDepositCap } from "@meteora-ag/alpha-vault"; +import { BalanceTree } from "./balance_tree"; + +export * from "./balance_tree"; +export * from "./merkle_tree"; + +export const createMerkleTree = async ( + whitelistedWallets: WalletDepositCap[], +): Promise => { + const tree = new BalanceTree( + whitelistedWallets.map((info) => { + return { + account: info.address, + maxCap: info.maxAmount, + }; + }), + ); + + return tree; +}; diff --git a/src/libs/merkle_tree/merkle_tree.ts b/src/libs/merkle_tree/merkle_tree.ts new file mode 100644 index 0000000..a8233a1 --- /dev/null +++ b/src/libs/merkle_tree/merkle_tree.ts @@ -0,0 +1,141 @@ +import { sha256 } from "js-sha256"; +import invariant from "tiny-invariant"; + +function getPairElement(idx: number, layer: Buffer[]): Buffer | null { + const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; + + if (pairIdx < layer.length) { + const pairEl = layer[pairIdx]; + invariant(pairEl, "pairEl"); + return pairEl; + } else { + return null; + } +} + +function bufDedup(elements: Buffer[]): Buffer[] { + return elements.filter((el, idx) => { + return idx === 0 || !elements[idx - 1]?.equals(el); + }); +} + +function bufArrToHexArr(arr: Buffer[]): string[] { + if (arr.some((el) => !Buffer.isBuffer(el))) { + throw new Error("Array is not an array of buffers"); + } + + return arr.map((el) => "0x" + el.toString("hex")); +} + +function sortAndConcat(...args: Buffer[]): Buffer { + return Buffer.concat([ + Buffer.from([1]), + Buffer.concat([...args].sort(Buffer.compare.bind(null))), + ]); +} + +export class MerkleTree { + private readonly _elements: Buffer[]; + private readonly _bufferElementPositionIndex: { + [hexElement: string]: number; + }; + private readonly _layers: Buffer[][]; + + constructor(elements: Buffer[]) { + this._elements = [...elements]; + // Sort elements + this._elements.sort(Buffer.compare.bind(null)); + // Deduplicate elements + this._elements = bufDedup(this._elements); + + this._bufferElementPositionIndex = this._elements.reduce<{ + [hexElement: string]: number; + }>((memo, el, index) => { + memo[el.toString("hex")] = index; + return memo; + }, {}); + + // Create layers + this._layers = this.getLayers(this._elements); + } + + getLayers(elements: Buffer[]): Buffer[][] { + if (elements.length === 0) { + throw new Error("empty tree"); + } + + const layers = []; + layers.push(elements); + + // Get next layer until we reach the root + while ((layers[layers.length - 1]?.length ?? 0) > 1) { + const nextLayerIndex: Buffer[] | undefined = layers[layers.length - 1]; + invariant(nextLayerIndex, "nextLayerIndex"); + layers.push(this.getNextLayer(nextLayerIndex)); + } + + return layers; + } + + getNextLayer(elements: Buffer[]): Buffer[] { + return elements.reduce((layer, el, idx, arr) => { + if (idx % 2 === 0) { + // Hash the current element with its pair element + const pairEl = arr[idx + 1]; + layer.push(MerkleTree.combinedHash(el, pairEl)); + } + + return layer; + }, []); + } + + static combinedHash(first: Buffer, second: Buffer | undefined): Buffer { + if (!first) { + invariant(second, "second element of pair must exist"); + return second; + } + if (!second) { + invariant(first, "first element of pair must exist"); + return first; + } + + return Buffer.from(sha256(sortAndConcat(first, second)), "hex"); + } + + getRoot(): Buffer { + const root = this._layers[this._layers.length - 1]?.[0]; + invariant(root, "root"); + return root; + } + + getHexRoot(): string { + return this.getRoot().toString("hex"); + } + + getProof(el: Buffer): Buffer[] { + const initialIdx = this._bufferElementPositionIndex[el.toString("hex")]; + + if (typeof initialIdx !== "number") { + throw new Error("Element does not exist in Merkle tree"); + } + + let idx = initialIdx; + return this._layers.reduce((proof, layer) => { + const pairElement = getPairElement(idx, layer); + + if (pairElement) { + proof.push(pairElement); + } + + idx = Math.floor(idx / 2); + + return proof; + }, []); + } + + getHexProof(el: Buffer): string[] { + const proof = this.getProof(el); + + return bufArrToHexArr(proof); + } +} diff --git a/src/libs/utils.ts b/src/libs/utils.ts index eba3155..fead818 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -34,6 +34,7 @@ import { PriceRoundingConfig, WhitelistModeConfig, } from ".."; +import { parse } from "csv-parse"; export const DEFAULT_ADD_LIQUIDITY_CU = 800_000; @@ -101,6 +102,26 @@ export function parseKeypairFromSecretKey(secretKey: string): Keypair { return keypair; } +// Function to parse CSV file +export async function parseCsv(filePath: string): Promise> { + const fileStream = fs.createReadStream(filePath); + + return new Promise((resolve, reject) => { + const parser = parse({ + columns: true, // Use the header row as keys + skip_empty_lines: true, // Skip empty lines + }); + + const results = []; + + fileStream + .pipe(parser) + .on("data", (row) => results.push(row)) // Collect rows + .on("end", () => resolve(results)) // Resolve the promise with results + .on("error", (err) => reject(err)); // Reject the promise if error occurs + }); +} + export function getAmountInLamports( amount: number | string, decimals: number, @@ -219,6 +240,17 @@ export function getAlphaVaultWhitelistMode( } } +export function toAlphaVaulSdkPoolType(poolType: PoolTypeConfig): PoolType { + switch (poolType) { + case PoolTypeConfig.Dynamic: + return PoolType.DYNAMIC; + case PoolTypeConfig.Dlmm: + return PoolType.DLMM; + default: + throw new Error(`Unsupported alpha vault pool type: ${poolType}`); + } +} + /** * Modify priority fee in transaction * @param tx diff --git a/src/tests/artifacts/alpha_vault.so b/src/tests/artifacts/alpha_vault.so new file mode 100755 index 0000000..fef2324 Binary files /dev/null and b/src/tests/artifacts/alpha_vault.so differ diff --git a/src/tests/create_dynamic_pool_with_permissioned_with_authority_alpha_vault.test.ts b/src/tests/create_dynamic_pool_with_permissioned_with_authority_alpha_vault.test.ts new file mode 100644 index 0000000..8254b9a --- /dev/null +++ b/src/tests/create_dynamic_pool_with_permissioned_with_authority_alpha_vault.test.ts @@ -0,0 +1,314 @@ +import { Keypair, PublicKey, sendAndConfirmTransaction } from "@solana/web3.js"; +import { SOL_TOKEN_MINT } from "../libs/constants"; +import { + createPermissionlessDynamicPool, + toAlphaVaulSdkPoolType, +} from "../index"; +import { web3 } from "@coral-xyz/anchor"; +import { + ActivationTypeConfig, + AlphaVaultTypeConfig, + MeteoraConfig, + PoolTypeConfig, + WhitelistModeConfig, +} from "../libs/config"; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + createMint, + getOrCreateAssociatedTokenAccount, + mintTo, +} from "@solana/spl-token"; +import { + connection, + payerKeypair, + rpcUrl, + keypairFilePath, + payerWallet, + DYNAMIC_AMM_PROGRAM_ID, + ALPHA_VAULT_PROGRAM_ID, +} from "./setup"; +import { deriveCustomizablePermissionlessConstantProductPoolAddress } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils"; +import { + createAlphaVaultInstance, + createPermissionedAlphaVaultWithAuthority, + deriveAlphaVault, +} from "../libs/create_alpha_vault_utils"; +import { BN } from "bn.js"; +import { PermissionWithAuthority } from "@meteora-ag/alpha-vault"; + +describe("Test create dynamic pool with permissioned authority fcfs alpha vault", () => { + const WEN_DECIMALS = 5; + const USDC_DECIMALS = 6; + const WEN_SUPPLY = 100_000_000; + const USDC_SUPPLY = 100_000_000; + const dryRun = false; + const computeUnitPriceMicroLamports = 100000; + + let WEN: PublicKey; + let USDC: PublicKey; + let userWEN: web3.PublicKey; + let userUSDC: web3.PublicKey; + + beforeAll(async () => { + WEN = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + WEN_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + USDC = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + USDC_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + const userWenInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + WEN, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userWEN = userWenInfo.address; + + const userUsdcInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + USDC, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userUSDC = userUsdcInfo.address; + + await mintTo( + connection, + payerKeypair, + WEN, + userWEN, + payerKeypair.publicKey, + WEN_SUPPLY * 10 ** WEN_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + + await mintTo( + connection, + payerKeypair, + USDC, + userUSDC, + payerKeypair.publicKey, + USDC_SUPPLY * 10 ** USDC_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + }); + + it("Happy case", async () => { + const currentSlot = await connection.getSlot({ + commitment: "confirmed", + }); + const activationPoint = currentSlot + 30; + const depositingPoint = currentSlot; + const startVestingPoint = currentSlot + 50; + const endVestingPoint = currentSlot + 60; + + // 1. Create pool + const config: MeteoraConfig = { + dryRun: false, + rpcUrl, + keypairFilePath, + computeUnitPriceMicroLamports: 100000, + createBaseToken: null, + baseMint: WEN.toString(), + quoteSymbol: "SOL", + dynamicAmm: { + baseAmount: 1000, + quoteAmount: 1, + tradeFeeNumerator: 2500, + activationType: ActivationTypeConfig.Slot, + activationPoint, + hasAlphaVault: true, + }, + dlmm: null, + alphaVault: { + poolType: PoolTypeConfig.Dynamic, + alphaVaultType: AlphaVaultTypeConfig.Fcfs, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxDepositCap: 5, + individualDepositingCap: 0.01, + escrowFee: 0, + whitelistMode: WhitelistModeConfig.PermissionedWithAuthority, + }, + lockLiquidity: null, + lfgSeedLiquidity: null, + singleBinSeedLiquidity: null, + }; + + // 1. Create pool + await createPermissionlessDynamicPool( + config, + connection, + payerWallet, + WEN, + SOL_TOKEN_MINT, + { + programId: DYNAMIC_AMM_PROGRAM_ID, + }, + ); + + const alphaVaultType = config.alphaVault.alphaVaultType; + const poolType = toAlphaVaulSdkPoolType(config.alphaVault.poolType); + const poolAddress = + deriveCustomizablePermissionlessConstantProductPoolAddress( + WEN, + SOL_TOKEN_MINT, + DYNAMIC_AMM_PROGRAM_ID, + ); + const alphaVaultConfig = config.alphaVault; + + // Generate whitelist wallets + const whitelistWallet_1 = Keypair.generate(); + const whitelistWallet_2 = Keypair.generate(); + await connection.requestAirdrop(whitelistWallet_1.publicKey, 10 * 10 ** 9); + await connection.requestAirdrop(whitelistWallet_2.publicKey, 10 * 10 ** 9); + + const whitelistWallet_1_maxAmount = new BN(1 * 10 ** 9); + const whitelistWallet_2_maxAmount = new BN(5 * 10 ** 9); + + const whitelistList = [ + { + address: whitelistWallet_1.publicKey, + maxAmount: whitelistWallet_1_maxAmount, + }, + { + address: whitelistWallet_2.publicKey, + maxAmount: whitelistWallet_2_maxAmount, + }, + ]; + + // 2. Create permissioned alpha vault + await createPermissionedAlphaVaultWithAuthority( + connection, + payerWallet, + alphaVaultType, + poolType, + poolAddress, + WEN, + SOL_TOKEN_MINT, + 9, + alphaVaultConfig, + whitelistList, + dryRun, + computeUnitPriceMicroLamports, + { + alphaVaultProgramId: ALPHA_VAULT_PROGRAM_ID, + }, + ); + + const [alphaVaultPubkey] = deriveAlphaVault( + payerWallet.publicKey, + poolAddress, + ALPHA_VAULT_PROGRAM_ID, + ); + + const alphaVault = await createAlphaVaultInstance( + connection, + ALPHA_VAULT_PROGRAM_ID, + alphaVaultPubkey, + ); + + expect(alphaVault.vault.whitelistMode).toEqual(PermissionWithAuthority); + + { + const depositAmount = new BN(5 * 10 ** 8); + const depositTx = await alphaVault.deposit( + depositAmount, + whitelistWallet_1.publicKey, + ); + + const depositTxHash = await sendAndConfirmTransaction( + connection, + depositTx, + [whitelistWallet_1], + ).catch((e) => { + console.error(e); + throw e; + }); + + const whitelistWalletEscrow_1 = await alphaVault.getEscrow( + whitelistWallet_1.publicKey, + ); + expect(whitelistWalletEscrow_1.totalDeposit.toString()).toEqual( + depositAmount.toString(), + ); + } + + { + const depositAmount = new BN(5 * 10 ** 8); + const depositTx = await alphaVault.deposit( + depositAmount, + whitelistWallet_1.publicKey, + ); + + const depositTxHash = await sendAndConfirmTransaction( + connection, + depositTx, + [whitelistWallet_1], + ).catch((e) => { + console.error(e); + throw e; + }); + + const whitelistWalletEscrow_1 = await alphaVault.getEscrow( + whitelistWallet_1.publicKey, + ); + expect(whitelistWalletEscrow_1.totalDeposit.toString()).toEqual( + (depositAmount * 2).toString(), + ); + } + + // deposit exceed cap + { + const depositAmount = new BN(1 * 10 ** 8); + const depositTx = await alphaVault.deposit( + depositAmount, + whitelistWallet_1.publicKey, + ); + + await expect( + sendAndConfirmTransaction(connection, depositTx, [whitelistWallet_1]), + ).rejects.toThrow(); + } + }); +}); diff --git a/src/tests/create_dynamic_pool_with_permissioned_with_merkle_proof.test.ts b/src/tests/create_dynamic_pool_with_permissioned_with_merkle_proof.test.ts new file mode 100644 index 0000000..8502e0a --- /dev/null +++ b/src/tests/create_dynamic_pool_with_permissioned_with_merkle_proof.test.ts @@ -0,0 +1,341 @@ +import { Keypair, PublicKey, sendAndConfirmTransaction } from "@solana/web3.js"; +import { SOL_TOKEN_MINT } from "../libs/constants"; +import { + createPermissionlessDynamicPool, + toAlphaVaulSdkPoolType, +} from "../index"; +import { web3 } from "@coral-xyz/anchor"; +import { + ActivationTypeConfig, + AlphaVaultTypeConfig, + MeteoraConfig, + PoolTypeConfig, + WhitelistModeConfig, +} from "../libs/config"; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + createMint, + getOrCreateAssociatedTokenAccount, + mintTo, +} from "@solana/spl-token"; +import { + connection, + payerKeypair, + rpcUrl, + keypairFilePath, + payerWallet, + DYNAMIC_AMM_PROGRAM_ID, + ALPHA_VAULT_PROGRAM_ID, +} from "./setup"; +import { deriveCustomizablePermissionlessConstantProductPoolAddress } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils"; +import { + createAlphaVaultInstance, + createPermissionedAlphaVaultWithMerkleProof, + deriveAlphaVault, + deriveMerkleRootConfig, +} from "../libs/create_alpha_vault_utils"; +import { BN } from "bn.js"; +import { PermissionWithMerkleProof } from "@meteora-ag/alpha-vault"; +import { createMerkleTree } from "../libs/merkle_tree"; + +describe("Test create dynamic pool with permissioned with merkle proof fcfs alpha vault", () => { + const WEN_DECIMALS = 5; + const USDC_DECIMALS = 6; + const WEN_SUPPLY = 100_000_000; + const USDC_SUPPLY = 100_000_000; + const dryRun = false; + const computeUnitPriceMicroLamports = 100000; + + let WEN: PublicKey; + let USDC: PublicKey; + let userWEN: web3.PublicKey; + let userUSDC: web3.PublicKey; + + beforeAll(async () => { + WEN = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + WEN_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + USDC = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + USDC_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + const userWenInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + WEN, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userWEN = userWenInfo.address; + + const userUsdcInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + USDC, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userUSDC = userUsdcInfo.address; + + await mintTo( + connection, + payerKeypair, + WEN, + userWEN, + payerKeypair.publicKey, + WEN_SUPPLY * 10 ** WEN_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + + await mintTo( + connection, + payerKeypair, + USDC, + userUSDC, + payerKeypair.publicKey, + USDC_SUPPLY * 10 ** USDC_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + }); + + it("Happy case", async () => { + const currentSlot = await connection.getSlot({ + commitment: "confirmed", + }); + const activationPoint = currentSlot + 30; + const depositingPoint = currentSlot; + const startVestingPoint = currentSlot + 50; + const endVestingPoint = currentSlot + 60; + + // 1. Create pool + const config: MeteoraConfig = { + dryRun: false, + rpcUrl, + keypairFilePath, + computeUnitPriceMicroLamports: 100000, + createBaseToken: null, + baseMint: WEN.toString(), + quoteSymbol: "SOL", + dynamicAmm: { + baseAmount: 1000, + quoteAmount: 1, + tradeFeeNumerator: 2500, + activationType: ActivationTypeConfig.Slot, + activationPoint, + hasAlphaVault: true, + }, + dlmm: null, + alphaVault: { + poolType: PoolTypeConfig.Dynamic, + alphaVaultType: AlphaVaultTypeConfig.Fcfs, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxDepositCap: 5, + individualDepositingCap: 0.01, + escrowFee: 0, + whitelistMode: WhitelistModeConfig.PermissionedWithMerkleProof, + }, + lockLiquidity: null, + lfgSeedLiquidity: null, + singleBinSeedLiquidity: null, + }; + + // 1. Create pool + await createPermissionlessDynamicPool( + config, + connection, + payerWallet, + WEN, + SOL_TOKEN_MINT, + { + programId: DYNAMIC_AMM_PROGRAM_ID, + }, + ); + + const alphaVaultType = config.alphaVault.alphaVaultType; + const poolType = toAlphaVaulSdkPoolType(config.alphaVault.poolType); + const poolAddress = + deriveCustomizablePermissionlessConstantProductPoolAddress( + WEN, + SOL_TOKEN_MINT, + DYNAMIC_AMM_PROGRAM_ID, + ); + const alphaVaultConfig = config.alphaVault; + + // Generate whitelist wallets + const whitelistWallet_1 = Keypair.generate(); + const whitelistWallet_2 = Keypair.generate(); + await connection.requestAirdrop(whitelistWallet_1.publicKey, 10 * 10 ** 9); + await connection.requestAirdrop(whitelistWallet_2.publicKey, 10 * 10 ** 9); + + const whitelistWallet_1_maxAmount = new BN(1 * 10 ** 9); + const whitelistWallet_2_maxAmount = new BN(5 * 10 ** 9); + + const whitelistList = [ + { + address: whitelistWallet_1.publicKey, + maxAmount: whitelistWallet_1_maxAmount, + }, + { + address: whitelistWallet_2.publicKey, + maxAmount: whitelistWallet_2_maxAmount, + }, + ]; + + // 2. Create permissioned alpha vault + await createPermissionedAlphaVaultWithMerkleProof( + connection, + payerWallet, + alphaVaultType, + poolType, + poolAddress, + WEN, + SOL_TOKEN_MINT, + 9, + alphaVaultConfig, + whitelistList, + dryRun, + computeUnitPriceMicroLamports, + { + alphaVaultProgramId: ALPHA_VAULT_PROGRAM_ID, + }, + ); + + const [alphaVaultPubkey] = deriveAlphaVault( + payerWallet.publicKey, + poolAddress, + ALPHA_VAULT_PROGRAM_ID, + ); + + const alphaVault = await createAlphaVaultInstance( + connection, + ALPHA_VAULT_PROGRAM_ID, + alphaVaultPubkey, + ); + + expect(alphaVault.vault.whitelistMode).toEqual(PermissionWithMerkleProof); + + // 3. Create merkle proof + const tree = await createMerkleTree(whitelistList); + + const [merkleRootConfig] = deriveMerkleRootConfig( + alphaVault.pubkey, + new BN(0), + ALPHA_VAULT_PROGRAM_ID, + ); + + const proof = tree + .getProof(whitelistWallet_1.publicKey, whitelistWallet_1_maxAmount) + .map((buffer) => { + return Array.from(new Uint8Array(buffer)); + }); + + const depositProof = { + merkleRootConfig, + maxCap: whitelistWallet_1_maxAmount, + proof, + }; + + // 4. Deposit + { + const depositAmount = new BN(5 * 10 ** 8); + const depositTx = await alphaVault.deposit( + depositAmount, + whitelistWallet_1.publicKey, + depositProof, + ); + + const depositTxHash = await sendAndConfirmTransaction( + connection, + depositTx, + [whitelistWallet_1], + ).catch((e) => { + console.error(e); + throw e; + }); + + const whitelistWalletEscrow_1 = await alphaVault.getEscrow( + whitelistWallet_1.publicKey, + ); + expect(whitelistWalletEscrow_1.totalDeposit.toString()).toEqual( + depositAmount.toString(), + ); + } + + { + const depositAmount = new BN(5 * 10 ** 8); + const depositTx = await alphaVault.deposit( + depositAmount, + whitelistWallet_1.publicKey, + depositProof, + ); + + const depositTxHash = await sendAndConfirmTransaction( + connection, + depositTx, + [whitelistWallet_1], + ).catch((e) => { + console.error(e); + throw e; + }); + + const whitelistWalletEscrow_1 = await alphaVault.getEscrow( + whitelistWallet_1.publicKey, + ); + expect(whitelistWalletEscrow_1.totalDeposit.toString()).toEqual( + (depositAmount * 2).toString(), + ); + } + + // deposit exceed cap + { + const depositAmount = new BN(1 * 10 ** 8); + const depositTx = await alphaVault.deposit( + depositAmount, + whitelistWallet_1.publicKey, + depositProof, + ); + + await expect( + sendAndConfirmTransaction(connection, depositTx, [whitelistWallet_1]), + ).rejects.toThrow(); + } + }); +}); diff --git a/src/tests/create_dynamic_pool_with_permissionless_alpha_vault.test.ts b/src/tests/create_dynamic_pool_with_permissionless_alpha_vault.test.ts new file mode 100644 index 0000000..7d952f0 --- /dev/null +++ b/src/tests/create_dynamic_pool_with_permissionless_alpha_vault.test.ts @@ -0,0 +1,425 @@ +import { Keypair, PublicKey } from "@solana/web3.js"; +import { SOL_TOKEN_MINT } from "../libs/constants"; +import { createPermissionlessDynamicPool } from "../index"; +import { web3 } from "@coral-xyz/anchor"; +import { + ActivationTypeConfig, + AlphaVaultTypeConfig, + FcfsAlphaVaultConfig, + MeteoraConfig, + PoolTypeConfig, + ProrataAlphaVaultConfig, + WhitelistModeConfig, +} from "../libs/config"; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + createMint, + getOrCreateAssociatedTokenAccount, + mintTo, +} from "@solana/spl-token"; +import { + connection, + payerKeypair, + rpcUrl, + keypairFilePath, + payerWallet, + DLMM_PROGRAM_ID, + DYNAMIC_AMM_PROGRAM_ID, + ALPHA_VAULT_PROGRAM_ID, +} from "./setup"; +import { deriveCustomizablePermissionlessConstantProductPoolAddress } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils"; +import { + createFcfsAlphaVault, + createProrataAlphaVault, + deriveAlphaVault, +} from "../libs/create_alpha_vault_utils"; +import AlphaVault, { + Permissionless, + PoolType, + VaultMode, +} from "@meteora-ag/alpha-vault"; +import { Clock, ClockLayout } from "@meteora-ag/dlmm"; +import { BN } from "bn.js"; +import AmmImpl from "@mercurial-finance/dynamic-amm-sdk"; + +describe("Test create permissonless dynamic pool with fcfs alpha vault", () => { + const WEN_DECIMALS = 5; + const USDC_DECIMALS = 6; + const WEN_SUPPLY = 100_000_000; + const USDC_SUPPLY = 100_000_000; + const dryRun = false; + const computeUnitPriceMicroLamports = 100000; + + let WEN: PublicKey; + let USDC: PublicKey; + let userWEN: web3.PublicKey; + let userUSDC: web3.PublicKey; + + beforeAll(async () => { + WEN = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + WEN_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + USDC = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + USDC_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + const userWenInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + WEN, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userWEN = userWenInfo.address; + + const userUsdcInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + USDC, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userUSDC = userUsdcInfo.address; + + await mintTo( + connection, + payerKeypair, + WEN, + userWEN, + payerKeypair.publicKey, + WEN_SUPPLY * 10 ** WEN_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + + await mintTo( + connection, + payerKeypair, + USDC, + userUSDC, + payerKeypair.publicKey, + USDC_SUPPLY * 10 ** USDC_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + }); + + it("Test create permissonless dynamic pool with fcfs alpha vault", async () => { + const currentSlot = await connection.getSlot({ + commitment: "confirmed", + }); + const activationPoint = currentSlot + 30; + const depositingPoint = currentSlot; + const startVestingPoint = currentSlot + 50; + const endVestingPoint = currentSlot + 60; + + // 1. Create pool + const config: MeteoraConfig = { + dryRun: false, + rpcUrl, + keypairFilePath, + computeUnitPriceMicroLamports: 100000, + createBaseToken: null, + baseMint: WEN.toString(), + quoteSymbol: "SOL", + dynamicAmm: { + baseAmount: 1000, + quoteAmount: 1, + tradeFeeNumerator: 2500, + activationType: ActivationTypeConfig.Slot, + activationPoint: activationPoint, + hasAlphaVault: true, + }, + dlmm: null, + alphaVault: { + poolType: PoolTypeConfig.Dynamic, + alphaVaultType: AlphaVaultTypeConfig.Fcfs, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxDepositCap: 0.5, + individualDepositingCap: 0.01, + escrowFee: 0, + whitelistMode: WhitelistModeConfig.Permissionless, + }, + lockLiquidity: null, + lfgSeedLiquidity: null, + singleBinSeedLiquidity: null, + }; + + await createPermissionlessDynamicPool( + config, + connection, + payerWallet, + WEN, + SOL_TOKEN_MINT, + { + programId: DYNAMIC_AMM_PROGRAM_ID, + }, + ); + + const poolKey = deriveCustomizablePermissionlessConstantProductPoolAddress( + WEN, + SOL_TOKEN_MINT, + DYNAMIC_AMM_PROGRAM_ID, + ); + + const pool = await AmmImpl.create(connection, poolKey, { + programId: DYNAMIC_AMM_PROGRAM_ID.toString(), + }); + + // 2. Create alpha vault + await createFcfsAlphaVault( + connection, + payerWallet, + PoolType.DYNAMIC, + poolKey, + WEN, + SOL_TOKEN_MINT, + 9, + config.alphaVault as FcfsAlphaVaultConfig, + dryRun, + computeUnitPriceMicroLamports, + { + alphaVaultProgramId: ALPHA_VAULT_PROGRAM_ID, + }, + ); + + const [alphaVaultPubkey] = deriveAlphaVault( + payerKeypair.publicKey, + poolKey, + ALPHA_VAULT_PROGRAM_ID, + ); + + const alphaVault = await AlphaVault.create(connection, alphaVaultPubkey); + expect(alphaVault.vault.baseMint).toEqual(WEN); + expect(alphaVault.vault.quoteMint).toEqual(SOL_TOKEN_MINT); + expect(alphaVault.vault.poolType).toEqual(PoolType.DYNAMIC); + expect(alphaVault.vault.vaultMode).toEqual(VaultMode.FCFS); + expect(alphaVault.vault.whitelistMode).toEqual(Permissionless); + }); +}); + +describe("Test create permissonless dynamic pool with prorata alpha vault", () => { + const WEN_DECIMALS = 5; + const USDC_DECIMALS = 6; + const WEN_SUPPLY = 100_000_000; + const USDC_SUPPLY = 100_000_000; + const dryRun = false; + const computeUnitPriceMicroLamports = 100000; + + let WEN: PublicKey; + let USDC: PublicKey; + let userWEN: web3.PublicKey; + let userUSDC: web3.PublicKey; + + beforeAll(async () => { + WEN = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + WEN_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + USDC = await createMint( + connection, + payerKeypair, + payerKeypair.publicKey, + null, + USDC_DECIMALS, + Keypair.generate(), + undefined, + TOKEN_PROGRAM_ID, + ); + + const userWenInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + WEN, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userWEN = userWenInfo.address; + + const userUsdcInfo = await getOrCreateAssociatedTokenAccount( + connection, + payerKeypair, + USDC, + payerKeypair.publicKey, + false, + "confirmed", + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID, + ); + userUSDC = userUsdcInfo.address; + + await mintTo( + connection, + payerKeypair, + WEN, + userWEN, + payerKeypair.publicKey, + WEN_SUPPLY * 10 ** WEN_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + + await mintTo( + connection, + payerKeypair, + USDC, + userUSDC, + payerKeypair.publicKey, + USDC_SUPPLY * 10 ** USDC_DECIMALS, + [], + { + commitment: "confirmed", + }, + TOKEN_PROGRAM_ID, + ); + }); + + it("Test create permissonless dynamic pool with prorata alpha vault", async () => { + const currentSlot = await connection.getSlot({ + commitment: "confirmed", + }); + const activationPoint = currentSlot + 30; + const depositingPoint = currentSlot; + const startVestingPoint = currentSlot + 40; + const endVestingPoint = currentSlot + 60; + + // 1. Create pool + const config: MeteoraConfig = { + dryRun: false, + rpcUrl, + keypairFilePath, + computeUnitPriceMicroLamports: 100000, + createBaseToken: null, + baseMint: WEN.toString(), + quoteSymbol: "SOL", + dynamicAmm: { + baseAmount: 1000, + quoteAmount: 1, + tradeFeeNumerator: 2500, + activationType: ActivationTypeConfig.Slot, + activationPoint: activationPoint, + hasAlphaVault: true, + }, + dlmm: null, + alphaVault: { + poolType: PoolTypeConfig.Dynamic, + alphaVaultType: AlphaVaultTypeConfig.Prorata, + depositingPoint, + startVestingPoint, + endVestingPoint, + maxBuyingCap: 10, + escrowFee: 0, + whitelistMode: WhitelistModeConfig.Permissionless, + }, + lockLiquidity: null, + lfgSeedLiquidity: null, + singleBinSeedLiquidity: null, + }; + + await createPermissionlessDynamicPool( + config, + connection, + payerWallet, + WEN, + SOL_TOKEN_MINT, + { + programId: DYNAMIC_AMM_PROGRAM_ID, + }, + ); + + const poolKey = deriveCustomizablePermissionlessConstantProductPoolAddress( + WEN, + SOL_TOKEN_MINT, + DYNAMIC_AMM_PROGRAM_ID, + ); + + const pool = await AmmImpl.create(connection, poolKey, { + programId: DYNAMIC_AMM_PROGRAM_ID.toString(), + }); + + // 2. Create alpha vault + await createProrataAlphaVault( + connection, + payerWallet, + PoolType.DYNAMIC, + poolKey, + WEN, + SOL_TOKEN_MINT, + 9, + config.alphaVault as ProrataAlphaVaultConfig, + dryRun, + computeUnitPriceMicroLamports, + { + alphaVaultProgramId: ALPHA_VAULT_PROGRAM_ID, + }, + ); + + const [alphaVaultPubkey] = deriveAlphaVault( + payerKeypair.publicKey, + poolKey, + ALPHA_VAULT_PROGRAM_ID, + ); + + const alphaVault = await AlphaVault.create(connection, alphaVaultPubkey); + expect(alphaVault.vault.baseMint).toEqual(WEN); + expect(alphaVault.vault.quoteMint).toEqual(SOL_TOKEN_MINT); + expect(alphaVault.vault.poolType).toEqual(PoolType.DYNAMIC); + expect(alphaVault.vault.vaultMode).toEqual(VaultMode.PRORATA); + expect(alphaVault.vault.whitelistMode).toEqual(Permissionless); + }); +}); diff --git a/src/tests/create_pool.test.ts b/src/tests/create_pool.test.ts index 8eec75f..f3b238c 100644 --- a/src/tests/create_pool.test.ts +++ b/src/tests/create_pool.test.ts @@ -10,7 +10,11 @@ import { createPermissionlessDynamicPool, } from "../index"; import { Wallet, web3 } from "@coral-xyz/anchor"; -import { MeteoraConfig } from "../libs/config"; +import { + ActivationTypeConfig, + MeteoraConfig, + PriceRoundingConfig, +} from "../libs/config"; import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, @@ -139,7 +143,7 @@ describe("Test Create Pool", () => { baseAmount: 1000, quoteAmount: 0.1, tradeFeeNumerator: 2500, - activationType: "timestamp", + activationType: ActivationTypeConfig.Timestamp, activationPoint: null, hasAlphaVault: false, }, @@ -174,9 +178,9 @@ describe("Test Create Pool", () => { binStep: 200, feeBps: 200, initialPrice: 0.5, - activationType: "timestamp", + activationType: ActivationTypeConfig.Timestamp, activationPoint: null, - priceRounding: "up", + priceRounding: PriceRoundingConfig.Up, hasAlphaVault: false, }, dynamicAmm: null, diff --git a/src/tests/seed_liquidity_lfg.test.ts b/src/tests/seed_liquidity_lfg.test.ts index 03df345..ca7ec4e 100644 --- a/src/tests/seed_liquidity_lfg.test.ts +++ b/src/tests/seed_liquidity_lfg.test.ts @@ -8,7 +8,11 @@ import { seedLiquiditySingleBin, } from "../index"; import { BN, Wallet, web3 } from "@coral-xyz/anchor"; -import { MeteoraConfig } from "../libs/config"; +import { + ActivationTypeConfig, + MeteoraConfig, + PriceRoundingConfig, +} from "../libs/config"; import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, @@ -151,9 +155,9 @@ describe("Test Seed Liquidity Single Bin", () => { binStep, feeBps, initialPrice, - activationType: "slot", + activationType: ActivationTypeConfig.Slot, activationPoint, - priceRounding: "up", + priceRounding: PriceRoundingConfig.Up, hasAlphaVault: false, }, dynamicAmm: null, diff --git a/src/tests/seed_liquidity_single_bin.test.ts b/src/tests/seed_liquidity_single_bin.test.ts index 73b360b..bffbf88 100644 --- a/src/tests/seed_liquidity_single_bin.test.ts +++ b/src/tests/seed_liquidity_single_bin.test.ts @@ -7,7 +7,11 @@ import { seedLiquiditySingleBin, } from "../index"; import { BN, Wallet, web3 } from "@coral-xyz/anchor"; -import { MeteoraConfig } from "../libs/config"; +import { + ActivationTypeConfig, + MeteoraConfig, + PriceRoundingConfig, +} from "../libs/config"; import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, @@ -145,9 +149,9 @@ describe("Test Seed Liquidity Single Bin", () => { binStep, feeBps, initialPrice, - activationType: "slot", + activationType: ActivationTypeConfig.Slot, activationPoint, - priceRounding: "up", + priceRounding: PriceRoundingConfig.Up, hasAlphaVault: false, }, dynamicAmm: null, diff --git a/src/tests/setup.ts b/src/tests/setup.ts new file mode 100644 index 0000000..4d40dd2 --- /dev/null +++ b/src/tests/setup.ts @@ -0,0 +1,25 @@ +import { Wallet } from "@coral-xyz/anchor"; +import { Connection, Keypair, PublicKey } from "@solana/web3.js"; +import { + ALPHA_VAULT_PROGRAM_IDS, + DLMM_PROGRAM_IDS, + DYNAMIC_AMM_PROGRAM_IDS, +} from "../libs/constants"; +import fs from "fs"; + +export const keypairFilePath = + "./src/tests/keys/localnet/admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json"; +export const keypairBuffer = fs.readFileSync(keypairFilePath, "utf-8"); +export const rpcUrl = "http://127.0.0.1:8899"; +export const connection = new Connection("http://127.0.0.1:8899", "confirmed"); +export const payerKeypair = Keypair.fromSecretKey( + new Uint8Array(JSON.parse(keypairBuffer)), +); +export const payerWallet = new Wallet(payerKeypair); +export const DLMM_PROGRAM_ID = new PublicKey(DLMM_PROGRAM_IDS["localhost"]); +export const DYNAMIC_AMM_PROGRAM_ID = new PublicKey( + DYNAMIC_AMM_PROGRAM_IDS["localhost"], +); +export const ALPHA_VAULT_PROGRAM_ID = new PublicKey( + ALPHA_VAULT_PROGRAM_IDS["localhost"], +);