This commit is contained in:
calintje
2024-12-18 19:34:44 +01:00
parent f3b8d2bb4f
commit 2c3fb17e41

View File

@@ -2,11 +2,51 @@ import { Keypair, PublicKey } 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, PoolUtil, TokenExtensionContextForPool, NO_TOKEN_EXTENSION_CONTEXT, TokenExtensionUtil, WhirlpoolIx, IncreaseLiquidityQuoteParam, increaseLiquidityQuoteByInputTokenWithParams } from "@orca-so/whirlpools-sdk";
import { Percentage, resolveOrCreateATAs, TransactionBuilder } from "@orca-so/common-sdk";
import { increaseLiquidityIx, increaseLiquidityV2Ix, initTickArrayIx, openPositionWithTokenExtensionsIx } from "@orca-so/whirlpools-sdk/dist/instructions";
import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import {
PDAUtil,
ORCA_WHIRLPOOL_PROGRAM_ID,
ORCA_WHIRLPOOLS_CONFIG,
WhirlpoolContext,
TickUtil,
PriceMath,
PoolUtil,
TokenExtensionContextForPool,
NO_TOKEN_EXTENSION_CONTEXT,
TokenExtensionUtil,
WhirlpoolIx,
IncreaseLiquidityQuoteParam,
increaseLiquidityQuoteByInputTokenWithParams,
} from "@orca-so/whirlpools-sdk";
import {
Percentage,
resolveOrCreateATAs,
TransactionBuilder,
} from "@orca-so/common-sdk";
import {
increaseLiquidityIx,
increaseLiquidityV2Ix,
initTickArrayIx,
openPositionWithTokenExtensionsIx,
} from "@orca-so/whirlpools-sdk/dist/instructions";
import {
getAssociatedTokenAddressSync,
TOKEN_2022_PROGRAM_ID,
} from "@solana/spl-token";
/**
* Maps fee tier percentages to their corresponding tick spacing values in the Orca Whirlpool protocol.
*
* @remarks
* Fee tiers determine the percentage of fees collected on swaps, while tick spacing affects
* the granularity of price ranges for liquidity positions.
*
* For more details, refer to:
* - [Whirlpool Fees](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Fees)
* - [Whirlpool Parameters](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Parameters)
*
* @example
* const tickSpacing = FEE_TIERS[0.30]; // Returns 64
*/
const FEE_TIERS = {
0.01: 1,
0.02: 2,
@@ -19,8 +59,70 @@ const FEE_TIERS = {
2.00: 256,
} as const;
type FeeTierPercentage = keyof typeof FEE_TIERS;
/**
* # Creates a single-sided Whirlpool.
*
* This function initializes a new Whirlpool (liquidity pool) on Orca and seeds it with liquidity from a single token.
*
* ## Example Usage:
* You created a new token called SHARK, and you want to set the initial price to 0.001 USDC.
* You set `depositTokenMint` to SHARK's mint address and `otherTokenMint` to USDC's mint address.
* You can minimize price impact for buyers in a few ways:
* 1. Increase the amount of tokens you deposit
* 2. Set the initial price very low
* 3. Set the maximum price closer to the initial price
*
* ### Note for experts:
* The Wrhirlpool program initializes the Whirlpool with the in a specific order. This might not be
* the order you expect, so the function checks the order and adjusts the inverts the prices. This means that
* on-chain the Whirlpool might be configured as USDC/SHARK instead of SHARK/USDC, and the on-chain price will
* be 1/`initialPrice`. This will not affect the price of the token as you intended it to be.
*
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection details.
* @param depositTokenAmount - The amount of the deposit token (including the decimals) to contribute to the pool.
* @param depositTokenMint - The mint address of the token being deposited into the pool, eg. SHARK.
* @param otherTokenMint - The mint address of the other token in the pool, eg. USDC.
* @param initialPrice - The initial price of the deposit token in terms of the other token.
* @param maxPrice - The maximum price at which liquidity is added.
* @param feeTier - The fee tier percentage for the pool, determining tick spacing and fee collection rates.
*
* @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool.
*
* @throws Will throw an error if:
* - Mint accounts for the tokens cannot be fetched.
* - Prices are out of bounds.
*
* @remarks
* This function is designed for single-sided deposits where users only contribute one type of token,
* and the function manages mint order and necessary calculations.
*
* @example
* ```typescript
* import { SolanaAgentKit } from "your-sdk";
* import { PublicKey } from "@solana/web3.js";
* import { BN } from "@coral-xyz/anchor";
* import Decimal from "decimal.js";
*
* const agent = new SolanaAgentKit(wallet, connection);
* const depositAmount = new BN(1_000_000_000_000); // 1 million SHARK if SHARK has 6 decimals
* const depositTokenMint = new PublicKey("DEPOSTI_TOKEN_ADDRESS");
* const otherTokenMint = new PublicKey("OTHER_TOKEN_ADDRESS");
* const initialPrice = new Decimal(0.001);
* const maxPrice = new Decimal(5.0);
* const feeTier = 0.30;
*
* const txId = await createOrcaSingleSidedWhirlpool(
* agent,
* depositAmount,
* depositTokenMint,
* otherTokenMint,
* initialPrice,
* maxPrice,
* feeTier,
* );
* console.log(`Single sided whirlpool created in transaction: ${txId}`);
* ```
*/
export async function createOrcaSingleSidedWhirlpool(
agent: SolanaAgentKit,
depositTokenAmount: BN,
@@ -28,7 +130,7 @@ export async function createOrcaSingleSidedWhirlpool(
otherTokenMint: PublicKey,
initialPrice: Decimal,
maxPrice: Decimal,
feeTier: FeeTierPercentage,
feeTier: keyof typeof FEE_TIERS,
): Promise<string> {
const wallet = new Wallet(agent.wallet);
const ctx = WhirlpoolContext.from(agent.connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID);
@@ -52,7 +154,6 @@ export async function createOrcaSingleSidedWhirlpool(
const tickSpacing = FEE_TIERS[feeTier];
const tickIndex = PriceMath.priceToTickIndex(initialPrice, mintAAccount.decimals, mintBAccount.decimals);
const initialTick = TickUtil.getInitializableTickIndex(tickIndex, tickSpacing);
if (!TickUtil.checkTickInBounds(initialTick)) throw Error('Initial tick is out of bounds');
const tokenExtensionCtx: TokenExtensionContextForPool = {
...NO_TOKEN_EXTENSION_CONTEXT,
@@ -140,6 +241,7 @@ export async function createOrcaSingleSidedWhirlpool(
}
const tickLowerInitializableIndex = TickUtil.getInitializableTickIndex(tickLowerIndex, tickSpacing);
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(tickUpperIndex, tickSpacing);
if (!TickUtil.checkTickInBounds(tickLowerInitializableIndex) || !TickUtil.checkTickInBounds(tickUpperInitializableIndex)) throw Error('Prices out of bounds');
const increasLiquidityQuoteParam: IncreaseLiquidityQuoteParam = {
inputTokenAmount: BN(depositTokenAmount),
inputTokenMint: depositTokenMint,
@@ -156,7 +258,7 @@ export async function createOrcaSingleSidedWhirlpool(
increasLiquidityQuoteParam
)
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;
const positionMintKeypair = Keypair.generate();
const positionMintPubkey = positionMintKeypair.publicKey;
const positionPda = PDAUtil.getPosition(
@@ -186,7 +288,7 @@ export async function createOrcaSingleSidedWhirlpool(
txBuilder.addInstruction(positionIx);
txBuilder.addSigner(positionMintKeypair);
const [ataA, ataB] = await resolveOrCreateATAs(
ctx.connection,
wallet.publicKey,
@@ -224,7 +326,7 @@ export async function createOrcaSingleSidedWhirlpool(
whirlpoolPda.publicKey,
tickArrayUpperStartIndex,
);
if ( tickArrayUpperStartIndex !== tickArrayLowerStartIndex ) {
if (tickArrayUpperStartIndex !== tickArrayLowerStartIndex) {
if (isCorrectMintOrder) {
txBuilder.addInstruction(
initTickArrayIx(ctx.program, {