Merge pull request #3 from quangkeu95/feat/meteora

feat: Create Meteora pools
This commit is contained in:
Hashira
2025-01-13 12:28:30 +07:00
committed by GitHub
14 changed files with 5088 additions and 5165 deletions

View File

@@ -40,6 +40,9 @@
"@metaplex-foundation/umi": "^0.9.2",
"@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
"@metaplex-foundation/umi-web3js-adapters": "^0.9.2",
"@mercurial-finance/dynamic-amm-sdk": "^1.1.19",
"@meteora-ag/alpha-vault": "^1.1.7",
"@meteora-ag/dlmm": "^1.3.0",
"@onsol/tldparser": "^0.6.7",
"@orca-so/common-sdk": "0.6.4",
"@orca-so/whirlpools-sdk": "^0.13.12",
@@ -78,4 +81,4 @@
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
}
}

9584
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,8 @@ import {
fetchPythPriceFeedID,
flashOpenTrade,
flashCloseTrade,
createMeteoraDynamicAMMPool,
createMeteoraDlmmPool,
createCollection,
createSingle,
multisig_transfer_from_treasury,
@@ -338,6 +340,61 @@ export class SolanaAgentKit {
);
}
async meteoraCreateDynamicPool(
tokenAMint: PublicKey,
tokenBMint: PublicKey,
tokenAAmount: BN,
tokenBAmount: BN,
tradeFeeNumerator: number,
activationPoint: BN | null,
hasAlphaVault: boolean,
activationType: number,
computeUnitMicroLamports: number = 100000,
): Promise<string> {
return createMeteoraDynamicAMMPool(
this,
tokenAMint,
tokenBMint,
tokenAAmount,
tokenBAmount,
{
tradeFeeNumerator,
activationPoint,
hasAlphaVault,
activationType,
padding: new Array(90).fill(0),
},
computeUnitMicroLamports,
);
}
async meteoraCreateDlmmPool(
tokenAMint: PublicKey,
tokenBMint: PublicKey,
binStep: number,
initialPrice: number,
priceRoundingUp: boolean,
feeBps: number,
activationType: number,
hasAlphaVault: boolean,
activationPoint: BN | undefined,
computeUnitMicroLamports: number = 100000,
): Promise<string> {
return createMeteoraDlmmPool(
this,
binStep,
tokenAMint,
tokenBMint,
initialPrice,
priceRoundingUp,
feeBps,
activationType,
hasAlphaVault,
activationPoint,
computeUnitMicroLamports,
);
}
async orcaClosePosition(positionMintAddress: PublicKey) {
return orcaClosePosition(this, positionMintAddress);
}

View File

@@ -27,9 +27,18 @@ export const DEFAULT_OPTIONS = {
LEVERAGE_BPS: 50000, // 10000 = x1, 50000 = x5, 100000 = x10, 1000000 = x100
} as const;
export const METEORA_DYNAMIC_FEE_DENOMINATOR = 100000;
/**
* Jupiter API URL
*/
export const JUP_API = "https://quote-api.jup.ag/v6";
export const JUP_REFERRAL_ADDRESS =
"REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3";
export const METEORA_DYNAMIC_AMM_PROGRAM_ID = new PublicKey(
"Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB",
);
export const METEORA_DLMM_PROGRAM_ID = new PublicKey(
"LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ",
);

View File

