Stake2Earn Pool is a DAMM v1 Pool that is launched with a Stake2Earn Vault. This page provides the code examples and steps to create a Stake2Earn Pool.

DAMM v1 Pool: A DAMM v1 Pool created with a fee scheduler and with initial liquidity perma-locked.

Stake2Earn Vault: Stake-to-earn farming mechanism and vault where top stakers can earn a share of fees from the locked liquidity.

Stake2Earn Pool: A Memecoin Pool with a Stake2Earn Vault.

Getting Started

For Creating a Memecoin Pool with Fee Scheduler - Using TypeScript SDK:

For Creating a Stake2Earn Vault - Using Bun Scripts:

API

Dependencies

Install Bun: You’ll also need bun to run the scripts. Install it via bun installation. Then install the dependencies by running the command bun install

Scripts

1. Install dependencies and initialize AmmImpl instance

DAMM v1 SDK: https://github.com/MeteoraAg/damm-v1-sdk

1.1 Install dependencies

# NPM
npm i @meteora-ag/dynamic-amm-sdk @solana/web3.js @solana/spl-token

# YARN
yarn add @meteora-ag/dynamic-amm-sdk @solana/web3.js @solana/spl-token

# Or, use any package manager of your choice.

1.2 Initialize AmmImpl instance

import AmmImpl, { MAINNET_POOL } from '@meteora-ag/dynamic-amm-sdk';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';

// Connection, Wallet, and AnchorProvider to interact with the network
const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com');

// Create single instance
const constantProductPool = await AmmImpl.create(mainnetConnection, MAINNET_POOL.USDC_SOL);
const stablePool = await AmmImpl.create(mainnetConnection, MAINNET_POOL.USDT_USDC);
// Or with any other pool address, refer to the pool creation section below
const pool = await AmmImpl.create(mainnetConnection, new PublicKey('...'));

// If you need to create multiple, can consider using `createMultiple`
const pools = [MAINNET_POOL.USDC_SOL, MAINNET_POOL.USDT_USDC];
const [constantProductPool, stablePool] = await AmmImpl.createMultiple(mainnetConnection, pools);

2. Fee Schedule Setup: Choose a suitable Pool and Fee Config

What fee parameters to choose depends on your project’s needs. You will need to look at the pool config list and choose a suitable pool_config_key with your preferred fee parameters.

If you want to create an Alpha Vault along with your new pool, you also need to find a pool_config_key that is mapped to a vault_config_key that meets your requirements.

If none of the pool_config_key in the list meet your requirements, please reach out to Meteora for a custom pool config key.

2.1 Public Pool Config List

Using this endpoint, integrators can input the config pubkey, and it will return an array of fee update timings for the config, if there’s any.

Integrators may choose a pool_config_key with a fee curve that suits your integration.

If there is no existing config in the list that fits your project’s requirement, a new config needs to be created.

Only the Meteora team can create a new config to add it to the preset pool config list.

2.2 Default Pool Config used by Meteora Frontend

By default, Meteora’s frontend is currently using this pool config key for Memecoin Pool creation: FiENCCbPi3rFh5pW2AJ59HC53yM32eLaCjMKxRqanKFJ

For the FiENCCbPi3rFh5pW2AJ59HC53yM32eLaCjMKxRqanKFJ pool config, it doesn’t allow creation of any Alpha Vault, therefore its field vault_config_key is 1111111...

2.3 New Custom Pool Config and Fee Curve for Integrators / Launchpads

Custom Pool Config Key: Integrators such as launchpads or other partners may need to use their own unique custom pool config key, if they want custom pool_creator_authority to prevent front-running of the token launch pool.

New dedicated config keys can only be created by Meteora. Please reach out to us if this is required.
The pool_creator_authority doesn’t work with program address. It’s not a program whitelist, it needs to be a PDA (program derived address) or normal account.

Custom Fee Curve: If you are an integrator, Meteora can also apply a custom fee curve/schedule for your pools, but we will need to create a dedicated config key for your team, where pool_creator_authority is linked to your signer wallet for pool creation. You would need to send us your signer wallet that would be used to deploy the pools.

Even with a dedicated config key for your team, Meteora would need to customize the fee curve/schedule for you. You can’t do it on your own.

Custom Pool Config and Fee Curve is used by integrators such as Moonshot by DEX Screener.

2.4 Custom Fee Curve for a Specific Pool

If required, Meteora can set a custom fee curve for a single, specific pool. This would override the fee curve used by the pool’s config key, doesn’t matter which config key is used.

For more information about setting Pool and Fee Config for Memecoin Pools, please read here.

3. Create a Memecoin Pool with Stake2Earn Vault

