mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-17 23:26:46 +00:00
170 lines
6.4 KiB
TypeScript
170 lines
6.4 KiB
TypeScript
import { Keypair, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
|
|
import { SolanaAgentKit } from "../agent";
|
|
import { Wallet } from "@coral-xyz/anchor";
|
|
import { Decimal } from "decimal.js";
|
|
import {
|
|
ORCA_WHIRLPOOL_PROGRAM_ID,
|
|
WhirlpoolContext,
|
|
PriceMath,
|
|
buildWhirlpoolClient,
|
|
increaseLiquidityQuoteByInputToken,
|
|
TokenExtensionContextForPool,
|
|
NO_TOKEN_EXTENSION_CONTEXT,
|
|
} from "@orca-so/whirlpools-sdk";
|
|
import { sendTx } from "../utils/send_tx";
|
|
import { Percentage } from "@orca-so/common-sdk";
|
|
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
|
|
|
/**
|
|
* # Opens a Single-Sided Liquidity Position in an Orca Whirlpool
|
|
*
|
|
* This function opens a single-sided liquidity position in a specified Orca Whirlpool. The user specifies
|
|
* a basis point (bps) offset from the current price for the lower bound and a width (bps) for the range width.
|
|
* The required amount of the other token is calculated automatically.
|
|
*
|
|
* ## Parameters
|
|
* - `agent`: The `SolanaAgentKit` instance representing the wallet and connection details.
|
|
* - `whirlpoolAddress`: The address of the Orca Whirlpool where the position will be opened.
|
|
* - `distanceFromCurrentPriceBps`: The basis point offset from the current price for the lower bound.
|
|
* - `widthBps`: The width of the range as a percentage increment from the lower bound.
|
|
* - `inputTokenMint`: The mint address of the token being deposited (e.g., USDC or another token).
|
|
* - `inputAmount`: The amount of the input token to deposit, specified as a `Decimal` value.
|
|
*
|
|
* ## Returns
|
|
* A `Promise` that resolves to the transaction ID (`string`) of the transaction that opens the position.
|
|
*
|
|
* ## Notes
|
|
* - The `distanceFromCurrentPriceBps` specifies the starting point of the range.
|
|
* - The `widthBps` determines the range size from the lower bound.
|
|
* - The specified `inputTokenMint` determines which token is deposited directly.
|
|
*
|
|
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection.
|
|
* @param whirlpoolAddress - The address of the Orca Whirlpool.
|
|
* @param distanceFromCurrentPriceBps - The basis point offset from the current price for the lower bound.
|
|
* @param widthBps - The width of the range as a percentage increment from the lower bound.
|
|
* @param inputTokenMint - The mint address of the token to deposit.
|
|
* @param inputAmount - The amount of the input token to deposit.
|
|
* @returns A promise resolving to the transaction ID (`string`).
|
|
*/
|
|
export async function orcaOpenSingleSidedPosition(
|
|
agent: SolanaAgentKit,
|
|
whirlpoolAddress: PublicKey,
|
|
distanceFromCurrentPriceBps: number,
|
|
widthBps: number,
|
|
inputTokenMint: PublicKey,
|
|
inputAmount: Decimal
|
|
): Promise<string> {
|
|
try {
|
|
const wallet = new Wallet(agent.wallet);
|
|
const ctx = WhirlpoolContext.from(
|
|
agent.connection,
|
|
wallet,
|
|
ORCA_WHIRLPOOL_PROGRAM_ID,
|
|
);
|
|
// ctx.accountResolverOpts.createWrappedSolAccountMethod = "ata";
|
|
const client = buildWhirlpoolClient(ctx);
|
|
|
|
const whirlpool = await client.getPool(whirlpoolAddress);
|
|
const whirlpoolData = whirlpool.getData();
|
|
const mintInfoA = whirlpool.getTokenAInfo();
|
|
const mintInfoB = whirlpool.getTokenBInfo();
|
|
const price = PriceMath.sqrtPriceX64ToPrice(
|
|
whirlpoolData.sqrtPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals
|
|
);
|
|
|
|
const isTokenA = inputTokenMint.equals(mintInfoA.mint);
|
|
let lowerBoundPrice;
|
|
let upperBoundPrice;
|
|
let lowerTick;
|
|
let upperTick;
|
|
if (isTokenA) {
|
|
lowerBoundPrice = price.mul(1 + distanceFromCurrentPriceBps / 10000);
|
|
upperBoundPrice = lowerBoundPrice.mul(1 + widthBps / 10000);
|
|
upperTick = PriceMath.priceToInitializableTickIndex(
|
|
upperBoundPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals,
|
|
whirlpoolData.tickSpacing
|
|
);
|
|
lowerTick = PriceMath.priceToInitializableTickIndex(
|
|
lowerBoundPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals,
|
|
whirlpoolData.tickSpacing
|
|
);
|
|
} else {
|
|
lowerBoundPrice = price.mul(1 - distanceFromCurrentPriceBps / 10000);
|
|
upperBoundPrice = lowerBoundPrice.mul(1 - widthBps / 10000);
|
|
lowerTick = PriceMath.priceToInitializableTickIndex(
|
|
upperBoundPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals,
|
|
whirlpoolData.tickSpacing
|
|
);
|
|
upperTick = PriceMath.priceToInitializableTickIndex(
|
|
lowerBoundPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals,
|
|
whirlpoolData.tickSpacing
|
|
);
|
|
}
|
|
|
|
const txBuilderTickArrays = await whirlpool.initTickArrayForTicks([lowerTick, upperTick]);
|
|
let txIds: string = '';
|
|
if (txBuilderTickArrays !== null) {
|
|
const txPayloadTickArrays = await txBuilderTickArrays.build();
|
|
const txPayloadTickArraysDecompiled = TransactionMessage.decompile((txPayloadTickArrays.transaction as VersionedTransaction).message);
|
|
const instructions = txPayloadTickArraysDecompiled.instructions;
|
|
const signers = txPayloadTickArrays.signers as Keypair[];
|
|
|
|
const tickArrayTxId = await sendTx(agent, instructions, signers);
|
|
txIds += tickArrayTxId + ',';
|
|
}
|
|
|
|
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
|
...NO_TOKEN_EXTENSION_CONTEXT,
|
|
tokenMintWithProgramA: mintInfoA,
|
|
tokenMintWithProgramB: mintInfoB,
|
|
};
|
|
const increaseLiquiditQuote = increaseLiquidityQuoteByInputToken(
|
|
inputTokenMint,
|
|
inputAmount,
|
|
lowerTick,
|
|
upperTick,
|
|
Percentage.fromFraction(1, 100),
|
|
whirlpool,
|
|
tokenExtensionCtx
|
|
);
|
|
const { positionMint, tx: txBuilder } = await whirlpool.openPositionWithMetadata(
|
|
lowerTick,
|
|
upperTick,
|
|
increaseLiquiditQuote,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
TOKEN_2022_PROGRAM_ID
|
|
);
|
|
|
|
const txPayload = await txBuilder.build();
|
|
const txPayloadDecompiled = TransactionMessage.decompile((txPayload.transaction as VersionedTransaction).message);
|
|
const instructions = txPayloadDecompiled.instructions;
|
|
const signers = txPayload.signers as Keypair[];
|
|
for (const signer of signers) {
|
|
console.log(signer.publicKey.toBase58());
|
|
}
|
|
|
|
const positionTxId = await sendTx(agent, instructions, signers);
|
|
txIds += positionTxId;
|
|
|
|
return JSON.stringify({
|
|
transactionIds: txIds,
|
|
positionMint: positionMint.toString(),
|
|
});
|
|
} catch (error) {
|
|
console.log(error);
|
|
throw new Error(`${error}`);
|
|
}
|
|
}
|