@@ -24,6 +24,7 @@ export * from "./tiplink";
export * from "./sns";
export * from "./lightprotocol";
export * from "./squads";
export * from "./meteora";
import { SolanaAgentKit } from "../agent";
import {
@@ -91,6 +92,8 @@ import {
SolanaApproveProposal2by2Multisig,
SolanaExecuteProposal2by2Multisig,
SolanaRejectProposal2by2Multisig,
SolanaMeteoraCreateDynamicPool,
SolanaMeteoraCreateDlmmPool,
} from "./index";
export function createSolanaTools(solanaKit: SolanaAgentKit) {
@@ -125,6 +128,8 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaBatchOrderTool(solanaKit),
new SolanaCancelAllOrdersTool(solanaKit),
new SolanaWithdrawAllTool(solanaKit),
new SolanaMeteoraCreateDynamicPool(solanaKit),
new SolanaMeteoraCreateDlmmPool(solanaKit),
new SolanaClosePosition(solanaKit),
new SolanaOrcaCreateCLMM(solanaKit),
new SolanaOrcaCreateSingleSideLiquidityPool(solanaKit),

View File

@@ -0,0 +1,2 @@
export * from "./meteora_dlmm_pool";
export * from "./meteora_dynamic_pool";

View File

@@ -0,0 +1,83 @@
import { PublicKey } from "@solana/web3.js";
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
import { BN } from "bn.js";
export class SolanaMeteoraCreateDlmmPool extends Tool {
name = "meteora_create_dlmm_pool";
description = `Create a Meteora DLMM Pool. This function doesn't add liquidity.
Inputs (JSON string):
- tokenAMint: string, token A mint (required).
- tokenBMint: string, token B mint (required).
- binStep: number, pool bin step, e.g., 20 (required).
- initialPrice: number, pool initial price, e.g., 0.25 (required).
- feeBps: number, trade fee in percentage, e.g. 20 for 0.2% (required).
- priceRoundingUp: boolean, whether the initial price should be rounded up or not, default is true (optional).
- activationType: number, pool start trading time indicator. 0 is slot and 1 is timestamp, default is 1 for timestamp (optional).
- activationPoint: number, pool start trading slot / timestamp, default is null means pool can start trading immediately (optional).
- hasAlphaVault: boolean, whether the pool supports alpha vault, default is false (optional).
- computeUnitMicroLamports: number, the priority fee in micro-lamports unit, default is 100000 (optional).
`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
interface CreateMeteoraDlmmPoolInput {
tokenAMint: string;
tokenBMint: string;
binStep: number;
initialPrice: number;
feeBps: number;
priceRoundingUp?: boolean;
activationType?: number;
activationPoint?: number;
hasAlphaVault?: boolean;
computeUnitMicroLamports?: number;
}
const inputFormat: CreateMeteoraDlmmPoolInput = JSON.parse(input);
const tokenAMint = new PublicKey(inputFormat.tokenAMint);
const tokenBMint = new PublicKey(inputFormat.tokenBMint);
const binStep = inputFormat.binStep;
const initialPrice = inputFormat.initialPrice;
const feeBps = inputFormat.feeBps;
const priceRoundingUp = inputFormat.priceRoundingUp ?? true;
const activationType = inputFormat.activationType ?? 1;
const activationPoint = inputFormat.activationPoint
? new BN(inputFormat.activationPoint)
: undefined;
const hasAlphaVault = inputFormat.hasAlphaVault ?? false;
const computeUnitMicroLamports =
inputFormat.computeUnitMicroLamports ?? 100000;
const txId = await this.solanaKit.meteoraCreateDlmmPool(
tokenAMint,
tokenBMint,
binStep,
initialPrice,
priceRoundingUp,
feeBps,
activationType,
hasAlphaVault,
activationPoint,
computeUnitMicroLamports,
);
return JSON.stringify({
status: "success",
message: "Meteora DLMM pool created successfully.",
transaction: txId,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,82 @@
import { PublicKey } from "@solana/web3.js";
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
import { BN } from "bn.js";
export class SolanaMeteoraCreateDynamicPool extends Tool {
name = "meteora_create_dynamic_pool";
description = `Create a Meteora Dynamic Pool. This function adds liquidity with a constant-product formula.
Inputs (JSON string):
- tokenAMint: string, token A mint (required).
- tokenBMint: string, token B mint (required).
- tokenAAmount: number, token A amount including decimals, e.g., 1000000000 (required).
- tokenBAmount: number, token B amount including decimals, e.g., 1000000000 (required).
- tradeFeeNumerator: number, trade fee numerator, e.g., 2500 for 2.5% (required).
- activationType: number, pool start trading time indicator, 0 is slot and 1 is timestamp, default is 1 for timestamp (optional).
- activationPoint: number, pool start trading slot / timestamp, default is null means pool can start trading immediately (optional).
- hasAlphaVault: boolean, whether the pool supports alpha vault, default is false (optional).
- computeUnitMicroLamports: number, the priority fee in micro-lamports unit, default is 100000 (optional).
`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
interface CreateMeteoraDynamicAmmPoolInput {
tokenAMint: string;
tokenBMint: string;
tokenAAmount: number;
tokenBAmount: number;
tradeFeeNumerator: number;
activationType?: number;
activationPoint?: number;
hasAlphaVault?: boolean;
computeUnitMicroLamports?: number;
}
const inputFormat: CreateMeteoraDynamicAmmPoolInput = JSON.parse(input);
const tokenAMint = new PublicKey(inputFormat.tokenAMint);
const tokenBMint = new PublicKey(inputFormat.tokenBMint);
const tokenAAmount = new BN(inputFormat.tokenAAmount.toString());
const tokenBAmount = new BN(inputFormat.tokenBAmount.toString());
const tradeFeeNumerator = new BN(
inputFormat.tradeFeeNumerator.toString(),
).toNumber();
const activationType = inputFormat.activationType ?? 1;
const activationPoint = inputFormat.activationPoint
? new BN(inputFormat.activationPoint)
: null;
const hasAlphaVault = inputFormat.hasAlphaVault ?? false;
const computeUnitMicroLamports =
inputFormat.computeUnitMicroLamports ?? 100000;
const txId = await this.solanaKit.meteoraCreateDynamicPool(
tokenAMint,
tokenBMint,
tokenAAmount,
tokenBAmount,
tradeFeeNumerator,
activationPoint,
hasAlphaVault,
activationType,
computeUnitMicroLamports,
);
return JSON.stringify({
status: "success",
message: "Meteora Dynamic pool created successfully.",
transaction: txId,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,121 @@
// import AlphaVault, { PoolType, WhitelistMode } from "@meteora-ag/alpha-vault";
// import { SolanaAgentKit } from "../agent";
// import { PublicKey, sendAndConfirmTransaction } from "@solana/web3.js";
// import BN from "bn.js";
// /**
// * Create Meteora FCFS alpha vault
// * @param agent SolanaAgentKit instance
// * @param tokenAMint Token A mint
// * @param tokenBMint Token B mint
// * @param poolAddress Pool mint
// * @param poolType Either PoolType.DYNAMIC or PoolType.DLMM
// * @param depositingPoint The point when the vault allows deposits
// * @param startVestingPoint The point when the vault starts vesting
// * @param endVestingPoint The point when the vault ends vesting
// * @param maxDepositingCap Maximum number of deposit amount for vault
// * @param individualDepositingCap Maximum number of deposit amount for individual
// * @param escrowFee Fee to create stake escrow account
// * @param whitelistMode Alpha vault whitelist mode
// * @returns
// */
// export async function createMeteoraFcfsAlphaVault(
// agent: SolanaAgentKit,
// tokenAMint: PublicKey,
// tokenBMint: PublicKey,
// poolAddress: PublicKey,
// poolType: PoolType,
// depositingPoint: BN,
// startVestingPoint: BN,
// endVestingPoint: BN,
// maxDepositingCap: BN,
// individualDepositingCap: BN,
// escrowFee: BN,
// whitelistMode: WhitelistMode
// ): Promise<string> {
// const createAlphaVaultTx = await AlphaVault.createCustomizableFcfsVault(
// agent.connection,
// {
// baseMint: tokenAMint,
// quoteMint: tokenBMint,
// poolAddress,
// poolType,
// depositingPoint,
// startVestingPoint,
// endVestingPoint,
// maxDepositingCap,
// individualDepositingCap,
// escrowFee,
// whitelistMode
// },
// agent.wallet_address
// );
// const createAlphaVaultTxHash = await sendAndConfirmTransaction(
// agent.connection,
// createAlphaVaultTx,
// [agent.wallet],
// ).catch((err) => {
// console.error(err);
// throw err;
// });
// return createAlphaVaultTxHash;
// }
// /**
// * Create Meteora Prorata alpha vault
// * @param agent SolanaAgentKit instance
// * @param tokenAMint Token A mint
// * @param tokenBMint Token B mint
// * @param poolAddress Pool mint
// * @param poolType Either PoolType.DYNAMIC or PoolType.DLMM
// * @param depositingPoint The point when the vault allows deposits
// * @param startVestingPoint The point when the vault starts vesting
// * @param endVestingPoint The point when the vault ends vesting
// * @param maxBuyingCap Maximum buying amount
// * @param escrowFee Fee to create stake escrow account
// * @param whitelistMode Alpha vault whitelist mode
// * @returns
// */
// export async function createMeteoraProrataAlphaVault(
// agent: SolanaAgentKit,
// tokenAMint: PublicKey,
// tokenBMint: PublicKey,
// poolAddress: PublicKey,
// poolType: PoolType,
// depositingPoint: BN,
// startVestingPoint: BN,
// endVestingPoint: BN,
// maxBuyingCap: BN,
// escrowFee: BN,
// whitelistMode: WhitelistMode
// ): Promise<string> {
// const createAlphaVaultTx = await AlphaVault.createCustomizableProrataVault(
// agent.connection,
// {
// baseMint: tokenAMint,
// quoteMint: tokenBMint,
// poolAddress,
// poolType,
// depositingPoint,
// startVestingPoint,
// endVestingPoint,
// maxBuyingCap,
// escrowFee,
// whitelistMode
// },
// agent.wallet_address
// );
// const createAlphaVaultTxHash = await sendAndConfirmTransaction(
// agent.connection,
// createAlphaVaultTx,
// [agent.wallet],
// ).catch((err) => {
// console.error(err);
// throw err;
// });
// return createAlphaVaultTxHash;
// }

View File

@@ -0,0 +1,97 @@
import { SolanaAgentKit } from "../agent";
import BN from "bn.js";
import {
ComputeBudgetProgram,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import DLMM, { ActivationType } from "@meteora-ag/dlmm";
import { getMint } from "@solana/spl-token";
/**
* Create Meteora DLMM pool
* @param agent SolanaAgentKit instance
* @param binStep DLMM pool bin step
* @param tokenAMint Token A mint
* @param tokenBMint Token B mint
* @param initialPrice Initial pool price in ratio tokenA / tokenB
* @param priceRoundingUp Whether to rounding up the initial pool price
* @param feeBps Pool trading fee in BPS
* @param activationType Pool activation type (ActivationType.Timestamp or ActivationType.Slot)
* @param hasAlphaVault Whether the pool has Meteora alpha vault or not
* @param activationPoint Activation point depending on activation type, or null if pool doesn't have an activation point
* @returns Transaction signature
*/
export async function createMeteoraDlmmPool(
agent: SolanaAgentKit,
binStep: number,
tokenAMint: PublicKey,
tokenBMint: PublicKey,
initialPrice: number,
priceRoundingUp: boolean,
feeBps: number,
activationType: ActivationType,
hasAlphaVault: boolean,
activationPoint: BN | undefined,
computeUnitMicroLamports: number,
): Promise<string> {
const tokenAMintInfo = await getMint(agent.connection, tokenAMint);
const tokenBMintInfo = await getMint(agent.connection, tokenBMint);
const initPrice = DLMM.getPricePerLamport(
tokenAMintInfo.decimals,
tokenBMintInfo.decimals,
initialPrice,
);
const activateBinId = DLMM.getBinIdFromPrice(
initPrice,
binStep,
!priceRoundingUp,
);
// console.log(`>>> Creating Meteora DLMM pool...`);
// console.log(`- Using tokenAMint: ${tokenAMint.toString()}`);
// console.log(`- Using tokenBMint: ${tokenBMint.toString()}`);
// console.log(`- Using binStep: ${binStep}`);
// console.log(`- Using initialPrice: ${initialPrice}`);
// console.log(`- Using priceRoundingUp: ${priceRoundingUp}`);
// console.log(`- Using feeBps ${feeBps}`);
// console.log(`- Using activationType: ${activationType}`);
// console.log(`- Using activationPoint: ${activationPoint?.toString()}`);
// console.log(`- Using hasAlphaVault: ${hasAlphaVault}`);
const initPoolTx = await DLMM.createCustomizablePermissionlessLbPair(
agent.connection,
new BN(binStep),
tokenAMint,
tokenBMint,
new BN(activateBinId.toString()),
new BN(feeBps),
activationType,
hasAlphaVault,
agent.wallet_address,
activationPoint,
{
cluster: "mainnet-beta",
},
);
initPoolTx.add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: computeUnitMicroLamports,
}),
);
const initPoolTxHash = await sendAndConfirmTransaction(
agent.connection,
initPoolTx,
[agent.wallet],
).catch((err) => {
console.error(err);
throw err;
});
// console.log(`<<< Finished creating Meteora DLMM pool.`);
return initPoolTxHash;
}

View File

@@ -0,0 +1,77 @@
import AmmImpl from "@mercurial-finance/dynamic-amm-sdk";
import { SolanaAgentKit } from "../agent";
import BN from "bn.js";
import {
ComputeBudgetProgram,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { CustomizableParams } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/types";
/**
* Create Meteora Dynamic AMM pool
* @param agent SolanaAgentKit instance
* @param tokenAMint Token A mint
* @param tokenBMint Token B mint
* @param tokenAAmount Token A amount in lamport units
* @param tokenBAmount Token B amount in lamport units
* @param customizableParams Parameters to create Dynamic AMM pool
* tradeFeeNumerator (number): Trade fee numerator, with default denominator is 100000
* activationType (enum): Should be ActivationType.Timestamp or ActivationType.Slot
* activationPoint (BN | null): Activation point depending on activation type, or null if pool doesn't have an activation point
* hasAlphaVault (boolean): Whether the pool has Meteora alpha vault or not
* padding (Array<number>): Should be set to value Array(90).fill(0)
* @returns Transaction signature
*/
export async function createMeteoraDynamicAMMPool(
agent: SolanaAgentKit,
tokenAMint: PublicKey,
tokenBMint: PublicKey,
tokenAAmount: BN,
tokenBAmount: BN,
customizableParams: CustomizableParams,
computeUnitMicroLamports: number,
): Promise<string> {
// console.log(`>>> Creating Meteora Dynamic Pool...`);
// console.log(`- Using tokenAMint: ${tokenAMint.toString()}`);
// console.log(`- Using tokenBMint: ${tokenBMint.toString()}`);
// console.log(`- Using tokenAAmount: ${tokenAAmount.toString()}`);
// console.log(`- Using tokenBAmount: ${tokenBAmount.toString()}`);
// console.log(
// `- Using tradeFeeNumerator ${customizableParams.tradeFeeNumerator}`,
// );
// console.log(`- Using activationType: ${customizableParams.activationType}`);
// console.log(
// `- Using activationPoint: ${customizableParams.activationPoint?.toString()}`,
// );
// console.log(`- Using hasAlphaVault: ${customizableParams.hasAlphaVault}`);
const initPoolTx =
await AmmImpl.createCustomizablePermissionlessConstantProductPool(
agent.connection,
agent.wallet_address,
tokenAMint,
tokenBMint,
tokenAAmount,
tokenBAmount,
customizableParams,
);
initPoolTx.add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: computeUnitMicroLamports,
}),
);
const initPoolTxHash = await sendAndConfirmTransaction(
agent.connection,
initPoolTx,
[agent.wallet],
).catch((err) => {
console.error(err);
throw err;
});
// console.log(`<<< Finished creating Meteora Dynamic Pool.`);
return initPoolTxHash;
}

View File

@@ -23,3 +23,5 @@ export * from "./3land";
export * from "./tiplink";
export * from "./lightprotocol";
export * from "./squads";
export * from "./create_meteora_dynamic_amm_pool";
export * from "./create_meteora_dlmm_pool";

View File

@@ -0,0 +1,59 @@
import { SolanaAgentKit, createSolanaTools } from "../../src";
import { deploy_token } from "../../src/tools";
const agent = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!,
process.env.RPC_URL!,
{ OPENAI_API_KEY: process.env.OPENAI_API_KEY! },
);
async function main() {
console.log("<<< Test Create Meteora DLMM pool");
const { mint: tokenAMint } = await deploy_token(
agent,
"token_a_mint",
"www.example.com",
"TOKEN_A",
6,
100_000,
);
const { mint: tokenBMint } = await deploy_token(
agent,
"token_b_mint",
"www.example.com",
"TOKEN_B",
6,
100_000,
);
// Delay for 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000));
const binStep = 20;
const initialPrice = 0.25;
const priceRoundingUp = true;
const feeBps = 20;
const activationType = 1; // timestamp
const hasAlphaVault = false;
const activationPoint = undefined;
const txHash = await agent.meteoraCreateDlmmPool(
tokenAMint,
tokenBMint,
binStep,
initialPrice,
priceRoundingUp,
feeBps,
activationType,
hasAlphaVault,
activationPoint,
);
console.log(`Tx successfully ${txHash.toString()}`);
console.log(">>> Test Create Meteora DLMM Pool Passed");
}
main();
export { SolanaAgentKit, createSolanaTools };

View File

@@ -0,0 +1,70 @@
import { SolanaAgentKit, createSolanaTools } from "../../src";
import { deploy_token } from "../../src/tools";
import BN from "bn.js";
import AmmImpl from "@mercurial-finance/dynamic-amm-sdk";
import { deriveCustomizablePermissionlessConstantProductPoolAddress } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/utils";
import { METEORA_DYNAMIC_AMM_PROGRAM_ID } from "../../src/constants";
const agent = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!,
process.env.RPC_URL!,
{ OPENAI_API_KEY: process.env.OPENAI_API_KEY! },
);
async function main() {
console.log("<<< Test Create Meteora Dynamic AMM pool");
const { mint: tokenAMint } = await deploy_token(
agent,
"token_a_mint",
"www.example.com",
"TOKEN_A",
6,
100_000,
);
const { mint: tokenBMint } = await deploy_token(
agent,
"token_b_mint",
"www.example.com",
"TOKEN_B",
6,
100_000,
);
// Delay for 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000));
const tokenAAmount = new BN(1000);
const tokenBAmount = new BN(5);
const params = {
tradeFeeNumerator: 250,
activationPoint: null,
hasAlphaVault: false,
activationType: 0,
};
const txHash = await agent.meteoraCreateDynamicPool(
tokenAMint,
tokenBMint,
tokenAAmount,
tokenBAmount,
params.tradeFeeNumerator,
params.activationPoint,
params.hasAlphaVault,
params.activationType,
);
console.log(`Tx successfully ${txHash.toString()}`);
const poolKey = deriveCustomizablePermissionlessConstantProductPoolAddress(
tokenAMint,
tokenBMint,
METEORA_DYNAMIC_AMM_PROGRAM_ID,
);
const pool = await AmmImpl.create(agent.connection, poolKey);
await pool.updateState();
console.log(">>> Test Create Meteora Dynamic AMM Pool Passed");
}
main();
export { SolanaAgentKit, createSolanaTools };