M3M3 is a new stake-to-earn mechanism where top memecoin stakers compete to earn fee rewards from permanently-locked liquidity in the memecoin pool, transforming memecoins from a race to dump to a race to stake.
As a highly-configurable mechanism that can be used by launchpads, creators, and holders, we believe M3M3 has the potential to greatly incentivize both staking and trading, and in turn bring more sustainable revenue and dynamics to memecoins.
npm i @meteora-ag/m3m3 @coral-xyz/anchor @solana/web3.js @solana/spl-token @solana/spl-token-registry
2. Initialize StakeForFee Instance
import StakeForFee from"@meteora-ag/m3m3";import { PublicKey } from"@solana/web3.js";import { Wallet, AnchorProvider } from"@project-serum/anchor";// Connection, Wallet, and AnchorProvider to interact with the networkconstmainnetConnection=newConnection("https://api.mainnet-beta.solana.com");constmockWallet=newWallet(newKeypair());constprovider=newAnchorProvider(mainnetConnection, mockWallet, { commitment:"confirmed",});// Alternatively, to use Solana Wallet AdapterconstpoolAddress=newPublicKey("G2MRSjNjCbFUmMf32Z1aXYhy6pc1ihLyYg6orKedyjJG");constm3m3=awaitStakeForFee.create(connection, poolAddress);
3. To interact with StakeForFee
Stake
conststakeAmount=newBN(1_000*10**feeFarm.accountStates.tokenAMint.decimals); // 1,000 stake token (make sure you have enough balance in your wallet)conststakeTx=awaitfeeFarm.stake(stakeAmount,mockWallet.publicKey);conststakeTxHash=awaitprovider.sendAndConfirm(stakeTx); // Transaction hash
Get stake balance and claimable balance
awaitfeeFarm.refreshStates(); // make sure to refresh states to get the latest dataconstuserEscrow=awaitfeeFarm.getUserStakeAndClaimBalance(mockWallet.publicKey);conststakeBalance=userStakeEscrow.stakeEscrow.stakeAmount.toNumber() /10**feeFarm.accountStates.tokenAMint.decimals;constclaimableFeeA=fromLamports(userStakeEscrow.unclaimFee.feeA ||0,feeFarm.accountStates.tokenAMint.decimals);constclaimableFeeB=fromLamports(userStakeEscrow.unclaimFee.feeB ||0,feeFarm.accountStates.tokenBMint.decimals);
Claim Fee
constclaimFeeTx=awaitfeeVault.claimFee(mockWallet.publicKey,newBN(U64_MAX)); // second param is max amount, so usually we just put max number BN.js can supportconstclaimfeeTxHash=awaitprovider.sendAndConfirm(claimFeeTx); // Transaction hash
Unstake
constunstakeKeyPair=newKeypair();constunstakeTx=awaitfeeVault.unstake(userEscrow.stakeEscrow.stakeAmount,unstakeKeyPair.publicKey,mockWallet.publicKey);unstakeTx.partialSign(unstakeKeyPair); // Make sure to partial sign unstakeKeypairconstunstakeTxHash=awaitprovider.sendAndConfirm(unstakeTx); // Transaction hash
You are recommended to configure your M3M3 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 M3M3 look more appealing to potential stakers.
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";constconnection=newConnection(DEVNET_URL);constpoolConfigKey=newPublicKey("BdfD7rrTZEWmf8UbEBPVpvM3wUqyrR8swjAy5SNT8gJ2");constmintADecimal=9;constmintANativeAmountMultiplier=10** mintADecimal;constmintAmount=10_000;conststakeFarmAmount=1_000;asyncfunctioncreatePoolAndInteractWithFeeVaultExample() {constkeypair=loadKeypairFromFile(DEFAULT_KEYPAIR_PATH);console.log(`Wallet ${keypair.publicKey} connected`);constamountAToMint=BigInt(mintAmount) *BigInt(mintANativeAmountMultiplier);constamountAToDeposit=BigInt(mintAmount - stakeFarmAmount) *BigInt(mintANativeAmountMultiplier); // 1,000 reserve to stakeconstamountB=BigInt(1_000_000);console.log("Create mint A");constmintA=awaitinitializeMintAndMint( connection, keypair, keypair, mintADecimal, amountAToMint );console.log("1. Create dynamic vaults and pool");constpoolKey=awaitcreatePool( keypair, mintA,NATIVE_MINT,newBN(amountAToDeposit.toString()),newBN(amountB.toString()), poolConfigKey );constpool=awaitAmmImpl.create(connection, poolKey);console.log("2. Create fee vault");constcurrentSlot=awaitconnection.getSlot("confirmed");constcurrentOnchainTimestamp=awaitconnection.getBlockTime(currentSlot);// Number of top stakersconsttopListLength=10;// Number of seconds to withdraw unstaked tokenconstunstakeLockDuration=newBN(3600*24);// Number of seconds for the swap fee fully dripped to stakersconstsecondsToFullUnlock=newBN(3600*24*7);// Timestamp to start fee distribution / drip to stakersconststartFeeDistributeTimestamp=newBN(currentOnchainTimestamp +10); // delay 10 seconds to be able to claimawaitcreateFeeVault( poolKey,pool.poolState.tokenAMint, keypair, topListLength, unstakeLockDuration, secondsToFullUnlock, startFeeDistributeTimestamp );console.log("3. Lock user LP for fee vault");awaitlockLiquidityToFeeVault(poolKey, pool, keypair,10_000); // 10_000 means 100% of LP is being lockconsole.log("4. Connect to the fee vault");constfeeVault=awaitStakeForFee.create(connection, poolKey);console.log("5. Stake amount");conststakeAmount=newBN( (BigInt(stakeFarmAmount) *BigInt(mintANativeAmountMultiplier)).toString() ); // 1,000 stake token (make sure you have enough balance in your wallet)conststakeTx=awaitfeeVault.stake(stakeAmount,keypair.publicKey);conststakeSignature=awaithandleSendTransaction( connection, stakeTx, keypair );console.log("Stake Signature", stakeSignature);console.log("6. Get stake balance");awaitfeeVault.refreshStates();constuserEscrow=awaitfeeVault.getUserStakeAndClaimBalance(keypair.publicKey );conststakeBalance=userEscrow.stakeEscrow.stakeAmount.toNumber() /10**feeVault.accountStates.tokenAMint.decimals;console.log("Stake Balance", stakeBalance);constclaimableFeeA= (userEscrow.unclaimFee.feeA.toNumber() ||0) /10**feeVault.accountStates.tokenAMint.decimals;console.log("Claimable Fee A", claimableFeeA);constclaimableFeeB= (userEscrow.unclaimFee.feeB.toNumber() ||0) /10**feeVault.accountStates.tokenBMint.decimals;console.log("Claimable Fee B", claimableFeeB);console.log("7. Claim fee");constclaimFeeTx=awaitfeeVault.claimFee(keypair.publicKey,newBN(U64_MAX) );for (const [index,tx] ofclaimFeeTx.entries()) {constsignature=awaithandleSendTransaction(connection, tx, keypair);console.log(`Claim Fee Signature ${index +1}`, signature); }console.log("8. Unstake");constunstakeKeyPair=newKeypair();constunstakeTx=awaitfeeVault.unstake(userEscrow.stakeEscrow.stakeAmount,unstakeKeyPair.publicKey,keypair.publicKey );constunstakeSignature=awaithandleSendTransaction(connection, unstakeTx, [ unstakeKeyPair, keypair, ]);console.log("Unstake Signature", unstakeSignature);console.log("9. Cancel unstaked");constcancelUnstake=awaitfeeVault.cancelUnstake(unstakeKeyPair.publicKey,keypair.publicKey );constcancelUnstakeSignature=awaithandleSendTransaction( connection, cancelUnstake, keypair );console.log("Cancel Unstake Signature", cancelUnstakeSignature);// ⚠️ This only works after unstake period is overconsole.log("10. Withdraw unstake");constwithdrawUnstake=awaitfeeVault.withdraw(unstakeKeyPair.publicKey,keypair.publicKey );constwithdrawUnstakeSignature=awaithandleSendTransaction( connection, withdrawUnstake, keypair );console.log("Withdraw Unstake Signature", withdrawUnstakeSignature);}createPoolAndInteractWithFeeVaultExample().then(() =>console.log("Done")).catch(console.error);
Difference between locking liquidity via M3M3 vs directly locking liquidity in the Memecoin Pool
User lock and M3M3 lock use the same lock mechanism on a Dynamic AMM / Memecoin Pool. But users and M3M3 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. Therefore, fees from locked liquidity go to the user's wallet.
When a user locks via the M3M3 creation process, the user is locking the LP to the M3M3 escrow account. Therefore, fees from locked liquidity go to the M3M3 vault, which then distributes fees to the top stakers.