Permissioned Alpha Vault with Whitelist

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.

Example Timeline (Alpha Vault: Pro rata mode)

Code Examples

1. Main Steps

This example demonstrates how to use the permissioned Alpha Vault to achieve authorization of user deposit with whitelisted wallet and deposit cap.

  1. Update address and deposit cap. Check whitelist_wallet.csv.

  2. Create a pool with permissioned vault. Check createDynamicPoolWithPermissionedVault.ts for more details.

  3. Generate merkle tree, and create merkle root config. Check createMerkleRootConfig.ts for more details.

  4. 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();

async function createDummyMints(connection: Connection, payer: Keypair) {
  console.log("Creating mint A");
  const mintAInfo = await createTokenAndMint(
    connection,
    payer,
    6,
    100_000_000_000
  );

  console.log("Creating mint B");
  const mintBInfo = await createTokenAndMint(
    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
 */
async function getPoolConfigByRequirement(
  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 pool
  const publicPoolConfigs =
    await DynamicAmm.getPoolConfigsWithPoolCreatorAuthority(
      connection,
      PublicKey.default
    );

  // 2. Pool configs that can only be used by specific pubkey to create pool
  const creatorPoolConfigs =
    await DynamicAmm.getPoolConfigsWithPoolCreatorAuthority(
      connection,
      creator
    );

  // 3. Look for pool configs that have vault support
  const poolConfigsWithVaultSupport = [
    ...publicPoolConfigs,
    ...creatorPoolConfigs,
  ].filter((config) => config.account.vaultConfigKey != PublicKey.default);

  console.log(
    `Gotten ${poolConfigsWithVaultSupport.length} usable pool configs with vault support`
  );

  const configFuture =
    vaultMode === VaultMode.FCFS
      ? AlphaVault.getFcfsConfigs(connection)
      : AlphaVault.getProrataConfigs(connection);

  const configs = await configFuture;

  for (const programAccount of poolConfigsWithVaultSupport) {
    const { account } = programAccount;
    // 3.1 Pool activation type and duration matches
    if (
      account.activationType == activationType &&
      account.activationDuration.toNumber() >= maximumActivationDuration
    ) {
      const vaultConfig = configs.find((c) =>
        c.publicKey.equals(account.vaultConfigKey)
      );

      if (!vaultConfig) {
        continue;
      }

      const startVestingDuration =
        vaultConfig.account.startVestingDuration.toNumber();
      const endVestingDuration =
        vaultConfig.account.endVestingDuration.toNumber();

      const vestingDuration = endVestingDuration - startVestingDuration;

      // 3.2 Vault activation type, lock and vesting duration matches
      if (
        startVestingDuration >= minimumLockDuration &&
        startVestingDuration <= maximumLockDuration &&
        vestingDuration >= minimumVestingDuration &&
        vestingDuration <= maximumVestingDuration &&
        vaultConfig.account.activationType == activationType
      ) {
        return programAccount;
      }
    }
  }

  return null;
}

async function createDynamicPoolWithPermissionedVault(
  connection: Connection,
  payer: Keypair
) {
  const { mintAInfo, mintBInfo } = await createDummyMints(connection, payer);

  // Pool and vault requirement
  const maximumActivationDuration = 86400; // 1 day
  const minimumLockDuration = 60 * 30; // 30 minutes
  const maximumLockDuration = 86400; // 1 day
  const minimumVestingDuration = 60 * 60; // 1 hour
  const maximumVestingDuration = 60 * 60 * 24 * 7; // 1 week
  const vaultMode = 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 vault
  const poolConfig = await getPoolConfigByRequirement(
    connection,
    payer.publicKey,
    ActivationType.Timestamp,
    maximumActivationDuration,
    minimumLockDuration,
    maximumLockDuration,
    minimumVestingDuration,
    maximumVestingDuration,
    vaultMode
  );

  console.log("Got pool config");
  console.log(poolConfig);

  const clockAccount = await connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY);
  const clock: Clock = ClockLayout.decode(clockAccount.data);

  // Pool start trade after created for 5 hour
  const activationPoint = clock.unixTimestamp.add(new BN(3600 * 5));

  // 2. Create the pool
  const transactions =
    await DynamicAmm.createPermissionlessConstantProductPoolWithConfig2(
      connection,
      payer.publicKey,
      mintAInfo.mint,
      mintBInfo.mint,
      new BN(100_000_000),
      new BN(100_000_000),
      poolConfig.publicKey,
      {
        activationPoint,
      }
    );

  console.log("Creating pool");
  for (const tx of transactions) {
    const txHash = await sendAndConfirmTransaction(connection, tx, [payer]);
    console.log(txHash);
  }

  const poolPubkey = derivePoolAddressWithConfig(
    mintAInfo.mint,
    mintBInfo.mint,
    poolConfig.publicKey,
    DYNAMIC_AMM_PROGRAM_ID
  );

  console.log("Pool created", poolPubkey.toBase58());

  // 3. Create the alpha vault
  const initPermissionlessVaultTx = await AlphaVault.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");
  const txHash = await sendAndConfirmTransaction(
    connection,
    initPermissionlessVaultTx,
    [payer]
  );
  console.log(txHash);
}

const connection = new Connection(clusterApiUrl("devnet"));
const payer = 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);

