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.
Getting Started SDK: https://github.com/MeteoraAg/stake-for-fee-sdk
NPM: https://www.npmjs.com/package/@meteora-ag/m3m3
Program ID: FEESngU3neckdwib9X3KWqdL7Mjmqk9XNp3uh5JbP4KP
API Mainnet API endpoint: https://stake-for-fee-api.meteora.ag/swagger-ui/
Devnet API endpoint: https://devnet.stake-for-fee-api.meteora.ag/swagger-ui/
- Install Dependencies Copy npm i @meteora-ag/m3m3 @coral-xyz/anchor @solana/web3.js @solana/spl-token @solana/spl-token-registry
- Initialize StakeForFee Instance Copy 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 network const mainnetConnection = new Connection("https://api.mainnet-beta.solana.com"); const mockWallet = new Wallet(new Keypair()); const provider = new AnchorProvider(mainnetConnection, mockWallet, { commitment: "confirmed", }); // Alternatively, to use Solana Wallet Adapter
const poolAddress = new PublicKey( "G2MRSjNjCbFUmMf32Z1aXYhy6pc1ihLyYg6orKedyjJG" ); const m3m3 = await StakeForFee.create(connection, poolAddress); 3. To interact with StakeForFee Stake
Copy const stakeAmount = new BN( 1_000 * 10 ** feeFarm.accountStates.tokenAMint.decimals ); // 1,000 stake token (make sure you have enough balance in your wallet) const stakeTx = await feeFarm.stake(stakeAmount, mockWallet.publicKey); const stakeTxHash = await provider.sendAndConfirm(stakeTx); // Transaction hash Get stake balance and claimable balance
Copy await feeFarm.refreshStates(); // make sure to refresh states to get the latest data const userEscrow = await feeFarm.getUserStakeAndClaimBalance( mockWallet.publicKey ); const stakeBalance = userStakeEscrow.stakeEscrow.stakeAmount.toNumber() / 10 ** feeFarm.accountStates.tokenAMint.decimals; const claimableFeeA = fromLamports( userStakeEscrow.unclaimFee.feeA || 0, feeFarm.accountStates.tokenAMint.decimals ); const claimableFeeB = fromLamports( userStakeEscrow.unclaimFee.feeB || 0, feeFarm.accountStates.tokenBMint.decimals ); Claim Fee
Copy const claimFeeTx = await feeVault.claimFee( mockWallet.publicKey, new BN(U64_MAX) ); // second param is max amount, so usually we just put max number BN.js can support const claimfeeTxHash = await provider.sendAndConfirm(claimFeeTx); // Transaction hash Unstake
Copy const unstakeKeyPair = new Keypair(); const unstakeTx = await feeVault.unstake( userEscrow.stakeEscrow.stakeAmount, unstakeKeyPair.publicKey, mockWallet.publicKey ); unstakeTx.partialSign(unstakeKeyPair); // Make sure to partial sign unstakeKeypair const unstakeTxHash = await provider.sendAndConfirm(unstakeTx); // Transaction hash Get unstake period (Seconds)
Copy const unstakePeriodInSeconds = feeFarm.accountStates.feeVault.configuration.unstakeLockDuration.toNumber(); Cancel unstake
Copy const cancelUnstakeTx = await feeFarm.cancelUnstake( unstakeKeyPair.publicKey, mockWallet.publicKey ); const cancelUnstakeTxHash = await provider.sendAndConfirm(cancelUnstakeTx); Withdraw
Copy const withdrawTx = await feeFarm.withdraw( unstakeKeyPair.publicKey, mockWallet.publicKey ); const withdrawTxHash = await provider.sendAndConfirm(withdrawTx); How to create a Memecoin Pool with M3M3 Code Example Github: 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 M3M3 fee vault
Lock user's LP to M3M3 fee vault
Copy 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);
//
createPoolAndInteractWithFeeVaultExample() .then(() => console.log("Done")) .catch(console.error);