Important Reminder: You are recommended to configure your Stake2Earn staking rewards distribution start time (fee claim start time) to be approximately 48 hours after launch. This allows more time for total fee rewards to accumulate from trading activity in the memecoin pool. A bigger total fee reward would help make your Stake2Earn Vault look more appealing to potential stakers.

3.1 Using TypeScript SDK

After you have selected your preferred pool config key, use this example to create a Memecoin Pool with Stake2Earn Vault: https://github.com/MeteoraAg/stake-for-fee-sdk/blob/main/ts-client/src/examples/index.ts

This code example includes the steps to:

  • Mint a token
  • Create dynamic vault and pool
  • Create Stake2Earn Vault
  • Lock user’s LP to Stake2Earn Vault
import AmmImpl from "@mercurial-finance/dynamic-amm-sdk";
import { NATIVE_MINT } from "@solana/spl-token";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import { StakeForFee } from "../stake-for-fee";
import {
  DEFAULT_KEYPAIR_PATH,
  DEVNET_URL,
  handleSendTransaction,
  initializeMintAndMint,
  loadKeypairFromFile,
} from "./utils";
import { createFeeVault, createPool, lockLiquidityToFeeVault } from "./actions";
import { U64_MAX } from "../stake-for-fee/constants";

const connection = new Connection(DEVNET_URL);
const poolConfigKey = new PublicKey(
  "BdfD7rrTZEWmf8UbEBPVpvM3wUqyrR8swjAy5SNT8gJ2"
);
const mintADecimal = 9;
const mintANativeAmountMultiplier = 10 ** mintADecimal;
const mintAmount = 10_000;
const stakeFarmAmount = 1_000;

async function createPoolAndInteractWithFeeVaultExample() {
  const keypair = loadKeypairFromFile(DEFAULT_KEYPAIR_PATH);
  console.log(`Wallet ${keypair.publicKey} connected`);

  const amountAToMint =
    BigInt(mintAmount) * BigInt(mintANativeAmountMultiplier);
  const amountAToDeposit =
    BigInt(mintAmount - stakeFarmAmount) * BigInt(mintANativeAmountMultiplier); // 1,000 reserve to stake
  const amountB = BigInt(1_000_000);

  console.log("Create mint A");
  const mintA = await initializeMintAndMint(
    connection,
    keypair,
    keypair,
    mintADecimal,
    amountAToMint
  );

  console.log("1. Create dynamic vaults and pool");
  const poolKey = await createPool(
    keypair,
    mintA,
    NATIVE_MINT,
    new BN(amountAToDeposit.toString()),
    new BN(amountB.toString()),
    poolConfigKey
  );

  const pool = await AmmImpl.create(connection, poolKey);

  console.log("2. Create fee vault");
  const currentSlot = await connection.getSlot("confirmed");
  const currentOnchainTimestamp = await connection.getBlockTime(currentSlot);
  // Number of top stakers
  const topListLength = 10;
  // Number of seconds to withdraw unstaked token
  const unstakeLockDuration = new BN(3600 * 24);
  // Number of seconds for the swap fee fully dripped to stakers
  const secondsToFullUnlock = new BN(3600 * 24 * 7);
  // Timestamp to start fee distribution / drip to stakers
  const startFeeDistributeTimestamp = new BN(currentOnchainTimestamp + 10); // delay 10 seconds to be able to claim

  await createFeeVault(
    poolKey,
    pool.poolState.tokenAMint,
    keypair,
    topListLength,
    unstakeLockDuration,
    secondsToFullUnlock,
    startFeeDistributeTimestamp
  );

  console.log("3. Lock user LP for fee vault");
  await lockLiquidityToFeeVault(poolKey, pool, keypair, 10_000); // 10_000 means 100% of LP is being lock

  console.log("4. Connect to the fee vault");
  const feeVault = await StakeForFee.create(connection, poolKey);

  console.log("5. Stake amount");
  const stakeAmount = new BN(
    (BigInt(stakeFarmAmount) * BigInt(mintANativeAmountMultiplier)).toString()
  ); // 1,000 stake token (make sure you have enough balance in your wallet)
  const stakeTx = await feeVault.stake(stakeAmount, keypair.publicKey);
  const stakeSignature = await handleSendTransaction(
    connection,
    stakeTx,
    keypair
  );
  console.log("Stake Signature", stakeSignature);

  console.log("6. Get stake balance");
  await feeVault.refreshStates();
  const userEscrow = await feeVault.getUserStakeAndClaimBalance(
    keypair.publicKey
  );
  const stakeBalance =
    userEscrow.stakeEscrow.stakeAmount.toNumber() /
    10 ** feeVault.accountStates.tokenAMint.decimals;
  console.log("Stake Balance", stakeBalance);
  const claimableFeeA =
    (userEscrow.unclaimFee.feeA.toNumber() || 0) /
    10 ** feeVault.accountStates.tokenAMint.decimals;
  console.log("Claimable Fee A", claimableFeeA);
  const claimableFeeB =
    (userEscrow.unclaimFee.feeB.toNumber() || 0) /
    10 ** feeVault.accountStates.tokenBMint.decimals;
  console.log("Claimable Fee B", claimableFeeB);

  console.log("7. Claim fee");
  const claimFeeTx = await feeVault.claimFee(
    keypair.publicKey,
    new BN(U64_MAX)
  );
  for (const [index, tx] of claimFeeTx.entries()) {
    const signature = await handleSendTransaction(connection, tx, keypair);
    console.log(`Claim Fee Signature ${index + 1}`, signature);
  }

  console.log("8. Unstake");
  const unstakeKeyPair = new Keypair();
  const unstakeTx = await feeVault.unstake(
    userEscrow.stakeEscrow.stakeAmount,
    unstakeKeyPair.publicKey,
    keypair.publicKey
  );
  const unstakeSignature = await handleSendTransaction(connection, unstakeTx, [
    unstakeKeyPair,
    keypair,
  ]);
  console.log("Unstake Signature", unstakeSignature);

  console.log("9. Cancel unstaked");
  const cancelUnstake = await feeVault.cancelUnstake(
    unstakeKeyPair.publicKey,
    keypair.publicKey
  );
  const cancelUnstakeSignature = await handleSendTransaction(
    connection,
    cancelUnstake,
    keypair
  );
  console.log("Cancel Unstake Signature", cancelUnstakeSignature);

  // ⚠️ This only works after unstake period is over
  console.log("10. Withdraw unstake");
  const withdrawUnstake = await feeVault.withdraw(
    unstakeKeyPair.publicKey,
    keypair.publicKey
  );
  const withdrawUnstakeSignature = await handleSendTransaction(
    connection,
    withdrawUnstake,
    keypair
  );
  console.log("Withdraw Unstake Signature", withdrawUnstakeSignature);
}