Whitelisted wallet csv file Example

3. Create Merkle Root Config

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();

async function createMerkleRootConfig(
  vault: PublicKey,
  csvPath: string,
  payer: Keypair
) {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const alphaVault = await AlphaVault.create(connection, vault);

  // 1. Load whitelist wallet from csv file
  const whitelistWallets = await loadWhitelistWalletCsv(csvPath);
  console.log("Loaded whitelist wallets");
  console.log(whitelistWallets);

  // 2. Create merkle tree
  const tree = await createMerkleTree(connection, alphaVault, whitelistWallets);

  // 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,
    payer.publicKey
  );

  // 4. Send transaction
  console.log("Sending transaction");
  const txHash = await sendAndConfirmTransaction(
    connection,
    createMerkleRootConfigTx,
    [payer]
  );
  console.log(txHash);
}

// Permissioned alpha vault
const vault = new PublicKey("ARGqVVUPPqtqH9UeBHvYsv7AtZv623YdEaEziZ1pdDUs");
const payer = loadKeypairFromFile(process.env.KEYPAIR_PATH);
const whitelistWalletCsvFilepath =
  "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();

async function depositToPermissionedAlphaVault(
  vault: PublicKey,
  depositAmount: BN,
  csvPath: string,
  payer: Keypair
) {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const alphaVault = await AlphaVault.create(connection, vault);

  // 1. Load whitelisted wallet
  const whitelistedWallets = await loadWhitelistWalletCsv(csvPath);

  // 2. Create merkle tree
  const tree = await createMerkleTree(
    connection,
    alphaVault,
    whitelistedWallets
  );

  // 3. Get wallet proof info
  const depositorWhitelistInfo = whitelistedWallets.find((w) =>
    w.wallet.equals(payer.publicKey)
  );
  const quoteMint = await connection.getTokenSupply(alphaVault.vault.quoteMint);
  const toNativeAmountMultiplier = new Decimal(10 ** quoteMint.value.decimals);

  const nativeDepositCap = new BN(
    depositorWhitelistInfo.depositCap.mul(toNativeAmountMultiplier).toString()
  );

  const depositorProof = tree
    .getProof(payer.publicKey, nativeDepositCap)
    .map((buffer) => {
      return Array.from(new Uint8Array(buffer));
    });

  const [merkleRootConfig] = deriveMerkleRootConfig(
    alphaVault.pubkey,
    new BN(0),
    new PublicKey(PROGRAM_ID["mainnet-beta"])
  );

  // 4. Deposit
  const depositTx = await alphaVault.deposit(depositAmount, payer.publicKey, {
    merkleRootConfig,
    maxCap: nativeDepositCap,
    proof: depositorProof,
  });

  console.log(`Depositing ${depositAmount.toString()}`);
  const txHash = await sendAndConfirmTransaction(connection, depositTx, [
    payer,
  ]);
  console.log(txHash);

  const escrow = await alphaVault.getEscrow(payer.publicKey);
  console.log("Escrow info");
  console.log(escrow);
}

// Alpha vault to be deposited to
const vault = new PublicKey("ARGqVVUPPqtqH9UeBHvYsv7AtZv623YdEaEziZ1pdDUs");
const depositAmount = new BN(100_000);
const payer = loadKeypairFromFile(process.env.KEYPAIR_PATH);
const whitelistWalletCsvFilepath =
  "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);

Last updated