From 0c8f482897b98a54c79b97e25b4d6e3554fc748b Mon Sep 17 00:00:00 2001 From: Will <82029448+wjthieme@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:13:25 -0400 Subject: [PATCH] Create a helper function for creating splash pools (#198) * Create a helper function for creating splash pools * Version bump * Tweak * Tweaks * Tweak * Comment * Update whirlpool-client-impl.ts --- legacy-sdk/whirlpool/package.json | 2 +- .../src/impl/whirlpool-client-impl.ts | 125 ++++++- .../whirlpool/src/types/public/constants.ts | 7 + legacy-sdk/whirlpool/src/whirlpool-client.ts | 19 + .../whirlpools/whirlpool-client-impl.test.ts | 349 +++++++++++++++++- 5 files changed, 493 insertions(+), 9 deletions(-) diff --git a/legacy-sdk/whirlpool/package.json b/legacy-sdk/whirlpool/package.json index 86ac1ee7b..bb8beea38 100644 --- a/legacy-sdk/whirlpool/package.json +++ b/legacy-sdk/whirlpool/package.json @@ -1,6 +1,6 @@ { "name": "@orca-so/whirlpools-sdk", - "version": "0.13.3", + "version": "0.13.4", "description": "Typescript SDK to interact with Orca's Whirlpool program.", "license": "Apache-2.0", "main": "dist/index.js", diff --git a/legacy-sdk/whirlpool/src/impl/whirlpool-client-impl.ts b/legacy-sdk/whirlpool/src/impl/whirlpool-client-impl.ts index 2db1e4c7d..19c89895d 100644 --- a/legacy-sdk/whirlpool/src/impl/whirlpool-client-impl.ts +++ b/legacy-sdk/whirlpool/src/impl/whirlpool-client-impl.ts @@ -16,7 +16,7 @@ import { WhirlpoolAccountFetcherInterface, } from "../network/public/fetcher"; import { WhirlpoolRouter, WhirlpoolRouterBuilder } from "../router/public"; -import { WhirlpoolData } from "../types/public"; +import { MAX_TICK_INDEX, MIN_TICK_INDEX, SPLASH_POOL_TICK_SPACING, WhirlpoolData } from "../types/public"; import { getTickArrayDataForPosition } from "../utils/builder/position-builder-util"; import { PDAUtil, PoolUtil, PriceMath, TickUtil } from "../utils/public"; import { Position, Whirlpool, WhirlpoolClient } from "../whirlpool-client"; @@ -24,6 +24,7 @@ import { PositionImpl } from "./position-impl"; import { getRewardInfos, getTokenMintInfos, getTokenVaultAccountInfos } from "./util"; import { WhirlpoolImpl } from "./whirlpool-impl"; import { NO_TOKEN_EXTENSION_CONTEXT, TokenExtensionContextForPool, TokenExtensionUtil } from "../utils/public/token-extension-util"; +import Decimal from "decimal.js"; export class WhirlpoolClientImpl implements WhirlpoolClient { constructor(readonly ctx: WhirlpoolContext) {} @@ -187,6 +188,128 @@ export class WhirlpoolClientImpl implements WhirlpoolClient { return Object.fromEntries(results); } + public async createSplashPool( + whirlpoolsConfig: Address, + tokenMintA: Address, + tokenMintB: Address, + initialPrice = new Decimal(1), + funder: Address, + opts = PREFER_CACHE + ): Promise<{ poolKey: PublicKey; tx: TransactionBuilder }> { + const correctTokenOrder = PoolUtil.orderMints(tokenMintA, tokenMintB).map((addr) => + addr.toString() + ); + + invariant( + correctTokenOrder[0] === tokenMintA.toString(), + "Token order needs to be flipped to match the canonical ordering (i.e. sorted on the byte repr. of the mint pubkeys)" + ); + + const mintInfos = await this.getFetcher().getMintInfos([tokenMintA, tokenMintB], opts); + invariant(mintInfos.size === 2, "At least one of the token mints cannot be found."); + + const tokenExtensionCtx: TokenExtensionContextForPool = { + ...NO_TOKEN_EXTENSION_CONTEXT, + tokenMintWithProgramA: mintInfos.get(tokenMintA.toString())!, + tokenMintWithProgramB: mintInfos.get(tokenMintB.toString())!, + }; + + whirlpoolsConfig = AddressUtil.toPubKey(whirlpoolsConfig); + + const feeTierKey = PDAUtil.getFeeTier( + this.ctx.program.programId, + whirlpoolsConfig, + SPLASH_POOL_TICK_SPACING + ).publicKey; + + const whirlpoolPda = PDAUtil.getWhirlpool( + this.ctx.program.programId, + whirlpoolsConfig, + new PublicKey(tokenMintA), + new PublicKey(tokenMintB), + SPLASH_POOL_TICK_SPACING + ); + + const tokenDecimalsA = mintInfos.get(tokenMintA.toString())?.decimals ?? 0; + const tokenDecimalsB = mintInfos.get(tokenMintB.toString())?.decimals ?? 0; + const initSqrtPrice = PriceMath.priceToSqrtPriceX64(initialPrice, tokenDecimalsA, tokenDecimalsB); + const tokenVaultAKeypair = Keypair.generate(); + const tokenVaultBKeypair = Keypair.generate(); + + const txBuilder = new TransactionBuilder( + this.ctx.provider.connection, + this.ctx.provider.wallet, + this.ctx.txBuilderOpts + ); + + const tokenBadgeA = PDAUtil.getTokenBadge(this.ctx.program.programId, whirlpoolsConfig, AddressUtil.toPubKey(tokenMintA)).publicKey; + const tokenBadgeB = PDAUtil.getTokenBadge(this.ctx.program.programId, whirlpoolsConfig, AddressUtil.toPubKey(tokenMintB)).publicKey; + + const baseParams = { + initSqrtPrice, + whirlpoolsConfig, + whirlpoolPda, + tokenMintA: new PublicKey(tokenMintA), + tokenMintB: new PublicKey(tokenMintB), + tokenVaultAKeypair, + tokenVaultBKeypair, + feeTierKey, + tickSpacing: SPLASH_POOL_TICK_SPACING, + funder: new PublicKey(funder), + }; + + const initPoolIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx) + ? WhirlpoolIx.initializePoolIx(this.ctx.program, baseParams) + : WhirlpoolIx.initializePoolV2Ix(this.ctx.program, { + ...baseParams, + tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram, + tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram, + tokenBadgeA, + tokenBadgeB, + }); + + txBuilder.addInstruction(initPoolIx); + + const [startTickIndex, endTickIndex] = TickUtil.getFullRangeTickIndex(SPLASH_POOL_TICK_SPACING); + const startInitializableTickIndex = TickUtil.getStartTickIndex(startTickIndex, SPLASH_POOL_TICK_SPACING); + const endInitializableTickIndex = TickUtil.getStartTickIndex(endTickIndex, SPLASH_POOL_TICK_SPACING); + + const startTickArrayPda = PDAUtil.getTickArray( + this.ctx.program.programId, + whirlpoolPda.publicKey, + startInitializableTickIndex + ); + + const endTickArrayPda = PDAUtil.getTickArray( + this.ctx.program.programId, + whirlpoolPda.publicKey, + endInitializableTickIndex + ); + + txBuilder.addInstruction( + initTickArrayIx(this.ctx.program, { + startTick: startInitializableTickIndex, + tickArrayPda: startTickArrayPda, + whirlpool: whirlpoolPda.publicKey, + funder: AddressUtil.toPubKey(funder), + }) + ); + + txBuilder.addInstruction( + initTickArrayIx(this.ctx.program, { + startTick: endInitializableTickIndex, + tickArrayPda: endTickArrayPda, + whirlpool: whirlpoolPda.publicKey, + funder: AddressUtil.toPubKey(funder), + }) + ); + + return { + poolKey: whirlpoolPda.publicKey, + tx: txBuilder, + }; + } + public async createPool( whirlpoolsConfig: Address, tokenMintA: Address, diff --git a/legacy-sdk/whirlpool/src/types/public/constants.ts b/legacy-sdk/whirlpool/src/types/public/constants.ts index 7f42e4c41..d1cb3e527 100644 --- a/legacy-sdk/whirlpool/src/types/public/constants.ts +++ b/legacy-sdk/whirlpool/src/types/public/constants.ts @@ -132,3 +132,10 @@ export const WHIRLPOOL_NFT_UPDATE_AUTH = new PublicKey( * @category Constants */ export const FULL_RANGE_ONLY_TICK_SPACING_THRESHOLD = 32768; + + +/** + * The tick spacing for splash pools. + * @category Constants + */ +export const SPLASH_POOL_TICK_SPACING = 32896; diff --git a/legacy-sdk/whirlpool/src/whirlpool-client.ts b/legacy-sdk/whirlpool/src/whirlpool-client.ts index 9d2a8f7da..19ea5deb3 100644 --- a/legacy-sdk/whirlpool/src/whirlpool-client.ts +++ b/legacy-sdk/whirlpool/src/whirlpool-client.ts @@ -17,6 +17,7 @@ import { WhirlpoolData, } from "./types/public"; import { TokenAccountInfo, TokenInfo, WhirlpoolRewardInfo } from "./types/public/client-types"; +import Decimal from "decimal.js"; /** * Helper class to help interact with Whirlpool Accounts with a simpler interface. @@ -92,6 +93,24 @@ export interface WhirlpoolClient { opts?: WhirlpoolAccountFetchOptions ) => Promise; + /** + * Create a Whirlpool account for a group of token A, token B and tick spacing + * @param whirlpoolConfig the address of the whirlpool config + * @param tokenMintA the address of the token A + * @param tokenMintB the address of the token B + * @param initialPrice the initial price of the pool (as x token B per 1 token A) + * @param funder the account to debit SOL from to fund the creation of the account(s) + * @return `poolKey`: The public key of the newly created whirlpool account. `tx`: The transaction containing instructions for the on-chain operations. + * @throws error when the tokens are not in the canonical byte-based ordering. To resolve this, invert the token order and the initialTick (see `TickUtil.invertTick()`, `PriceMath.invertSqrtPriceX64()`, or `PriceMath.invertPrice()`). + */ + createSplashPool: ( + whirlpoolsConfig: Address, + tokenMintA: Address, + tokenMintB: Address, + initialPrice: Decimal, + funder: Address + ) => Promise<{ poolKey: PublicKey; tx: TransactionBuilder }>; + /** * Create a Whirlpool account for a group of token A, token B and tick spacing * @param whirlpoolConfig the address of the whirlpool config diff --git a/legacy-sdk/whirlpool/tests/sdk/whirlpools/whirlpool-client-impl.test.ts b/legacy-sdk/whirlpool/tests/sdk/whirlpools/whirlpool-client-impl.test.ts index 306d11bf6..62d1f4535 100644 --- a/legacy-sdk/whirlpool/tests/sdk/whirlpools/whirlpool-client-impl.test.ts +++ b/legacy-sdk/whirlpool/tests/sdk/whirlpools/whirlpool-client-impl.test.ts @@ -4,11 +4,10 @@ import Decimal from "decimal.js"; import { buildWhirlpoolClient, InitPoolParams, - InitPoolV2Params, PDAUtil, PriceMath, + SPLASH_POOL_TICK_SPACING, TickUtil, - toTx, WhirlpoolContext } from "../../../src"; import { IGNORE_CACHE } from "../../../src/network/public/fetcher"; @@ -27,11 +26,13 @@ describe("whirlpool-client-impl", () => { describe("TokenProgram", () => { let funderKeypair: anchor.web3.Keypair; - let poolInitInfo: InitPoolParams; beforeEach(async () => { funderKeypair = anchor.web3.Keypair.generate(); await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); - poolInitInfo = ( + }); + + it("successfully creates a new whirpool account and initial tick array account", async () => { + const poolInitInfo = ( await buildTestPoolParams( ctx, TickSpacing.Standard, @@ -40,9 +41,7 @@ describe("whirlpool-client-impl", () => { funderKeypair.publicKey ) ).poolInitInfo; - }); - it("successfully creates a new whirpool account and initial tick array account", async () => { const initalTick = TickUtil.getInitializableTickIndex( PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice), poolInitInfo.tickSpacing @@ -118,6 +117,16 @@ describe("whirlpool-client-impl", () => { }); it("throws an error when token order is incorrect", async () => { + const poolInitInfo = ( + await buildTestPoolParams( + ctx, + TickSpacing.Standard, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + funderKeypair.publicKey + ) + ).poolInitInfo; + const initalTick = TickUtil.getInitializableTickIndex( PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice), poolInitInfo.tickSpacing @@ -137,6 +146,132 @@ describe("whirlpool-client-impl", () => { /Token order needs to be flipped to match the canonical ordering \(i.e. sorted on the byte repr. of the mint pubkeys\)/ ); }); + + it("successfully creates a new splash pool whirlpool account and initial tick array account", async () => { + const poolInitInfo = ( + await buildTestPoolParams( + ctx, + SPLASH_POOL_TICK_SPACING, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + funderKeypair.publicKey + ) + ).poolInitInfo; + const [startTick, endTick] = TickUtil.getFullRangeTickIndex(SPLASH_POOL_TICK_SPACING); + + const { poolKey: actualPubkey, tx } = await client.createSplashPool( + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintA, + poolInitInfo.tokenMintB, + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), + funderKeypair.publicKey + ); + + const expectedPda = PDAUtil.getWhirlpool( + ctx.program.programId, + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintA, + poolInitInfo.tokenMintB, + SPLASH_POOL_TICK_SPACING + ); + + const startTickArrayPda = PDAUtil.getTickArrayFromTickIndex( + startTick, + SPLASH_POOL_TICK_SPACING, + expectedPda.publicKey, + ctx.program.programId + ); + + const endTickArrayPda = PDAUtil.getTickArrayFromTickIndex( + endTick, + SPLASH_POOL_TICK_SPACING, + expectedPda.publicKey, + ctx.program.programId + ); + + assert.ok(expectedPda.publicKey.equals(actualPubkey)); + + const [whirlpoolAccountBefore, startTickArrayAccountBefore, endTickArrayAccountBefore] = await Promise.all([ + ctx.fetcher.getPool(expectedPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(startTickArrayPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(endTickArrayPda.publicKey, IGNORE_CACHE), + ]); + + assert.ok(whirlpoolAccountBefore === null); + assert.ok(startTickArrayAccountBefore === null); + assert.ok(endTickArrayAccountBefore === null); + + await tx.addSigner(funderKeypair).buildAndExecute(); + + const [whirlpoolAccountAfter, startTickArrayAccountAfter, endTickArrayAccountAfter] = await Promise.all([ + ctx.fetcher.getPool(expectedPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(startTickArrayPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(endTickArrayPda.publicKey, IGNORE_CACHE), + ]); + + assert.ok(whirlpoolAccountAfter !== null); + assert.ok(startTickArrayAccountAfter !== null); + assert.ok(endTickArrayAccountAfter !== null); + + const startSqrtPrice = PriceMath.priceToSqrtPriceX64( + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), 6, 6 + ) + + assert.ok(whirlpoolAccountAfter.feeGrowthGlobalA.eqn(0)); + assert.ok(whirlpoolAccountAfter.feeGrowthGlobalB.eqn(0)); + assert.ok(whirlpoolAccountAfter.feeRate === 3000); + assert.ok(whirlpoolAccountAfter.liquidity.eqn(0)); + assert.ok(whirlpoolAccountAfter.protocolFeeOwedA.eqn(0)); + assert.ok(whirlpoolAccountAfter.protocolFeeOwedB.eqn(0)); + assert.ok(whirlpoolAccountAfter.protocolFeeRate === 300); + assert.ok(whirlpoolAccountAfter.rewardInfos.length === 3); + assert.ok(whirlpoolAccountAfter.rewardLastUpdatedTimestamp.eqn(0)); + assert.ok(whirlpoolAccountAfter.sqrtPrice.eq(startSqrtPrice)); + assert.ok(whirlpoolAccountAfter.tickCurrentIndex === PriceMath.sqrtPriceX64ToTickIndex(startSqrtPrice)); + assert.ok(whirlpoolAccountAfter.tickSpacing === SPLASH_POOL_TICK_SPACING); + assert.ok(whirlpoolAccountAfter.tokenMintA.equals(poolInitInfo.tokenMintA)); + assert.ok(whirlpoolAccountAfter.tokenMintB.equals(poolInitInfo.tokenMintB)); + assert.ok(whirlpoolAccountAfter.whirlpoolBump[0] === expectedPda.bump); + assert.ok(whirlpoolAccountAfter.whirlpoolsConfig.equals(poolInitInfo.whirlpoolsConfig)); + + assert.ok( + startTickArrayAccountAfter.startTickIndex === + TickUtil.getStartTickIndex(startTick, SPLASH_POOL_TICK_SPACING) + ); + assert.ok(startTickArrayAccountAfter.ticks.length > 0); + assert.ok(startTickArrayAccountAfter.whirlpool.equals(expectedPda.publicKey)); + + assert.ok( + endTickArrayAccountAfter.startTickIndex === + TickUtil.getStartTickIndex(endTick, SPLASH_POOL_TICK_SPACING) + ); + + assert.ok(endTickArrayAccountAfter.ticks.length > 0); + assert.ok(endTickArrayAccountAfter.whirlpool.equals(expectedPda.publicKey)); + }); + + it("throws an error when token order is incorrect while creating splash pool", async () => { + const poolInitInfo = ( + await buildTestPoolParams( + ctx, + SPLASH_POOL_TICK_SPACING, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + funderKeypair.publicKey + ) + ).poolInitInfo; + + await assert.rejects( + client.createSplashPool( + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintB, + poolInitInfo.tokenMintA, + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), + funderKeypair.publicKey + ), + /Token order needs to be flipped to match the canonical ordering \(i.e. sorted on the byte repr. of the mint pubkeys\)/ + ); + }); }); describe("TokenExtension", () => { @@ -166,7 +301,7 @@ describe("whirlpool-client-impl", () => { const transferFeeConfigB = getTransferFeeConfig(mintDataB); assert.ok(transferFeeConfigA !== null); assert.ok(transferFeeConfigB !== null); - + const initalTick = TickUtil.getInitializableTickIndex( PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice), poolInitInfo.tickSpacing @@ -347,6 +482,206 @@ describe("whirlpool-client-impl", () => { ); }); + it("successfully creates a new whirpool account and initial tick array account (without TokenBadge) for splash pool", async () => { + const poolInitInfo = ( + await buildTestPoolV2Params( + ctx, + {isToken2022: true, hasTransferFeeExtension: true}, + {isToken2022: true, hasTransferFeeExtension: true}, + SPLASH_POOL_TICK_SPACING, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + funderKeypair.publicKey + ) + ).poolInitInfo; + + // initialized with TransferFee extension + const mintDataA = await getMint(provider.connection, poolInitInfo.tokenMintA, "confirmed", TEST_TOKEN_2022_PROGRAM_ID); + const mintDataB = await getMint(provider.connection, poolInitInfo.tokenMintB, "confirmed", TEST_TOKEN_2022_PROGRAM_ID); + const transferFeeConfigA = getTransferFeeConfig(mintDataA); + const transferFeeConfigB = getTransferFeeConfig(mintDataB); + assert.ok(transferFeeConfigA !== null); + assert.ok(transferFeeConfigB !== null); + + const [startTick, endTick] = TickUtil.getFullRangeTickIndex(SPLASH_POOL_TICK_SPACING); + + const { poolKey: actualPubkey, tx } = await client.createSplashPool( + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintA, + poolInitInfo.tokenMintB, + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), + funderKeypair.publicKey + ); + + const expectedPda = PDAUtil.getWhirlpool( + ctx.program.programId, + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintA, + poolInitInfo.tokenMintB, + SPLASH_POOL_TICK_SPACING + ); + + const startTickArrayPda = PDAUtil.getTickArrayFromTickIndex( + startTick, + SPLASH_POOL_TICK_SPACING, + expectedPda.publicKey, + ctx.program.programId + ); + + const endTickArrayPda = PDAUtil.getTickArrayFromTickIndex( + endTick, + SPLASH_POOL_TICK_SPACING, + expectedPda.publicKey, + ctx.program.programId + ); + + assert.ok(expectedPda.publicKey.equals(actualPubkey)); + + const [whirlpoolAccountBefore, startTickArrayAccountBefore, endTickArrayAccountBefore] = await Promise.all([ + ctx.fetcher.getPool(expectedPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(startTickArrayPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(endTickArrayPda.publicKey, IGNORE_CACHE), + ]); + + assert.ok(whirlpoolAccountBefore === null); + assert.ok(startTickArrayAccountBefore === null); + assert.ok(endTickArrayAccountBefore === null); + + await tx.addSigner(funderKeypair).buildAndExecute(); + + const [whirlpoolAccountAfter, startTickArrayAccountAfter, endTickArrayAccountAfter] = await Promise.all([ + ctx.fetcher.getPool(expectedPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(startTickArrayPda.publicKey, IGNORE_CACHE), + ctx.fetcher.getTickArray(endTickArrayPda.publicKey, IGNORE_CACHE), + ]); + + assert.ok(whirlpoolAccountAfter !== null); + assert.ok(startTickArrayAccountAfter !== null); + assert.ok(endTickArrayAccountAfter !== null); + + const startSqrtPrice = PriceMath.priceToSqrtPriceX64( + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), 6, 6 + ) + + assert.ok(whirlpoolAccountAfter.feeGrowthGlobalA.eqn(0)); + assert.ok(whirlpoolAccountAfter.feeGrowthGlobalB.eqn(0)); + assert.ok(whirlpoolAccountAfter.feeRate === 3000); + assert.ok(whirlpoolAccountAfter.liquidity.eqn(0)); + assert.ok(whirlpoolAccountAfter.protocolFeeOwedA.eqn(0)); + assert.ok(whirlpoolAccountAfter.protocolFeeOwedB.eqn(0)); + assert.ok(whirlpoolAccountAfter.protocolFeeRate === 300); + assert.ok(whirlpoolAccountAfter.rewardInfos.length === 3); + assert.ok(whirlpoolAccountAfter.rewardLastUpdatedTimestamp.eqn(0)); + assert.ok(whirlpoolAccountAfter.sqrtPrice.eq(startSqrtPrice)); + assert.ok(whirlpoolAccountAfter.tickCurrentIndex === PriceMath.sqrtPriceX64ToTickIndex(startSqrtPrice)); + assert.ok(whirlpoolAccountAfter.tickSpacing === SPLASH_POOL_TICK_SPACING); + assert.ok(whirlpoolAccountAfter.tokenMintA.equals(poolInitInfo.tokenMintA)); + assert.ok(whirlpoolAccountAfter.tokenMintB.equals(poolInitInfo.tokenMintB)); + assert.ok(whirlpoolAccountAfter.whirlpoolBump[0] === expectedPda.bump); + assert.ok(whirlpoolAccountAfter.whirlpoolsConfig.equals(poolInitInfo.whirlpoolsConfig)); + + assert.ok( + startTickArrayAccountAfter.startTickIndex === + TickUtil.getStartTickIndex(startTick, SPLASH_POOL_TICK_SPACING) + ); + assert.ok(startTickArrayAccountAfter.ticks.length > 0); + assert.ok(startTickArrayAccountAfter.whirlpool.equals(expectedPda.publicKey)); + + assert.ok( + endTickArrayAccountAfter.startTickIndex === + TickUtil.getStartTickIndex(endTick, SPLASH_POOL_TICK_SPACING) + ); + + assert.ok(endTickArrayAccountAfter.ticks.length > 0); + assert.ok(endTickArrayAccountAfter.whirlpool.equals(expectedPda.publicKey)); + }); + + it("successfully creates a new whirpool account (with TokenBadge) for splash pool", async () => { + const poolInitInfo = ( + await buildTestPoolV2Params( + ctx, + {isToken2022: true, hasTransferHookExtension: true, hasPermanentDelegate: true}, // TokenBadge required + {isToken2022: true, hasTransferHookExtension: true, hasPermanentDelegate: true}, // TokenBadge required + SPLASH_POOL_TICK_SPACING, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + ctx.wallet.publicKey, + true, // initialize TokenBadge + true, // initialize TokenBadge + ) + ).poolInitInfo; + + const tx = (await client.createSplashPool( + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintA, + poolInitInfo.tokenMintB, + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), + ctx.wallet.publicKey, + )).tx; + + await tx.buildAndExecute(); + + const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey, IGNORE_CACHE); + + assert.ok(whirlpool !== null); + assert.ok(whirlpool.getData().tokenMintA.equals(poolInitInfo.tokenMintA)); + assert.ok(whirlpool.getData().tokenMintB.equals(poolInitInfo.tokenMintB)); + }); + + it("throws an error when token order is incorrect for splash pool", async () => { + const poolInitInfo = ( + await buildTestPoolV2Params( + ctx, + {isToken2022: true, hasTransferFeeExtension: true}, + {isToken2022: true, hasTransferFeeExtension: true}, + SPLASH_POOL_TICK_SPACING, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + funderKeypair.publicKey + ) + ).poolInitInfo; + + await assert.rejects( + client.createSplashPool( + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintB, + poolInitInfo.tokenMintA, + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), + funderKeypair.publicKey + ), + /Token order needs to be flipped to match the canonical ordering \(i.e. sorted on the byte repr. of the mint pubkeys\)/ + ); + }); + + it("throws an error when TokenBadge is not initialized for splash pool", async () => { + const poolInitInfo = ( + await buildTestPoolV2Params( + ctx, + {isToken2022: true, hasTransferHookExtension: true, hasPermanentDelegate: true}, + {isToken2022: true, hasTransferHookExtension: true, hasPermanentDelegate: true}, + SPLASH_POOL_TICK_SPACING, + 3000, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6), + ctx.wallet.publicKey, + false, // not initialize TokenBadge + false, // not initialize TokenBadge + ) + ).poolInitInfo; + + const tx = (await client.createSplashPool( + poolInitInfo.whirlpoolsConfig, + poolInitInfo.tokenMintA, + poolInitInfo.tokenMintB, + PriceMath.sqrtPriceX64ToPrice(poolInitInfo.initSqrtPrice, 6, 6), + ctx.wallet.publicKey, + )).tx; + + await assert.rejects( + tx.buildAndExecute(), + /0x179f/ // UnsupportedTokenMint + ); + }); + }); });