createPoolAndInteractWithFeeVaultExample()
  .then(() => console.log("Done"))
  .catch(console.error);

3.2 Using Bun Scripts

Alternatively, the following bun script examples can be used to initialize a Stake2Earn Vault for the Memecoin Pool you created on Meteora.

First, make sure you create the token mint and the Memecoin Pool. After that, you need to lock the liquidity, before creating the Stake2Earn Vault for the Memecoin Pool. The addresses in the allocations should contain the feeFarm (Stake2Earn Vault) address.

Clone repo: https://github.com/MeteoraAg/meteora-pool-setup

Select an example config from the list to reference and change the parameters inside according to your needs: https://github.com/MeteoraAg/meteora-pool-setup/tree/main/config

Run the script with the config file which is set with your preferred parameters

Lock Liquidity
bun run src/lock_liquidity_for_m3m3.ts --config ./config/create_m3m3_farm.json
Create the Stake2Earn Vault
bun run src/create_m3m3_farm.ts --config ./config/create_m3m3_farm.json

Important Considerations

Stake2Earn staking rewards distribution start time

You are recommended to configure your Stake2Earn staking rewards distribution start time (fee claim start time) to be approximately 48 hours after launch. This allows more time for total fee rewards to accumulate from trading activity in the memecoin pool. A bigger total fee reward would help make your Stake2Earn Vault look more appealing to potential stakers.

Difference between locking liquidity via Stake2Earn vs directly locking liquidity in the Memecoin Pool

User lock and Stake2Earn lock use the same lock mechanism on a Dynamic AMM / Memecoin Pool. But users and Stake2Earn vaults have their own personal lock escrow account for the pool.

When a user permanently locks liquidity directly on the Memecoin Pool page, the user is locking the LP token to their own personal escrow account for the pool. Fees from this locked liquidity go to the user’s wallet.

However, when a user locks via the Stake2Earn creation process, the user is locking the LP to their unique Stake2Earn Vault stake escrow account. Therefore, fees from this locked liquidity go to the Stake2Earn Vault, which then distributes fees to the top stakers.

How to find the Stake2Earn vault address and a user wallet’s unique personal Stake Escrow address?

Use a wallet simulator (e.g. Sherlock extension) and paste the user’s wallet address. Try to stake or unstake tokens in a Stake2Earn Vault.

Click “Open explorer” and on the explorer page search for Vault and/or Stake Escrow fields to find the respective addresses.

Other Considerations

If you plan to use a multisig on the Meteora website, please make sure it is the SquadsX multisig, as that is the only type supported on Meteora website. Otherwise, you can’t manage liquidity through the website.