Add compatibility with devnet. Update pool creation to support SOL wrapping. Update send tx function.

This commit is contained in:
calintje
2024-12-25 13:51:56 +01:00
parent daec25f7cc
commit ab7d03b2af
4 changed files with 122 additions and 102 deletions

View File

@@ -751,15 +751,15 @@ export class SolanaCompressedAirdropTool extends Tool {
export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
name = "create_orca_single_sided_whirlpool";
description = `Create a single-sided Whirlpool with liquidity.
description = `Create a single-sided liquidity pools with liquidity on Orca.
Inputs (input is a JSON string):
- depositTokenAmount: number, eg: 1000000000 (required, in units of deposit token including decimals)
- depositTokenMint: string, eg: "DepositTokenMintAddress" (required, mint address of deposit token)
- otherTokenMint: string, eg: "OtherTokenMintAddress" (required, mint address of other token)
- initialPrice: number, eg: 0.001 (required, initial price of deposit token in terms of other token)
- maxPrice: number, eg: 5.0 (required, maximum price at which liquidity is added)
- feeTier: number, eg: 0.30 (required, fee tier for the pool)`;
- depositTokenAmount: number, in units of deposit token including decimals, eg: 1000000000 (required)
- depositTokenMint: string, mint address of deposit token, eg: "DepositTokenMintAddress" (required)
- otherTokenMint: string, mint address of other token, eg: "OtherTokenMintAddress" (required)
- initialPrice: number, initial price of deposit token in terms of other token, eg: 0.001, (required)
- maxPrice: number, maximum price at which liquidity is added, eg: 5.0 (required)
- feeTier: number, fee tier for the pool in %. Possible values on mainnet are: 0.01, 0.02, 0.04, 0.05, 0.16, 0.30, 0.65, 1.0, 2.0 (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
@@ -803,7 +803,6 @@ export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
}
}
export class SolanaRaydiumCreateAmmV4 extends Tool {
name = "raydium_create_ammV4";
description = `Raydium's Legacy AMM that requiers an OpenBook marketID

View File

@@ -1,11 +1,10 @@
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
import { Keypair, PublicKey, Transaction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
import { SolanaAgentKit } from "../agent";
import { BN, Wallet } from "@coral-xyz/anchor";
import { Decimal } from "decimal.js";
import {
PDAUtil,
ORCA_WHIRLPOOL_PROGRAM_ID,
ORCA_WHIRLPOOLS_CONFIG,
WhirlpoolContext,
TickUtil,
PriceMath,
@@ -110,7 +109,7 @@ export const FEE_TIERS = {
* const otherTokenMint = new PublicKey("OTHER_TOKEN_ADDRESS");
* const initialPrice = new Decimal(0.001);
* const maxPrice = new Decimal(5.0);
* const feeTier = 0.30;
* const feeTier = 0.02;
*
* const txId = await createOrcaSingleSidedWhirlpool(
* agent,
@@ -133,6 +132,14 @@ export async function createOrcaSingleSidedWhirlpool(
maxPrice: Decimal,
feeTier: keyof typeof FEE_TIERS,
): Promise<string> {
let whirlpoolsConfigAddress: PublicKey;
if (agent.connection.rpcEndpoint.includes('mainnet')) {
whirlpoolsConfigAddress = new PublicKey('2LecshUwdy9xi7meFgHtFJQNSKk4KdTrcpvaB56dP2NQ');
} else if (agent.connection.rpcEndpoint.includes('devnet')) {
whirlpoolsConfigAddress = new PublicKey('FcrweFY1G9HJAHG5inkGB6pKg1HZ6x9UC2WioAfWrGkR');
} else {
throw new Error('Unsupported network');
}
const wallet = new Wallet(agent.wallet);
const ctx = WhirlpoolContext.from(agent.connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID);
const fetcher = ctx.fetcher;
@@ -163,7 +170,7 @@ export async function createOrcaSingleSidedWhirlpool(
};
const feeTierKey = PDAUtil.getFeeTier(
ORCA_WHIRLPOOL_PROGRAM_ID,
ORCA_WHIRLPOOLS_CONFIG,
whirlpoolsConfigAddress,
tickSpacing,
).publicKey;
const initSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(initialTick);
@@ -171,24 +178,24 @@ export async function createOrcaSingleSidedWhirlpool(
const tokenVaultBKeypair = Keypair.generate();
const whirlpoolPda = PDAUtil.getWhirlpool(
ORCA_WHIRLPOOL_PROGRAM_ID,
ORCA_WHIRLPOOLS_CONFIG,
whirlpoolsConfigAddress,
mintA,
mintB,
FEE_TIERS[feeTier],
);
const tokenBadgeA = PDAUtil.getTokenBadge(
ORCA_WHIRLPOOL_PROGRAM_ID,
ORCA_WHIRLPOOLS_CONFIG,
whirlpoolsConfigAddress,
mintA,
).publicKey;
const tokenBadgeB = PDAUtil.getTokenBadge(
ORCA_WHIRLPOOL_PROGRAM_ID,
ORCA_WHIRLPOOLS_CONFIG,
whirlpoolsConfigAddress,
mintB,
).publicKey;
const baseParamsPool = {
initSqrtPrice,
whirlpoolsConfig: ORCA_WHIRLPOOLS_CONFIG,
whirlpoolsConfig: whirlpoolsConfigAddress,
whirlpoolPda,
tokenMintA: mintA,
tokenMintB: mintB,
@@ -301,7 +308,7 @@ export async function createOrcaSingleSidedWhirlpool(
wallet.publicKey,
undefined,
ctx.accountResolverOpts.allowPDAOwnerAddress,
ctx.accountResolverOpts.createWrappedSolAccountMethod,
"ata",
);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
@@ -378,22 +385,18 @@ export async function createOrcaSingleSidedWhirlpool(
});
txBuilder.addInstruction(liquidityIx);
const txPayload = await txBuilder.build({
maxSupportedTransactionVersion: "legacy"
});
const txPayload = await txBuilder.build();
const instructions = TransactionMessage.decompile(
(txPayload.transaction as VersionedTransaction).message).instructions
if (txPayload.transaction instanceof Transaction) {
try {
const txId = await sendTx(
agent,
txPayload.transaction,
[positionMintKeypair, tokenVaultAKeypair, tokenVaultBKeypair],
);
return txId;
} catch (error) {
throw new Error(`Failed to create pool: ${JSON.stringify(error)}`);
}
} else {
throw new Error('Failed to create pool: Transaction not created');
try {
const txId = await sendTx(
agent,
instructions,
[positionMintKeypair, tokenVaultAKeypair, tokenVaultBKeypair],
);
return txId;
} catch (error) {
throw new Error(`Failed to create pool: ${JSON.stringify(error)}`);
}
}

View File

@@ -1,68 +1,60 @@
import { SolanaAgentKit } from "../agent";
import { Transaction, Keypair, TransactionInstruction } from "@solana/web3.js";
import { Connection, ComputeBudgetProgram } from "@solana/web3.js";
import { Keypair, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
import { Connection, ComputeBudgetProgram, } from "@solana/web3.js";
const feeTiers = {
min: 1,
mid: 0.5,
max: 0.95
}
/**
* Get priority fees for the current block
* @param connection - Solana RPC connection
* @returns Priority fees statistics and instructions for different fee levels
*/
export async function getPriorityFees(connection: Connection): Promise<{
min: number;
median: number;
max: number;
instructions?: {
low: TransactionInstruction;
medium: TransactionInstruction;
high: TransactionInstruction;
};
}> {
export async function getComputeBudgetInstructions(agent: SolanaAgentKit, instructions: TransactionInstruction[], feeTier: keyof typeof feeTiers): Promise<{
blockhash: string;
computeBudgetLimitInstruction: TransactionInstruction;
computeBudgetPriorityFeeInstructions: TransactionInstruction;
} > {
try {
// Get recent prioritization fees
const priorityFees = await connection.getRecentPrioritizationFees();
const blockhash = (await agent.connection.getLatestBlockhash()).blockhash;
const messageV0 = new TransactionMessage({
payerKey: agent.wallet_address,
recentBlockhash: blockhash,
instructions: instructions,
}).compileToV0Message();
const transaction = new VersionedTransaction(messageV0);
const simulatedTx = agent.connection.simulateTransaction(transaction);
const estimatedComputeUnits = (await simulatedTx).value.unitsConsumed;
const safeComputeUnits = Math.ceil(
estimatedComputeUnits ?
Math.max(estimatedComputeUnits + 100000, estimatedComputeUnits * 1.2)
: 200000
);
const computeBudgetLimitInstruction = ComputeBudgetProgram.setComputeUnitLimit({
units: safeComputeUnits,
});
if (!priorityFees.length) {
return {
min: 0,
median: 0,
max: 0,
};
}
const priorityFee = await agent.connection.getRecentPrioritizationFees()
.then(fees =>
fees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)
[Math.floor(fees.length * feeTiers[feeTier])].prioritizationFee
);
// Sort fees by value
const sortedFees = priorityFees
.map((x) => x.prioritizationFee)
.sort((a, b) => a - b);
// Calculate statistics
const min = sortedFees[0] ?? 0;
const max = sortedFees[sortedFees.length - 1] ?? 0;
const mid = Math.floor(sortedFees.length / 2);
const median =
sortedFees.length % 2 === 0
? ((sortedFees[mid - 1] ?? 0) + (sortedFees[mid] ?? 0)) / 2
: sortedFees[mid] ?? 0;
// Helper to create priority fee IX based on chosen strategy
const createPriorityFeeIx = (fee: number) => {
return ComputeBudgetProgram.setComputeUnitPrice({
microLamports: fee,
});
};
const computeBudgetPriorityFeeInstructions = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFee,
});
return {
min,
median,
max,
// Return instructions for different fee levels
instructions: {
low: createPriorityFeeIx(min),
medium: createPriorityFeeIx(median),
high: createPriorityFeeIx(max),
},
blockhash,
computeBudgetLimitInstruction,
computeBudgetPriorityFeeInstructions
};
} catch (error) {
console.error("Error getting priority fees:", error);
console.error("Error getting compute budget instructions fees:", error);
throw error;
}
}
@@ -75,24 +67,50 @@ export async function getPriorityFees(connection: Connection): Promise<{
*/
export async function sendTx(
agent: SolanaAgentKit,
tx: Transaction,
instructions: TransactionInstruction[],
otherKeypairs?: Keypair[]
) {
tx.recentBlockhash = (await agent.connection.getLatestBlockhash()).blockhash;
tx.feePayer = agent.wallet_address;
const fees = await getPriorityFees(agent.connection);
if (fees.instructions) {
tx.add(fees.instructions.medium!);
}
console.log(instructions)
tx.sign(agent.wallet, ...(otherKeypairs ?? []));
let txid = await agent.connection.sendRawTransaction(tx.serialize());
await agent.connection.confirmTransaction({
signature: txid,
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
lastValidBlockHeight: (
await agent.connection.getLatestBlockhash()
).lastValidBlockHeight,
});
return txid;
const ixComputeBudget = await getComputeBudgetInstructions(agent, instructions, "mid");
const allInstructions = [
ixComputeBudget.computeBudgetLimitInstruction,
ixComputeBudget.computeBudgetPriorityFeeInstructions,
...instructions];
const messageV0 = new TransactionMessage({
payerKey: agent.wallet_address,
recentBlockhash: ixComputeBudget.blockhash,
instructions: allInstructions,
}).compileToV0Message();
const transaction = new VersionedTransaction(messageV0);
transaction.sign([agent.wallet, ...(otherKeypairs ?? [])]);
const timeoutMs = 90000;
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
const transactionStartTime = Date.now();
const signature = await agent.connection.sendTransaction(
transaction,
{
maxRetries: 0,
skipPreflight: true,
});
const statuses = await agent.connection.getSignatureStatuses([signature]);
if (statuses.value[0]) {
if (!statuses.value[0].err) {
return signature;
} else {
throw new Error(`Transaction failed: ${statuses.value[0].err.toString()}`);
}
}
const elapsedTime = Date.now() - transactionStartTime;
const remainingTime = Math.max(0, 1000 - elapsedTime);
if (remainingTime > 0) {
await new Promise(resolve => setTimeout(resolve, remainingTime));
}
}
throw new Error("Transaction timeout");
}

View File

@@ -36,7 +36,7 @@ const WALLET_DATA_FILE = "wallet_data.txt";
async function initializeAgent() {
try {
const llm = new ChatOpenAI({
modelName: "gpt-4o-mini",
modelName: "gpt-4o",
temperature: 0.7,
});