mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-21 23:26:45 +00:00
162 lines
5.9 KiB
TypeScript
162 lines
5.9 KiB
TypeScript
import {
|
|
Keypair,
|
|
PublicKey,
|
|
TransactionInstruction,
|
|
TransactionMessage,
|
|
VersionedTransaction,
|
|
} from "@solana/web3.js";
|
|
import { SolanaAgentKit } from "../agent";
|
|
import { Wallet } from "../utils/keypair";
|
|
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 Centered Liquidity Position in an Orca Whirlpool
|
|
*
|
|
* This function opens a centered liquidity position in a specified Orca Whirlpool. The user defines
|
|
* a basis point (bps) offset from the current price of the pool to set the lower and upper bounds of the position.
|
|
* The user also specifies the token mint and the amount to deposit. 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.
|
|
* - `priceOffsetBps`: The basis point (bps) offset (on one side) from the current price fo the pool. For example,
|
|
* 500 bps (5%) creates a range from 95% to 105% of the current pool price.
|
|
* - `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 `priceOffsetBps` specifies the range symmetrically around the current price.
|
|
* - The specified `inputTokenMint` determines which token is deposited directly. The function calculates
|
|
* the required amount of the other token based on the specified price range.
|
|
* - This function supports Orca's token extensions for managing tokens with special behaviors.
|
|
* - The function assumes a maximum slippage of 1% for liquidity provision.
|
|
*
|
|
* ## Throws
|
|
* An error will be thrown if:
|
|
* - The specified Whirlpool address is invalid or inaccessible.
|
|
* - The transaction fails to send.
|
|
* - Any required mint information cannot be fetched.
|
|
*
|
|
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection.
|
|
* @param whirlpoolAddress - The address of the Orca Whirlpool.
|
|
* @param priceOffsetBps - The basis point offset (one side) from the current pool price.
|
|
* @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 orcaOpenCenteredPositionWithLiquidity(
|
|
agent: SolanaAgentKit,
|
|
whirlpoolAddress: PublicKey,
|
|
priceOffsetBps: 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,
|
|
);
|
|
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 lowerPrice = price.mul(1 - priceOffsetBps / 10000);
|
|
const upperPrice = price.mul(1 + priceOffsetBps / 10000);
|
|
const lowerTick = PriceMath.priceToInitializableTickIndex(
|
|
lowerPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals,
|
|
whirlpoolData.tickSpacing,
|
|
);
|
|
const upperTick = PriceMath.priceToInitializableTickIndex(
|
|
upperPrice,
|
|
mintInfoA.decimals,
|
|
mintInfoB.decimals,
|
|
whirlpoolData.tickSpacing,
|
|
);
|
|
|
|
const txBuilderTickArrays = await whirlpool.initTickArrayForTicks([
|
|
lowerTick,
|
|
upperTick,
|
|
]);
|
|
let instructions: TransactionInstruction[] = [];
|
|
let signers: Keypair[] = [];
|
|
if (txBuilderTickArrays !== null) {
|
|
const txPayloadTickArrays = await txBuilderTickArrays.build();
|
|
const txPayloadTickArraysDecompiled = TransactionMessage.decompile(
|
|
(txPayloadTickArrays.transaction as VersionedTransaction).message,
|
|
);
|
|
const instructionsTickArrays = txPayloadTickArraysDecompiled.instructions;
|
|
instructions = instructions.concat(instructionsTickArrays);
|
|
signers = signers.concat(txPayloadTickArrays.signers as Keypair[]);
|
|
}
|
|
|
|
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,
|
|
);
|
|
instructions = instructions.concat(txPayloadDecompiled.instructions);
|
|
signers = signers.concat(txPayload.signers as Keypair[]);
|
|
|
|
const txId = await sendTx(agent, instructions, signers);
|
|
return JSON.stringify({
|
|
transactionId: txId,
|
|
positionMint: positionMint.toString(),
|
|
});
|
|
} catch (error) {
|
|
throw new Error(`${error}`);
|
|
}
|
|
}
|