Permissioned Alpha Vault has whitelist and individual deposit cap, so only whitelisted addresses can deposit into the vault, up to the deposit cap defined for the wallet.
To use the following Alpha Vault SDK examples, please complete the steps listed in the Alpha Vault SDK section first.
This example demonstrates how to use the permissioned Alpha Vault to achieve authorization of user deposit with whitelisted wallet and deposit cap.
Update address and deposit cap. Check whitelist_wallet.csv.
Create a pool with permissioned vault. Check createDynamicPoolWithPermissionedVault.ts for more details.
Generate merkle tree, and create merkle root config. Check createMerkleRootConfig.ts for more details.
Deposit with merkle proof. Check depositWithProof.ts for more details.
2. Create Permissioned Alpha Vault with Whitelist and Individual Deposit Cap
This example shows how to create a Dynamic AMM Pool with permissioned Alpha Vault (that has a whitelist and deposit cap)
Please contact the Meteora team if the existing pool and Alpha Vault config don't meet your requirements.
Currently only Dynamic AMM Pool support permissioned Alpha Vault with whitelist and deposit cap.
import { clusterApiUrl, Connection, Keypair, PublicKey, sendAndConfirmTransaction, SYSVAR_CLOCK_PUBKEY,} from"@solana/web3.js";import AlphaVault, { DYNAMIC_AMM_PROGRAM_ID, PoolType, VaultMode } from"../..";import DynamicAmm from"@mercurial-finance/dynamic-amm-sdk";import { ActivationType, Clock, ClockLayout, createTokenAndMint, loadKeypairFromFile,} from"../utils";import dotenv from"dotenv";import { BN } from"bn.js";import { derivePoolAddressWithConfig } from"@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils";dotenv.config();asyncfunctioncreateDummyMints(connection:Connection, payer:Keypair) {console.log("Creating mint A");constmintAInfo=awaitcreateTokenAndMint( connection, payer,6,100_000_000_000 );console.log("Creating mint B");constmintBInfo=awaitcreateTokenAndMint( connection, payer,6,100_000_000_000 );return { mintAInfo, mintBInfo, };}/** * * @param connection * @param creator * @param activationType Pool activation based on time or slot * @param maximumActivationDuration Maximum duration for pool to be activation after creation * @param minimumLockDuration Minimum duration for bought token be locked * @param maximumLockDuration Maximum duration for bought token to be locked * @param minimumVestingDuration Minimum duration for bought token to be vested * @param maximumVestingDuration Maximum duration for bought token be vested * @param vaultMode Vault mode: fcfs or prorata */asyncfunctiongetPoolConfigByRequirement( connection:Connection, creator:PublicKey, activationType:ActivationType, maximumActivationDuration:number, minimumLockDuration:number, maximumLockDuration:number, minimumVestingDuration:number, maximumVestingDuration:number, vaultMode:VaultMode) {// 1. Pool configs that can be used by anyone to create poolconstpublicPoolConfigs=awaitDynamicAmm.getPoolConfigsWithPoolCreatorAuthority( connection,PublicKey.default );// 2. Pool configs that can only be used by specific pubkey to create poolconstcreatorPoolConfigs=awaitDynamicAmm.getPoolConfigsWithPoolCreatorAuthority( connection, creator );// 3. Look for pool configs that have vault supportconstpoolConfigsWithVaultSupport= [...publicPoolConfigs,...creatorPoolConfigs, ].filter((config) =>config.account.vaultConfigKey !=PublicKey.default);console.log(`Gotten ${poolConfigsWithVaultSupport.length} usable pool configs with vault support` );constconfigFuture= vaultMode ===VaultMode.FCFS?AlphaVault.getFcfsConfigs(connection):AlphaVault.getProrataConfigs(connection);constconfigs=await configFuture;for (constprogramAccountof poolConfigsWithVaultSupport) {const { account } = programAccount;// 3.1 Pool activation type and duration matchesif (account.activationType == activationType &&account.activationDuration.toNumber() >= maximumActivationDuration ) {constvaultConfig=configs.find((c) =>c.publicKey.equals(account.vaultConfigKey) );if (!vaultConfig) {continue; }conststartVestingDuration=vaultConfig.account.startVestingDuration.toNumber();constendVestingDuration=vaultConfig.account.endVestingDuration.toNumber();constvestingDuration= endVestingDuration - startVestingDuration;// 3.2 Vault activation type, lock and vesting duration matchesif ( startVestingDuration >= minimumLockDuration && startVestingDuration <= maximumLockDuration && vestingDuration >= minimumVestingDuration && vestingDuration <= maximumVestingDuration &&vaultConfig.account.activationType == activationType ) {return programAccount; } } }returnnull;}asyncfunctioncreateDynamicPoolWithPermissionedVault( connection:Connection, payer:Keypair) {const { mintAInfo,mintBInfo } =awaitcreateDummyMints(connection, payer);// Pool and vault requirementconstmaximumActivationDuration=86400; // 1 dayconstminimumLockDuration=60*30; // 30 minutesconstmaximumLockDuration=86400; // 1 dayconstminimumVestingDuration=60*60; // 1 hourconstmaximumVestingDuration=60*60*24*7; // 1 weekconstvaultMode=VaultMode.PRORATA; // 1.Find pool config where it allow user to create pool with customizable start trading time which start from NOW -> NOW + 24H
// With lock duration fall between 30 minutes -> 1 days (non customizable)// And vesting duration fall between 1 hour -> 1 week (non customizable)// And prorata vaultconstpoolConfig=awaitgetPoolConfigByRequirement( connection,payer.publicKey,ActivationType.Timestamp, maximumActivationDuration, minimumLockDuration, maximumLockDuration, minimumVestingDuration, maximumVestingDuration, vaultMode );console.log("Got pool config");console.log(poolConfig);constclockAccount=awaitconnection.getAccountInfo(SYSVAR_CLOCK_PUBKEY);constclock:Clock=ClockLayout.decode(clockAccount.data);// Pool start trade after created for 5 hourconstactivationPoint=clock.unixTimestamp.add(newBN(3600*5));// 2. Create the poolconsttransactions=awaitDynamicAmm.createPermissionlessConstantProductPoolWithConfig2( connection,payer.publicKey,mintAInfo.mint,mintBInfo.mint,newBN(100_000_000),newBN(100_000_000),poolConfig.publicKey, { activationPoint, } );console.log("Creating pool");for (consttxof transactions) {consttxHash=awaitsendAndConfirmTransaction(connection, tx, [payer]);console.log(txHash); }constpoolPubkey=derivePoolAddressWithConfig(mintAInfo.mint,mintBInfo.mint,poolConfig.publicKey,DYNAMIC_AMM_PROGRAM_ID );console.log("Pool created",poolPubkey.toBase58());// 3. Create the alpha vaultconstinitPermissionlessVaultTx=awaitAlphaVault.createPermissionedVault( connection, { baseMint:mintAInfo.mint, quoteMint:mintBInfo.mint, poolType:PoolType.DYNAMIC, vaultMode:VaultMode.PRORATA, poolAddress: poolPubkey, config:poolConfig.account.vaultConfigKey, },payer.publicKey );console.log("Creating vault");consttxHash=awaitsendAndConfirmTransaction( connection, initPermissionlessVaultTx, [payer] );console.log(txHash);}constconnection=newConnection(clusterApiUrl("devnet"));constpayer=loadKeypairFromFile(process.env.KEYPAIR_PATH);/** * This example shows how to create dynamic pool with permissioned vault * Please contact meteora team if the existing pool and vault config doesn't meet the requirement * Currently only dynamic pool support permissioned vault */createDynamicPoolWithPermissionedVault(connection, payer).then(() => {console.log("Done"); }).catch(console.error);
This example shows how to create a merkle root config after generating a merkle tree, which is required to verify the whitelisted wallet address data, and enable deposits by whitelisted wallets.
import { clusterApiUrl, Connection, Keypair, PublicKey, sendAndConfirmTransaction,} from"@solana/web3.js";import { loadKeypairFromFile } from"../utils";import { AlphaVault } from"../../alpha-vault";import dotenv from"dotenv";import { createMerkleTree, loadWhitelistWalletCsv } from"./utils";import { BN } from"bn.js";dotenv.config();asyncfunctioncreateMerkleRootConfig( vault:PublicKey, csvPath:string, payer:Keypair) {constconnection=newConnection(clusterApiUrl("devnet"),"confirmed");constalphaVault=awaitAlphaVault.create(connection, vault);// 1. Load whitelist wallet from csv fileconstwhitelistWallets=awaitloadWhitelistWalletCsv(csvPath);console.log("Loaded whitelist wallets");console.log(whitelistWallets);// 2. Create merkle treeconsttree=awaitcreateMerkleTree(connection, alphaVault, whitelistWallets);// 3. Create merkle root config// If the tree grew too large, one can partition it into multiple tree by setting different versionconstversion=newBN(0);constcreateMerkleRootConfigTx=awaitalphaVault.createMerkleRootConfig(tree.getRoot(), version,payer.publicKey );// 4. Send transactionconsole.log("Sending transaction");consttxHash=awaitsendAndConfirmTransaction( connection, createMerkleRootConfigTx, [payer] );console.log(txHash);}// Permissioned alpha vaultconstvault=newPublicKey("ARGqVVUPPqtqH9UeBHvYsv7AtZv623YdEaEziZ1pdDUs");constpayer=loadKeypairFromFile(process.env.KEYPAIR_PATH);constwhitelistWalletCsvFilepath="src/examples/permissioned/whitelist_wallet.csv";/** * This example shows how to close an escrow account and get rental back. Close escrow can only happen after vesting is complete, and escrow have claimed all the bought token.
*/createMerkleRootConfig(vault, whitelistWalletCsvFilepath, payer).then(() => {console.log("Done"); }).catch(console.error);
4. Deposit SOL or USDC with Proof
This example shows how to deposit into a permissioned alpha vault using a whitelisted wallet. Deposit can only happen before the end of the deposit period.
import { clusterApiUrl, Connection, Keypair, PublicKey, sendAndConfirmTransaction,} from"@solana/web3.js";import { loadKeypairFromFile } from"../utils";import { AlphaVault } from"../../alpha-vault";import BN from"bn.js";import dotenv from"dotenv";import Decimal from"decimal.js";import { deriveMerkleRootConfig } from"../../alpha-vault/helper";import { createMerkleTree, loadWhitelistWalletCsv } from"./utils";import { PROGRAM_ID } from"../../alpha-vault/constant";dotenv.config();asyncfunctiondepositToPermissionedAlphaVault( vault:PublicKey, depositAmount:BN, csvPath:string, payer:Keypair) {constconnection=newConnection(clusterApiUrl("devnet"),"confirmed");constalphaVault=awaitAlphaVault.create(connection, vault);// 1. Load whitelisted walletconstwhitelistedWallets=awaitloadWhitelistWalletCsv(csvPath);// 2. Create merkle treeconsttree=awaitcreateMerkleTree( connection, alphaVault, whitelistedWallets );// 3. Get wallet proof infoconstdepositorWhitelistInfo=whitelistedWallets.find((w) =>w.wallet.equals(payer.publicKey) );constquoteMint=awaitconnection.getTokenSupply(alphaVault.vault.quoteMint);consttoNativeAmountMultiplier=newDecimal(10**quoteMint.value.decimals);constnativeDepositCap=newBN(depositorWhitelistInfo.depositCap.mul(toNativeAmountMultiplier).toString() );constdepositorProof= tree.getProof(payer.publicKey, nativeDepositCap).map((buffer) => {returnArray.from(newUint8Array(buffer)); });const [merkleRootConfig] =deriveMerkleRootConfig(alphaVault.pubkey,newBN(0),newPublicKey(PROGRAM_ID["mainnet-beta"]) );// 4. DepositconstdepositTx=awaitalphaVault.deposit(depositAmount,payer.publicKey, { merkleRootConfig, maxCap: nativeDepositCap, proof: depositorProof, });console.log(`Depositing ${depositAmount.toString()}`);consttxHash=awaitsendAndConfirmTransaction(connection, depositTx, [ payer, ]);console.log(txHash);constescrow=awaitalphaVault.getEscrow(payer.publicKey);console.log("Escrow info");console.log(escrow);}// Alpha vault to be deposited toconstvault=newPublicKey("ARGqVVUPPqtqH9UeBHvYsv7AtZv623YdEaEziZ1pdDUs");constdepositAmount=newBN(100_000);constpayer=loadKeypairFromFile(process.env.KEYPAIR_PATH);constwhitelistWalletCsvFilepath="src/examples/permissioned/whitelist_wallet.csv";/** * This example shows how to deposit to permissioned alpha vault. Deposit can only happen before the deposit close. */depositToPermissionedAlphaVault( vault, depositAmount, whitelistWalletCsvFilepath, payer).then(() => {console.log("Done"); }).catch(console.error);
5. Configure Alpha Vault Timings
If you are integrating the Alpha Vault program and need to view and select the predefined timings for the various phases, such as:
Deposit open time (only for FCFS mode currently)
Deposit close time
Vault crank/buy start time
Vault crank/buy end time
Withdraw unused USDC or SOL after crank is successful
Token claim start time
Vesting start time
Vesting end time
You can use this config endpoint that has predefined timings created by Meteora.
If your preferred configs are not available in the predefined config lists above, and you want more customization, please contact the Meteora team.
Config endpoint is based on Pool Activation Timing
The above config endpoint is tied to the pool activation timing, so all timings for the different Alpha Vault phases is derived from the pool activation timing.
In this example below, the claim process will start 3600 seconds (1 hour) after pool activation, and it will end 7200 seconds (2 hours) after pool activation. In other words, after the pool starts trading, tokens are locked first and released for claiming 3600 seconds (1 hour) later.
{
maxBuyingCap: new BN(10000000),
index,
startVestingDuration: new BN(3600),
endVestingDuration: new BN(7200),
escrowFee: new BN(0),
individualDepositingCap: new BN(50000),
activationType: ActivationType.TIMESTAMP,
depositingPoint: new BN(1028979080),
}
6. Important Reminders
A) Claim start time should NOT be earlier than Pool activation time
For a new token launch, the project should ensure that token claiming from the Alpha Vault is NOT possible before the launch pool trading activation or before the token starts trading anywhere, whether on a Dynamic AMM or DLMM Pool. If users are able to claim tokens before the launch pool/token starts trading, they may create a separate market with a price that deviates from the project's preferred launch price.
B) For Memecoins that want to permanently lock liquidity, lock it before Pool activation time
If the Alpha Vault is used for a Memecoin launch pool, and the project wants to permanently lock liquidity in the Memecoin pool, they should ideally lock the liquidity BEFORE the pool starts trading, so that swap fees from locked liquidity are captured the second trading starts.
In a scenario where:
The pool started trading - t0
Liquidity gets deposited to the pool by the project, but not locked - t1
Subsequently, that liquidity is permanently locked by the project - t2
The project will not be able to claim swap fees earned from t1 to t2, since fees from t1 to t2 are assigned to the LP token before the LP token gets permanently locked. When the LP token gets locked, the earlier fees are locked along with it.
The project will only be able to claim swap fees from locked liquidity starting from t2.