diff --git a/ts-sdk/whirlpool/src/createPool.ts b/ts-sdk/whirlpool/src/createPool.ts index 9735c6d5..f9cc2bcb 100644 --- a/ts-sdk/whirlpool/src/createPool.ts +++ b/ts-sdk/whirlpool/src/createPool.ts @@ -32,6 +32,7 @@ import { } from "@orca-so/whirlpools-core"; import { fetchAllMint, getTokenSize } from "@solana-program/token"; import assert from "assert"; +import { orderMints } from "./token"; /** * Represents the instructions and metadata for creating a pool. @@ -142,7 +143,7 @@ export async function createConcentratedLiquidityPoolInstructions( "Either supply a funder or set the default funder", ); assert( - Buffer.from(tokenMintA) < Buffer.from(tokenMintB), + orderMints(tokenMintA, tokenMintB)[0] === tokenMintA, "Token order needs to be flipped to match the canonical ordering (i.e. sorted on the byte repr. of the mint pubkeys)", ); const instructions: IInstruction[] = []; diff --git a/ts-sdk/whirlpool/src/pool.ts b/ts-sdk/whirlpool/src/pool.ts index 5c1038cf..13a6a241 100644 --- a/ts-sdk/whirlpool/src/pool.ts +++ b/ts-sdk/whirlpool/src/pool.ts @@ -17,6 +17,7 @@ import type { GetProgramAccountsApi, } from "@solana/web3.js"; import { SPLASH_POOL_TICK_SPACING, WHIRLPOOLS_CONFIG_ADDRESS } from "./config"; +import { orderMints } from "./token"; /** * Type representing a pool that is not yet initialized. @@ -123,10 +124,7 @@ export async function fetchConcentratedLiquidityPool( tokenMintTwo: Address, tickSpacing: number, ): Promise { - const [tokenMintA, tokenMintB] = - Buffer.from(tokenMintOne) < Buffer.from(tokenMintTwo) - ? [tokenMintOne, tokenMintTwo] - : [tokenMintTwo, tokenMintOne]; + const [tokenMintA, tokenMintB] = orderMints(tokenMintOne, tokenMintTwo); const feeTierAddress = await getFeeTierAddress( WHIRLPOOLS_CONFIG_ADDRESS, tickSpacing, @@ -196,11 +194,7 @@ export async function fetchWhirlpoolsByTokenPair( tokenMintOne: Address, tokenMintTwo: Address, ): Promise { - const [tokenMintA, tokenMintB] = - Buffer.from(tokenMintOne) < Buffer.from(tokenMintTwo) - ? [tokenMintOne, tokenMintTwo] - : [tokenMintTwo, tokenMintOne]; - + const [tokenMintA, tokenMintB] = orderMints(tokenMintOne, tokenMintTwo); const feeTierAccounts = await fetchAllFeeTierWithFilter( rpc, feeTierWhirlpoolsConfigFilter(WHIRLPOOLS_CONFIG_ADDRESS), diff --git a/ts-sdk/whirlpool/src/token.ts b/ts-sdk/whirlpool/src/token.ts index 392d3a39..1065d145 100644 --- a/ts-sdk/whirlpool/src/token.ts +++ b/ts-sdk/whirlpool/src/token.ts @@ -279,3 +279,18 @@ export function getCurrentTransferFee( maxFee: transferFee.maximumFee, }; } + +/** + * Orders two mints by canonical byte order. + * @param {Address} mint1 + * @param {Address} mint2 + * @returns {[Address, Address]} [mint1, mint2] if mint1 should come first, [mint2, mint1] otherwise + */ +export function orderMints(mint1: Address, mint2: Address): [Address, Address] { + const encoder = getAddressEncoder(); + const mint1Bytes = new Uint8Array(encoder.encode(mint1)); + const mint2Bytes = new Uint8Array(encoder.encode(mint2)); + return Buffer.compare(mint1Bytes, mint2Bytes) < 0 + ? [mint1, mint2] + : [mint2, mint1]; +} diff --git a/ts-sdk/whirlpool/tests/token.test.ts b/ts-sdk/whirlpool/tests/token.test.ts index fe86bd3e..6790cea7 100644 --- a/ts-sdk/whirlpool/tests/token.test.ts +++ b/ts-sdk/whirlpool/tests/token.test.ts @@ -19,8 +19,8 @@ import { setSolWrappingStrategy, } from "../src/config"; import type { Address, TransactionSigner } from "@solana/web3.js"; -import { createNoopSigner, generateKeyPairSigner } from "@solana/web3.js"; -import { NATIVE_MINT, prepareTokenAccountsInstructions } from "../src/token"; +import { address, createNoopSigner, generateKeyPairSigner } from "@solana/web3.js"; +import { NATIVE_MINT, orderMints, prepareTokenAccountsInstructions } from "../src/token"; import assert from "assert"; import { assertCloseAccountInstruction, @@ -600,4 +600,16 @@ describe("Token Account Creation", () => { owner: signer.address, }); }); + + it("Should order mints by canonical byte order", () => { + const mint1 = address("Jd4M8bfJG3sAkd82RsGWyEXoaBXQP7njFzBwEaCTuDa"); + const mint2 = address("BRjpCHtyQLNCo8gqRUr8jtdAj5AjPYQaoqbvcZiHok1k"); + const [mintA, mintB] = orderMints(mint1, mint2); + assert.strictEqual(mintA, mint1); + assert.strictEqual(mintB, mint2); + + const [mintC, mintD] = orderMints(mint2, mint1); + assert.strictEqual(mintC, mint1); + assert.strictEqual(mintD, mint2); + }); });