mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-13 23:16:55 +00:00
feat: Create Meteora pools (#203)
# Pull Request Description This PR helps to create Meteora Dynamic and DLMM pool with prompt. ## Related Issue Fixes # (issue number) ## Changes Made This PR adds the following changes: <!-- List the key changes made in this PR --> - - ## Implementation Details <!-- Provide technical details about the implementation --> - The agent kit uses the functions from the `dynamic-amm-sdk` and the `dlmm-sdk` to create Meteora pools. - Both the tools to create pools have the parameter `computeUnitMicroLamports` to allow user inputs the priority fee so that the transaction can be landed faster. ## Transaction executed by agent <!-- If applicable, provide example usage, transactions, or screenshots --> Example transaction: - Create Meteora Dynamic pool: https://solscan.io/tx/3g1NEaiLAmrdewxTMP861MzZiubGs1K52PuADQpngX7frpABugUv1UfdKsyqFtvBjGcHqFW9wASMGtL8itADHujG - Create Meteora DLMM pool: https://solscan.io/tx/2zA6vzGAZZy3yQR9ZRNYw478gKtYLQpguiFAvbsedE7Sii65AVpoXM1YqQe3x1KsRxfqkqhZBdDaHpQ9hoq2hrZF ## Prompt Used <!-- If relevant, include the prompt or configuration used --> - Prompt to create Meteora Dynamic pool: ``` create meteora dynamic pool ``` - Prompt to input Dynamic pool params: ``` tokenAMint DVMkqJAx16dHV29RtHrFLe7VzbWCG64KCFfzgWQYvbVn tokenBMint So11111111111111111111111111111111111111112 tokenAAmount 100 tokenBAmount 0.01 tradeFeeNumerator 2500 ``` - Prompt to create Meteora DLMM pool: ``` create meteora dlmm pool ``` - Prompt to input DLMM pool params: ``` tokenAMint DVMkqJAx16dHV29RtHrFLe7VzbWCG64KCFfzgWQYvbVn tokenBMint So11111111111111111111111111111111111111112 binStep 20 initialPrice 0.25 feeBps 20 ``` ## Additional Notes <!-- Any additional information that reviewers should know --> ## Checklist - [x] I have tested these changes locally - [ ] I have updated the documentation - [x] I have added a transaction link - [x] I have added the prompt used to test it
This commit is contained in:
123
examples/discord-bot-starter/.eslintrc.ts
Normal file
123
examples/discord-bot-starter/.eslintrc.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
root: true,
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {},
|
||||
},
|
||||
},
|
||||
ignorePatterns: [
|
||||
'.eslintrc.js',
|
||||
'webpack.config.js',
|
||||
'dist/*',
|
||||
'**/*.js',
|
||||
'node_modules/*',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: '.',
|
||||
sourceType: 'module',
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:sonarjs/recommended',
|
||||
'plugin:security/recommended',
|
||||
'plugin:promise/recommended',
|
||||
'prettier',
|
||||
],
|
||||
plugins: [
|
||||
'@typescript-eslint/eslint-plugin',
|
||||
'sonarjs',
|
||||
'security',
|
||||
'promise',
|
||||
'prettier',
|
||||
],
|
||||
rules: {
|
||||
semi: [2, 'always'],
|
||||
quotes: [1, 'single', { allowTemplateLiterals: true }],
|
||||
curly: [2, 'all'],
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
{ checksVoidReturn: false },
|
||||
],
|
||||
'security/detect-non-literal-regexp': 0,
|
||||
'security/detect-object-injection': 0,
|
||||
'promise/always-return': 0,
|
||||
'promise/no-callback-in-promise': 0,
|
||||
'sonarjs/cognitive-complexity': [2, 50],
|
||||
'sonarjs/no-duplicate-string': 0,
|
||||
'sonarjs/no-useless-catch': 1,
|
||||
'sonarjs/no-nested-template-literals': 0,
|
||||
'sonarjs/prefer-single-boolean-return': 1,
|
||||
'sonarjs/no-small-switch': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
1,
|
||||
{ argsIgnorePattern: '^_|^returns$|^of$|^type$' },
|
||||
],
|
||||
'import/extensions': 'off',
|
||||
'import/no-import-module-exports': 'off',
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'import/no-dynamic-require': 'off',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
useTabs: false,
|
||||
arrowParens: 'always',
|
||||
printWidth: 80,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
endOfLine: 'auto',
|
||||
bracketSpacing: true,
|
||||
},
|
||||
{
|
||||
usePrettierrc: false,
|
||||
},
|
||||
],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: ['**/dist/**'],
|
||||
},
|
||||
],
|
||||
'no-use-before-define': 'off',
|
||||
'no-console': 'off',
|
||||
'no-return-await': 'off',
|
||||
'consistent-return': 'off',
|
||||
'default-case': 'off',
|
||||
'no-fallthrough': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'no-continue': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'no-void': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'no-return-assign': 'off',
|
||||
'no-case-declarations': 'off',
|
||||
'global-require': 'off',
|
||||
'security/detect-non-literal-require': 'off',
|
||||
'global-require': 'off',
|
||||
},
|
||||
};
|
||||
@@ -42,6 +42,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",
|
||||
|
||||
509
pnpm-lock.yaml
generated
509
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,8 @@ import {
|
||||
fetchPythPriceFeedID,
|
||||
flashOpenTrade,
|
||||
flashCloseTrade,
|
||||
createMeteoraDynamicAMMPool,
|
||||
createMeteoraDlmmPool,
|
||||
createCollection,
|
||||
createSingle,
|
||||
multisig_transfer_from_treasury,
|
||||
@@ -397,6 +399,57 @@ export class SolanaAgentKit {
|
||||
);
|
||||
}
|
||||
|
||||
async meteoraCreateDynamicPool(
|
||||
tokenAMint: PublicKey,
|
||||
tokenBMint: PublicKey,
|
||||
tokenAAmount: BN,
|
||||
tokenBAmount: BN,
|
||||
tradeFeeNumerator: number,
|
||||
activationPoint: BN | null,
|
||||
hasAlphaVault: boolean,
|
||||
activationType: number,
|
||||
): Promise<string> {
|
||||
return createMeteoraDynamicAMMPool(
|
||||
this,
|
||||
tokenAMint,
|
||||
tokenBMint,
|
||||
tokenAAmount,
|
||||
tokenBAmount,
|
||||
{
|
||||
tradeFeeNumerator,
|
||||
activationPoint,
|
||||
hasAlphaVault,
|
||||
activationType,
|
||||
padding: new Array(90).fill(0),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async meteoraCreateDlmmPool(
|
||||
tokenAMint: PublicKey,
|
||||
tokenBMint: PublicKey,
|
||||
binStep: number,
|
||||
initialPrice: number,
|
||||
priceRoundingUp: boolean,
|
||||
feeBps: number,
|
||||
activationType: number,
|
||||
hasAlphaVault: boolean,
|
||||
activationPoint: BN | undefined,
|
||||
): Promise<string> {
|
||||
return createMeteoraDlmmPool(
|
||||
this,
|
||||
binStep,
|
||||
tokenAMint,
|
||||
tokenBMint,
|
||||
initialPrice,
|
||||
priceRoundingUp,
|
||||
feeBps,
|
||||
activationType,
|
||||
hasAlphaVault,
|
||||
activationPoint,
|
||||
);
|
||||
}
|
||||
|
||||
async orcaClosePosition(positionMintAddress: PublicKey) {
|
||||
return orcaClosePosition(this, positionMintAddress);
|
||||
}
|
||||
@@ -656,7 +709,7 @@ export class SolanaAgentKit {
|
||||
collectionOpts: CreateCollectionOptions,
|
||||
isDevnet: boolean = false,
|
||||
): Promise<string> {
|
||||
let optionsWithBase58: StoreInitOptions = {
|
||||
const optionsWithBase58: StoreInitOptions = {
|
||||
privateKey: this.wallet.secretKey,
|
||||
};
|
||||
if (isDevnet) {
|
||||
@@ -675,7 +728,7 @@ export class SolanaAgentKit {
|
||||
isDevnet: boolean = false,
|
||||
withPool: boolean = false,
|
||||
): Promise<string> {
|
||||
let optionsWithBase58: StoreInitOptions = {
|
||||
const optionsWithBase58: StoreInitOptions = {
|
||||
privateKey: this.wallet.secretKey,
|
||||
};
|
||||
if (isDevnet) {
|
||||
@@ -782,7 +835,7 @@ export class SolanaAgentKit {
|
||||
}) {
|
||||
return await createVault(this, params);
|
||||
}
|
||||
|
||||
|
||||
async depositIntoDriftVault(amount: number, vault: string) {
|
||||
return await depositIntoVault(this, amount, vault);
|
||||
}
|
||||
@@ -924,6 +977,7 @@ export class SolanaAgentKit {
|
||||
}
|
||||
async getLendAndBorrowAPY(symbol: string) {
|
||||
return getLendingAndBorrowAPY(this, symbol);
|
||||
}
|
||||
|
||||
async voltrDepositStrategy(
|
||||
depositAmount: BN,
|
||||
|
||||
@@ -27,6 +27,8 @@ 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
|
||||
*/
|
||||
@@ -34,6 +36,12 @@ 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",
|
||||
);
|
||||
/**
|
||||
* Minimum compute price required to carry out complex transactions on the Drift protocol
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ export * from "./tiplink";
|
||||
export * from "./sns";
|
||||
export * from "./lightprotocol";
|
||||
export * from "./squads";
|
||||
export * from "./meteora";
|
||||
export * from "./helius";
|
||||
export * from "./drift";
|
||||
export * from "./voltr";
|
||||
@@ -96,6 +97,8 @@ import {
|
||||
SolanaApproveProposal2by2Multisig,
|
||||
SolanaExecuteProposal2by2Multisig,
|
||||
SolanaRejectProposal2by2Multisig,
|
||||
SolanaMeteoraCreateDynamicPool,
|
||||
SolanaMeteoraCreateDlmmPool,
|
||||
SolanaSendTransactionWithPriorityFee,
|
||||
SolanaHeliusWebhookTool,
|
||||
SolanaGetHeliusWebhookTool,
|
||||
@@ -163,6 +166,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),
|
||||
|
||||
@@ -2,36 +2,36 @@ import { Tool } from "langchain/tools";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
|
||||
export class SolanaLuloLendTool extends Tool {
|
||||
name = "solana_lulo_lend";
|
||||
description = `Lend token for yield using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP)
|
||||
name = "solana_lulo_lend";
|
||||
description = `Lend token for yield using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP)
|
||||
Inputs:
|
||||
mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)
|
||||
amount: number, eg 1, 0.01 (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
const mintAddress = parsedInput.mintAddress;
|
||||
const amount = parsedInput.amount;
|
||||
|
||||
const tx = await this.solanaKit.luloLend(mintAddress, amount);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Asset lent successfully",
|
||||
transaction: tx,
|
||||
amount,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
const mintAddress = parsedInput.mintAddress
|
||||
const amount = parsedInput.amount;
|
||||
|
||||
const tx = await this.solanaKit.luloLend(mintAddress, amount);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Asset lent successfully",
|
||||
transaction: tx,
|
||||
amount,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,36 +2,36 @@ import { Tool } from "langchain/tools";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
|
||||
export class SolanaLuloWithdrawTool extends Tool {
|
||||
name = "solana_lulo_withdraw";
|
||||
description = `Withdraw token USDC using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP)
|
||||
name = "solana_lulo_withdraw";
|
||||
description = `Withdraw token USDC using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP)
|
||||
Inputs (input is a json string):
|
||||
mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)
|
||||
amount: number, eg 1, 0.01 (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
const mintAddress = parsedInput.mintAddress;
|
||||
const amount = parsedInput.amount;
|
||||
|
||||
const tx = await this.solanaKit.luloWithdraw(mintAddress, amount);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Asset withdraw successfully",
|
||||
transaction: tx,
|
||||
amount,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
const mintAddress = parsedInput.mintAddress
|
||||
const amount = parsedInput.amount;
|
||||
|
||||
const tx = await this.solanaKit.luloWithdraw(mintAddress, amount);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Asset withdraw successfully",
|
||||
transaction: tx,
|
||||
amount,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
src/langchain/meteora/index.ts
Normal file
2
src/langchain/meteora/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./meteora_dlmm_pool";
|
||||
export * from "./meteora_dynamic_pool";
|
||||
78
src/langchain/meteora/meteora_dlmm_pool.ts
Normal file
78
src/langchain/meteora/meteora_dlmm_pool.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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).
|
||||
`;
|
||||
|
||||
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;
|
||||
}
|
||||
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 txId = await this.solanaKit.meteoraCreateDlmmPool(
|
||||
tokenAMint,
|
||||
tokenBMint,
|
||||
binStep,
|
||||
initialPrice,
|
||||
priceRoundingUp,
|
||||
feeBps,
|
||||
activationType,
|
||||
hasAlphaVault,
|
||||
activationPoint,
|
||||
);
|
||||
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/langchain/meteora/meteora_dynamic_pool.ts
Normal file
111
src/langchain/meteora/meteora_dynamic_pool.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Tool } from "langchain/tools";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { BN } from "bn.js";
|
||||
import { MintLayout } from "@solana/spl-token";
|
||||
import Decimal from "decimal.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 not including decimals, e.g., 1 (required).
|
||||
- tokenBAmount: number, token B amount not including decimals, e.g., 0.2 (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).
|
||||
`;
|
||||
|
||||
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;
|
||||
}
|
||||
const inputFormat: CreateMeteoraDynamicAmmPoolInput = JSON.parse(input);
|
||||
|
||||
const tokenAMint = new PublicKey(inputFormat.tokenAMint);
|
||||
const tokenBMint = new PublicKey(inputFormat.tokenBMint);
|
||||
|
||||
const tokenAMintInfo =
|
||||
await this.solanaKit.connection.getAccountInfo(tokenAMint);
|
||||
const tokenBMintInfo =
|
||||
await this.solanaKit.connection.getAccountInfo(tokenBMint);
|
||||
|
||||
if (!tokenAMintInfo) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: "failed to fetch tokenAMint info",
|
||||
code: "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
if (!tokenBMintInfo) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: "failed to fetch tokenBMint info",
|
||||
code: "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
const tokenADecimals = MintLayout.decode(tokenAMintInfo.data).decimals;
|
||||
const tokenBDecimals = MintLayout.decode(tokenBMintInfo.data).decimals;
|
||||
|
||||
const tokenAAmount = new BN(
|
||||
new Decimal(inputFormat.tokenAAmount)
|
||||
.mul(10 ** tokenADecimals)
|
||||
.toString(),
|
||||
);
|
||||
const tokenBAmount = new BN(
|
||||
new Decimal(inputFormat.tokenBAmount)
|
||||
.mul(10 ** tokenBDecimals)
|
||||
.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 txId = await this.solanaKit.meteoraCreateDynamicPool(
|
||||
tokenAMint,
|
||||
tokenBMint,
|
||||
tokenAAmount,
|
||||
tokenBAmount,
|
||||
tradeFeeNumerator,
|
||||
activationPoint,
|
||||
hasAlphaVault,
|
||||
activationType,
|
||||
);
|
||||
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,6 @@ export * from "./3land";
|
||||
export * from "./tiplink";
|
||||
export * from "./lightprotocol";
|
||||
export * from "./squads";
|
||||
export * from "./meteora";
|
||||
export * from "./helius";
|
||||
export * from "./voltr";
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function luloLend(
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-wallet-pubkey": agent.wallet.publicKey.toBase58(),
|
||||
"x-api-key": process.env.FLEXLEND_API_KEY!
|
||||
"x-api-key": process.env.FLEXLEND_API_KEY!,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
owner: agent.wallet.publicKey.toBase58(),
|
||||
@@ -30,7 +30,9 @@ export async function luloLend(
|
||||
}),
|
||||
},
|
||||
);
|
||||
const { data: { transactionMeta } } = await response.json()
|
||||
const {
|
||||
data: { transactionMeta },
|
||||
} = await response.json();
|
||||
|
||||
// Deserialize the transaction
|
||||
const luloTxn = VersionedTransaction.deserialize(
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function luloWithdraw(
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-wallet-pubkey": agent.wallet.publicKey.toBase58(),
|
||||
"x-api-key": agent.config.FLEXLEND_API_KEY
|
||||
"x-api-key": agent.config.FLEXLEND_API_KEY,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
owner: agent.wallet.publicKey.toBase58(),
|
||||
@@ -35,7 +35,9 @@ export async function luloWithdraw(
|
||||
},
|
||||
);
|
||||
|
||||
const { data: { transactionMeta } } = await response.json()
|
||||
const {
|
||||
data: { transactionMeta },
|
||||
} = await response.json();
|
||||
|
||||
// Deserialize the transaction
|
||||
const luloTxn = VersionedTransaction.deserialize(
|
||||
|
||||
70
src/tools/meteora/create_meteora_dlmm_pool.ts
Normal file
70
src/tools/meteora/create_meteora_dlmm_pool.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import BN from "bn.js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import DLMM, { ActivationType } from "@meteora-ag/dlmm";
|
||||
import { getMint } from "@solana/spl-token";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
): 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,
|
||||
);
|
||||
|
||||
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",
|
||||
},
|
||||
);
|
||||
|
||||
const initPoolTxHash = await sendTx(agent, initPoolTx.instructions, [
|
||||
agent.wallet,
|
||||
]);
|
||||
|
||||
return initPoolTxHash;
|
||||
}
|
||||
47
src/tools/meteora/create_meteora_dynamic_amm_pool.ts
Normal file
47
src/tools/meteora/create_meteora_dynamic_amm_pool.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import AmmImpl from "@mercurial-finance/dynamic-amm-sdk";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import BN from "bn.js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { CustomizableParams } from "@mercurial-finance/dynamic-amm-sdk/dist/cjs/src/amm/types";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
): Promise<string> {
|
||||
const initPoolTx =
|
||||
await AmmImpl.createCustomizablePermissionlessConstantProductPool(
|
||||
agent.connection,
|
||||
agent.wallet_address,
|
||||
tokenAMint,
|
||||
tokenBMint,
|
||||
tokenAAmount,
|
||||
tokenBAmount,
|
||||
customizableParams,
|
||||
);
|
||||
|
||||
const initPoolTxHash = await sendTx(agent, initPoolTx.instructions, [
|
||||
agent.wallet,
|
||||
]);
|
||||
|
||||
return initPoolTxHash;
|
||||
}
|
||||
2
src/tools/meteora/index.ts
Normal file
2
src/tools/meteora/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./create_meteora_dlmm_pool";
|
||||
export * from "./create_meteora_dynamic_amm_pool";
|
||||
59
test/tools/create_meteora_dlmm_pool.ts
Normal file
59
test/tools/create_meteora_dlmm_pool.ts
Normal 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 };
|
||||
70
test/tools/create_meteora_dynamic_amm_pool.ts
Normal file
70
test/tools/create_meteora_dynamic_amm_pool.ts
Normal 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 * 10 ** 6);
|
||||
const tokenBAmount = new BN(5 * 10 ** 6);
|
||||
const params = {
|
||||
tradeFeeNumerator: 2500,
|
||||
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 };
|
||||
Reference in New Issue
Block a user