Skip to content

Commit

Permalink
Merge pull request #13 from MeteoraAg/feat/m3m3
Browse files Browse the repository at this point in the history
feat: Add script to create m3m3 farm
  • Loading branch information
quangkeu95 authored Jan 22, 2025
2 parents 1f79531 + 6f1f5b8 commit 0129812
Show file tree
Hide file tree
Showing 12 changed files with 418 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

## [1.3.0] - 2025-01-21 - [PR #8](https://github.com/MeteoraAg/meteora-pool-setup/pull/13)
### Added
- Script to create M3M3 fee farm.

## [1.2.0] - 2025-01-06 - [PR #8](https://github.com/MeteoraAg/meteora-pool-setup/pull/8)

### Added
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ Also we need to provide the keypair for the payer wallet in `keypair.json` file.
- `escrowFee`: Fee to create stake escrow account.
- `whitelistMode`: `permissionless` or `permission_with_merkle_proof` or `permission_with_authority`.

### Create M3M3 configuration
- `topListLength`: Length of the top list.
- `unstakeLockDurationSecs`: Duration need wait before withdraw. Starting from the unstack action timestamp.
- `secondsToFullUnlock`: Time required for locked claim fee to be fully dripped.
- `startFeeDistributeTimestamp`: When the fee start distributes. The timestamp should be 48h after pool activate to accumulate more rewards to attract stakers as in [M3M3 reminder](https://docs.meteora.ag/for-memecoins/m3m3#important-reminder)

## Testings
First, run the localnet
```bash
Expand Down Expand Up @@ -118,6 +124,18 @@ bun run src/seed_liquidity_single_bin.ts --config ./config/seed_liquidity_single
bun run src/seed_liquidity_lfg.ts --config ./config/seed_liquidity_lfg.json
```

** Create M3M3 farm**
This script requires you to create the token mint and the pool first.
After that you need to lock the liquidity before creating the M3M3 farm. The addresses in the allocations should contains the fee farm address.
```bash
bun run src/lock_liquidity_for_m3m3.ts --config ./config/create_m3m3_farm.json
```

Create the M3M3 fee farm
```bash
bun run src/create_m3m3_farm.ts --config ./config/create_m3m3_farm.json
```

## After deployment
To view pool on the UI, access the links below
- For Dynamic AMM pool: `https://app.meteora.ag/pools/<POOL_ADDRESS>`
Expand Down
22 changes: 22 additions & 0 deletions config/create_m3m3_farm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"rpcUrl": "https://api.mainnet-beta.solana.com",
"dryRun": false,
"keypairFilePath": "keypair.json",
"computeUnitPriceMicroLamports": 100000,
"baseMint": "FvxPZWBViVsmzS11MGi3ybNGjTKChwdfXU3UWopBujTn",
"quoteSymbol": "SOL",
"m3m3": {
"topListLength": 100,
"unstakeLockDurationSecs": 25200,
"secondsToFullUnlock": 86400,
"startFeeDistributeTimestamp": 1737590400
},
"lockLiquidity": {
"allocations": [
{
"percentage": 100,
"address": "D2Yt1jtjjk6cPiwYKs6krtbjfjjYiQmYWbFtTrgL2WR2"
}
]
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "meteora_pool_setup",
"version": "1.2.0",
"version": "1.3.0",
"main": "index.js",
"scripts": {
"format": "bun prettier ./src --write",
Expand All @@ -14,6 +14,7 @@
"@mercurial-finance/dynamic-amm-sdk": "^1.1.19",
"@meteora-ag/alpha-vault": "^1.1.8",
"@meteora-ag/dlmm": "^1.3.5",
"@meteora-ag/m3m3": "^1.0.5",
"@solana/spl-token": "^0.4.9",
"@solana/spl-token-registry": "^0.2.4574",
"@solana/web3.js": "^1.95.8",
Expand Down
82 changes: 82 additions & 0 deletions src/create_m3m3_farm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { BN, Wallet } from "@coral-xyz/anchor";
import {
Connection,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { MeteoraConfig, parseConfigFromCli } from "./libs/config";
import { DEFAULT_COMMITMENT_LEVEL, M3M3_PROGRAM_IDS } from "./libs/constants";
import {
safeParseKeypairFromFile,
getQuoteMint,
getQuoteDecimals,
runSimulateTransaction,
} from "./libs/utils";
import { createTokenMint } from "./libs/create_token_mint";
import { createPermissionlessDynamicPool } from "./libs/create_pool_utils";
import {
createProgram,
deriveCustomizablePermissionlessConstantProductPoolAddress,
} from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils";
import AmmImpl from "@mercurial-finance/dynamic-amm-sdk";
import StakeForFee, { deriveFeeVault } from "@meteora-ag/m3m3";
import {
create_m3m3_farm,
lockLiquidityToFeeVault,
} from "./libs/create_m3m3_farm_utils";

async function main() {
let config: MeteoraConfig = parseConfigFromCli();

console.log(`> Using keypair file path ${config.keypairFilePath}`);
let keypair = safeParseKeypairFromFile(config.keypairFilePath);

console.log("\n> Initializing with general configuration...");
console.log(`- Using RPC URL ${config.rpcUrl}`);
console.log(`- Dry run = ${config.dryRun}`);
console.log(`- Using payer ${keypair.publicKey} to execute commands`);

const connection = new Connection(config.rpcUrl, DEFAULT_COMMITMENT_LEVEL);
const wallet = new Wallet(keypair);

if (!config.baseMint) {
throw new Error("Missing baseMint in configuration");
}
let baseMint = new PublicKey(config.baseMint);
let quoteMint = getQuoteMint(config.quoteSymbol);
const ammProgram = createProgram(connection).ammProgram;
const poolKey = deriveCustomizablePermissionlessConstantProductPoolAddress(
baseMint,
quoteMint,
ammProgram.programId,
);

const poolAccount = await connection.getAccountInfo(poolKey, {
commitment: 'confirmed'
});

if (!poolAccount) {
throw new Error(`Pool ${poolKey} didn't exist. Please create it first.`);
}

console.log(`- Using base token mint ${baseMint.toString()}`);
console.log(`- Using quote token mint ${quoteMint.toString()}`);
console.log(`- Pool key ${poolKey}`);

if (!config.m3m3) {
throw new Error("Missing M3M3 configuration");
}

// 3. Create M3M3 farm
await create_m3m3_farm(
connection,
wallet.payer,
poolKey,
baseMint,
config.m3m3,
config.dryRun,
config.computeUnitPriceMicroLamports
);
}

main();
6 changes: 0 additions & 6 deletions src/create_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ async function main() {

// If we want to create a new token mint
if (config.createBaseToken) {
if (!config.createBaseToken.mintBaseTokenAmount) {
throw new Error("Missing mintBaseTokenAmount in configuration");
}
if (!config.createBaseToken.baseDecimals) {
throw new Error("Missing baseDecimals in configuration");
}
baseMint = await createTokenMint(connection, wallet, {
dryRun: config.dryRun,
mintTokenAmount: config.createBaseToken.mintBaseTokenAmount,
Expand Down
28 changes: 27 additions & 1 deletion src/libs/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
extraConfigValidation,
parseCliArguments,
safeParseJsonFromFile,
validate_config,
} from "./utils";
import Ajv, { JSONSchemaType } from "ajv";

Expand Down Expand Up @@ -225,6 +224,25 @@ const CONFIG_SCHEMA: JSONSchemaType<MeteoraConfig> = {
"seedTokenXToPositionOwner",
],
},
m3m3: {
type: "object",
nullable: true,
properties: {
topListLength: {
type: "number",
},
unstakeLockDurationSecs: {
type: "number",
},
secondsToFullUnlock: {
type: "number",
},
startFeeDistributeTimestamp: {
type: "number"
}
},
required: ["topListLength", "unstakeLockDurationSecs", "secondsToFullUnlock", "startFeeDistributeTimestamp"],
}
},
required: [
"rpcUrl",
Expand All @@ -250,6 +268,7 @@ export interface MeteoraConfig {
lockLiquidity: LockLiquidityConfig | null;
lfgSeedLiquidity: LfgSeedLiquidityConfig | null;
singleBinSeedLiquidity: SingleBinSeedLiquidityConfig | null;
m3m3: M3m3Config | null;
}

export interface CreateBaseMintConfig {
Expand Down Expand Up @@ -346,6 +365,13 @@ export interface SingleBinSeedLiquidityConfig {
seedTokenXToPositionOwner: boolean;
}

export interface M3m3Config {
topListLength: number;
unstakeLockDurationSecs: number;
secondsToFullUnlock: number;
startFeeDistributeTimestamp: number;
}

export enum ActivationTypeConfig {
Slot = "slot",
Timestamp = "timestamp",
Expand Down
6 changes: 5 additions & 1 deletion src/libs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ export const ALPHA_VAULT_PROGRAM_IDS = {
"mainnet-beta": "vaU6kP7iNEGkbmPkLmZfGwiGxd4Mob24QQCie5R9kd2",
};

export const MAX_INSTRUCTIONS_PER_STAKE_ESCROW_ACCOUNTS_CREATED = 8;
export const MAX_INSTRUCTIONS_PER_STAKE_ESCROW_ACCOUNTS_CREATED = 8;

export const M3M3_PROGRAM_IDS = {
"mainnet-beta": "FEESngU3neckdwib9X3KWqdL7Mjmqk9XNp3uh5JbP4KP",
};
88 changes: 88 additions & 0 deletions src/libs/create_m3m3_farm_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
Connection,
Keypair,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { M3m3Config, MeteoraConfig } from "./config";
import { M3M3_PROGRAM_IDS } from "./constants";
import StakeForFee, { deriveFeeVault } from "@meteora-ag/m3m3";
import { BN } from "@coral-xyz/anchor";
import { modifyComputeUnitPriceIx, runSimulateTransaction } from "./utils";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import AmmImpl, { VaultIdl } from "@mercurial-finance/dynamic-amm-sdk";
import Decimal from "decimal.js";

export async function create_m3m3_farm(
connection: Connection,
payer: Keypair,
poolKey: PublicKey,
stakeMint: PublicKey,
config: M3m3Config,
dryRun: boolean,
computeUnitPriceMicroLamports: number,
opts?: {
m3m3ProgramId: PublicKey;
},
): Promise<void> {
const m3m3ProgramId =
opts?.m3m3ProgramId ?? new PublicKey(M3M3_PROGRAM_IDS["mainnet-beta"]);
const m3m3VaultPubkey = deriveFeeVault(poolKey, m3m3ProgramId);
console.log(`- M3M3 fee vault ${m3m3VaultPubkey}`);

// 1. Create m3m3 farm
const m3m3VaultAccount = await connection.getAccountInfo(m3m3VaultPubkey, {
commitment: 'confirmed',
});

if (m3m3VaultAccount) {
console.log(`>>> M3M3 farm is already existed. Skip creating new farm.`);
return;
}

console.log(`>> Creating M3M3 fee farm...`);
const topListLength = config.topListLength;
const unstakeLockDuration = new BN(config.unstakeLockDurationSecs);
const secondsToFullUnlock = new BN(config.secondsToFullUnlock);
const startFeeDistributeTimestamp = new BN(
config.startFeeDistributeTimestamp,
);

console.log(`- Using topListLength: ${topListLength}`);
console.log(`- Using unstakeLockDuration ${unstakeLockDuration}`);
console.log(`- Using secondsToFullUnlock ${secondsToFullUnlock}`);
console.log(`- Using startFeeDistributeTimestamp ${startFeeDistributeTimestamp}`);

// m3m3 farm didn't exist
const createTx = await StakeForFee.createFeeVault(
connection,
poolKey,
stakeMint,
payer.publicKey,
{
topListLength,
unstakeLockDuration,
secondsToFullUnlock,
startFeeDistributeTimestamp,
},
);
modifyComputeUnitPriceIx(createTx, computeUnitPriceMicroLamports);

if (dryRun) {
console.log(`> Simulating create m3m3 farm tx...`);
await runSimulateTransaction(connection, [payer], payer.publicKey, [
createTx,
]);
} else {
console.log(`>> Sending create m3m3 farm transaction...`);
const txHash = await sendAndConfirmTransaction(connection, createTx, [
payer,
]).catch((err) => {
console.error(err);
throw err;
});
console.log(
`>>> M3M3 farm initialized successfully with tx hash: ${txHash}`,
);
}
}
2 changes: 2 additions & 0 deletions src/libs/create_token_mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ async function createAndMintToken(
mintDecimals,
computeUnitPriceMicroLamports,
);
console.log(`Created token mint ${mint}`);

const walletTokenATA = await getOrCreateAssociatedTokenAccount(
connection,
Expand All @@ -94,6 +95,7 @@ async function createAndMintToken(
commitment: DEFAULT_COMMITMENT_LEVEL,
},
);
console.log(`Minted ${mint} to wallet`);

return mint;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lock_liquidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async function main() {
[Buffer.from(SEEDS.LP_MINT), poolKey.toBuffer()],
createProgram(connection).ammProgram.programId,
);
const payerPoolLp = await getAssociatedTokenAccount(lpMint, wallet.publicKey);
const payerPoolLp = getAssociatedTokenAccount(lpMint, wallet.publicKey);
const payerPoolLpBalance = (
await provider.connection.getTokenAccountBalance(payerPoolLp)
).value.amount;
Expand Down
Loading

0 comments on commit 0129812

Please sign in to comment.