New SwapUtil. getSwapParamsFromQuote to make calling WhirlpoolIx.swap simpler (#76)

\
This commit is contained in:
meep 2023-01-14 21:17:40 +08:00 committed by GitHub
parent 1fc24cedff
commit bdb267efe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 190 additions and 95 deletions

View File

@ -21,8 +21,8 @@ import {
initTickArrayIx,
openPositionIx,
openPositionWithMetadataIx,
swapAsync,
SwapInput,
swapIx,
} from "../instructions";
import {
collectFeesQuote,
@ -181,11 +181,19 @@ export class WhirlpoolImpl implements Whirlpool {
);
}
async swap(quote: SwapInput, sourceWallet?: Address) {
async swap(quote: SwapInput, sourceWallet?: Address): Promise<TransactionBuilder> {
const sourceWalletKey = sourceWallet
? AddressUtil.toPubKey(sourceWallet)
: this.ctx.wallet.publicKey;
return this.getSwapTx(quote, sourceWalletKey);
return swapAsync(
this.ctx,
{
swapInput: quote,
whirlpool: this,
wallet: sourceWalletKey,
},
true
);
}
async swapWithDevFees(
@ -219,7 +227,19 @@ export class WhirlpoolImpl implements Whirlpool {
);
}
return this.getSwapTx(quote, sourceWalletKey, txBuilder);
const swapTxBuilder = await swapAsync(
this.ctx,
{
swapInput: quote,
whirlpool: this,
wallet: sourceWalletKey,
},
true
);
txBuilder.addInstruction(swapTxBuilder.compressIx(true));
return txBuilder;
}
/**
@ -549,64 +569,6 @@ export class WhirlpoolImpl implements Whirlpool {
return txBuilders;
}
private async getSwapTx(
input: SwapInput,
wallet: PublicKey,
initTxBuilder?: TransactionBuilder
): Promise<TransactionBuilder> {
invariant(input.amount.gt(ZERO), "swap amount must be more than zero.");
// Check if all the tick arrays have been initialized.
const tickArrayAddresses = [input.tickArray0, input.tickArray1, input.tickArray2];
const tickArrays = await this.ctx.fetcher.listTickArrays(tickArrayAddresses, true);
const uninitializedIndices = TickArrayUtil.getUninitializedArrays(tickArrays);
if (uninitializedIndices.length > 0) {
const uninitializedArrays = uninitializedIndices
.map((index) => tickArrayAddresses[index].toBase58())
.join(", ");
throw new Error(`TickArray addresses - [${uninitializedArrays}] need to be initialized.`);
}
const { amount, aToB } = input;
const whirlpool = this.data;
const txBuilder =
initTxBuilder ??
new TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet);
const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection,
wallet,
[
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: aToB ? amount : ZERO },
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: !aToB ? amount : ZERO },
],
() => this.ctx.fetcher.getAccountRentExempt()
);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
txBuilder.addInstruction(tokenOwnerAccountAIx);
txBuilder.addInstruction(tokenOwnerAccountBIx);
const oraclePda = PDAUtil.getOracle(this.ctx.program.programId, this.address);
txBuilder.addInstruction(
swapIx(this.ctx.program, {
...input,
whirlpool: this.address,
tokenAuthority: wallet,
tokenOwnerAccountA,
tokenVaultA: whirlpool.tokenVaultA,
tokenOwnerAccountB,
tokenVaultB: whirlpool.tokenVaultB,
oracle: oraclePda.publicKey,
})
);
return txBuilder;
}
private async refresh() {
const account = await this.ctx.fetcher.getPool(this.address, true);
if (!!account) {

View File

@ -1,5 +1,4 @@
import { Instruction, TokenUtil, TransactionBuilder, ZERO } from "@orca-so/common-sdk";
import { createWSOLAccountInstructions } from "@orca-so/common-sdk/dist/helpers/token-instructions";
import { Address } from "@project-serum/anchor";
import { NATIVE_MINT } from "@solana/spl-token";
import { PACKET_DATA_SIZE, PublicKey } from "@solana/web3.js";

View File

@ -1,2 +1,3 @@
export * from "./collect-all-txn";
export * from "./collect-protocol-fees";
export * from "./swap-async";

View File

@ -0,0 +1,67 @@
import { resolveOrCreateATAs, TransactionBuilder, ZERO } from "@orca-so/common-sdk";
import { PublicKey } from "@solana/web3.js";
import { SwapUtils, TickArrayUtil, Whirlpool, WhirlpoolContext } from "../..";
import { SwapInput, swapIx } from "../swap-ix";
export type SwapAsyncParams = {
swapInput: SwapInput;
whirlpool: Whirlpool;
wallet: PublicKey;
};
/**
* Swap instruction builder method with resolveATA & additional checks.
* @param ctx - WhirlpoolContext object for the current environment.
* @param params - {@link SwapAsyncParams}
* @param refresh - If true, the network calls will always fetch for the latest values.
* @returns
*/
export async function swapAsync(
ctx: WhirlpoolContext,
params: SwapAsyncParams,
refresh: boolean
): Promise<TransactionBuilder> {
const { wallet, whirlpool, swapInput } = params;
const { aToB, amount } = swapInput;
const txBuilder = new TransactionBuilder(ctx.connection, ctx.wallet);
const tickArrayAddresses = [swapInput.tickArray0, swapInput.tickArray1, swapInput.tickArray2];
let uninitializedArrays = await TickArrayUtil.getUninitializedArraysString(
tickArrayAddresses,
ctx.fetcher,
refresh
);
if (uninitializedArrays) {
throw new Error(`TickArray addresses - [${uninitializedArrays}] need to be initialized.`);
}
const data = whirlpool.getData();
const [resolvedAtaA, resolvedAtaB] = await resolveOrCreateATAs(
ctx.connection,
wallet,
[
{ tokenMint: data.tokenMintA, wrappedSolAmountIn: aToB ? amount : ZERO },
{ tokenMint: data.tokenMintB, wrappedSolAmountIn: !aToB ? amount : ZERO },
],
() => ctx.fetcher.getAccountRentExempt()
);
const { address: ataAKey, ...tokenOwnerAccountAIx } = resolvedAtaA;
const { address: ataBKey, ...tokenOwnerAccountBIx } = resolvedAtaB;
txBuilder.addInstructions([tokenOwnerAccountAIx, tokenOwnerAccountBIx]);
const inputTokenAccount = aToB ? ataAKey : ataBKey;
const outputTokenAccount = aToB ? ataBKey : ataAKey;
return txBuilder.addInstruction(
swapIx(
ctx.program,
SwapUtils.getSwapParamsFromQuote(
swapInput,
ctx,
whirlpool,
inputTokenAccount,
outputTokenAccount,
wallet
)
)
);
}

View File

@ -5,18 +5,10 @@ import { PublicKey } from "@solana/web3.js";
import { Whirlpool } from "../artifacts/whirlpool";
/**
* Parameters and accounts to swap on a Whirlpool
* Raw parameters and accounts to swap on a Whirlpool
*
* @category Instruction Types
* @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
* @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents
* the input token of the swap.
* @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput).
* @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput).
* @param sqrtPriceLimit - The maximum/minimum price the swap will swap to.
* @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in
* @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0.
* @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1.
* @param swapInput - Parameters in {@link SwapInput}
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
* @param tokenOwnerAccountA - PublicKey for the associated token account for tokenA in the collection wallet
* @param tokenOwnerAccountB - PublicKey for the associated token account for tokenB in the collection wallet
@ -36,7 +28,7 @@ export type SwapParams = SwapInput & {
};
/**
* Parameters to swap on a Whirlpool
* Parameters that describe the nature of a swap on a Whirlpool.
*
* @category Instruction Types
* @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
@ -64,15 +56,7 @@ export type SwapInput = {
* Parameters to swap on a Whirlpool with developer fees
*
* @category Instruction Types
* @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
* @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents
* the input token of the swap.
* @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput).
* @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput).
* @param sqrtPriceLimit - The maximum/minimum price the swap will swap to.
* @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in
* @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0.
* @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1.
* @param swapInput - Parameters in {@link SwapInput}
* @param devFeeAmount - FeeAmount (developer fees) charged on this swap
*/
export type DevFeeSwapInput = SwapInput & {
@ -95,7 +79,7 @@ export type DevFeeSwapInput = SwapInput & {
* ### Parameters
* @category Instructions
* @param context - Context object containing services required to generate the instruction
* @param params - SwapParams object
* @param params - {@link SwapParams}
* @returns - Instruction to perform the action.
*/
export function swapIx(program: Program<Whirlpool>, params: SwapParams): Instruction {

View File

@ -5,7 +5,7 @@ import { Whirlpool } from "./artifacts/whirlpool";
import * as ix from "./instructions";
/**
* Instruction set for the Whirlpools program.
* Instruction builders for the Whirlpools program.
*
* @category Core
*/
@ -182,7 +182,7 @@ export class WhirlpoolIx {
*
* ### Parameters
* @param program - program object containing services required to generate the instruction
* @param params - SwapParams object
* @param params - {@link SwapParams}
* @returns - Instruction to perform the action.
*/
public static swapIx(program: Program<Whirlpool>, params: ix.SwapParams) {
@ -418,14 +418,16 @@ export class WhirlpoolIx {
}
/**
* DEPRECATED - use ${@link WhirlpoolClient} collectFeesAndRewardsForPositions function
* A set of transactions to collect all fees and rewards from a list of positions.
*
* @deprecated
* @param ctx - WhirlpoolContext object for the current environment.
* @param params - CollectAllPositionAddressParams object.
* @param refresh - if true, will always fetch for the latest values on chain to compute.
* @returns
*/
public static collectAllForPositionsTxns(
public static async collectAllForPositionsTxns(
ctx: WhirlpoolContext,
params: ix.CollectAllPositionAddressParams,
refresh: boolean

View File

@ -1,8 +1,5 @@
export {
ClosePositionParams,
CollectAllParams,
CollectAllPositionAddressParams,
CollectAllPositionParams,
CollectFeesParams,
CollectProtocolFeesParams,
CollectRewardParams,
@ -31,3 +28,8 @@ export {
SwapParams,
UpdateFeesAndRewardsParams,
} from "../../instructions/";
export {
CollectAllParams,
CollectAllPositionAddressParams,
CollectAllPositionParams,
} from "../../instructions/composites";

View File

@ -1,15 +1,19 @@
import { ZERO, U64_MAX, Percentage } from "@orca-so/common-sdk";
import { AddressUtil, Percentage, U64_MAX, ZERO } from "@orca-so/common-sdk";
import { Address } from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import { WhirlpoolContext } from "../..";
import { AccountFetcher } from "../../network/public";
import {
MIN_SQRT_PRICE,
MAX_SQRT_PRICE,
WhirlpoolData,
MAX_SWAP_TICK_ARRAYS,
TickArray,
MIN_SQRT_PRICE,
SwapInput,
SwapParams,
TickArray,
WhirlpoolData,
} from "../../types/public";
import { Whirlpool } from "../../whirlpool-client";
import { adjustForSlippage } from "../math/token-math";
import { PDAUtil } from "./pda-utils";
import { PoolUtil } from "./pool-utils";
@ -166,4 +170,45 @@ export class SwapUtils {
};
}
}
/**
* Convert a quote object and WhirlpoolClient's {@link Whirlpool} object into a {@link SwapParams} type
* to be plugged into {@link WhirlpoolIx.swapIx}.
*
* @param quote - A {@link SwapQuote} type generated from {@link swapQuoteWithParams}
* @param ctx - {@link WhirlpoolContext}
* @param whirlpool - A {@link Whirlpool} object from WhirlpoolClient
* @param inputTokenAssociatedAddress - The public key for the ATA of the input token in the swap
* @param outputTokenAssociatedAddress - The public key for the ATA of the input token in the swap
* @param wallet - The token authority for this swap
* @returns A converted {@link SwapParams} generated from the input
*/
public static getSwapParamsFromQuote(
quote: SwapInput,
ctx: WhirlpoolContext,
whirlpool: Whirlpool,
inputTokenAssociatedAddress: Address,
outputTokenAssociatedAddress: Address,
wallet: PublicKey
) {
const addr = whirlpool.getAddress();
const data = whirlpool.getData();
const aToB = quote.aToB;
const [inputTokenATA, outputTokenATA] = AddressUtil.toPubKeys([
inputTokenAssociatedAddress,
outputTokenAssociatedAddress,
]);
const oraclePda = PDAUtil.getOracle(ctx.program.programId, addr);
const params: SwapParams = {
whirlpool: whirlpool.getAddress(),
tokenOwnerAccountA: aToB ? inputTokenATA : outputTokenATA,
tokenOwnerAccountB: aToB ? outputTokenATA : inputTokenATA,
tokenVaultA: data.tokenVaultA,
tokenVaultB: data.tokenVaultB,
oracle: oraclePda.publicKey,
tokenAuthority: wallet,
...quote,
};
return params;
}
}

View File

@ -1,4 +1,5 @@
import { PDA } from "@orca-so/common-sdk";
import { AddressUtil, PDA } from "@orca-so/common-sdk";
import { Address } from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import invariant from "tiny-invariant";
import { AccountFetcher } from "../../network/public";
@ -217,6 +218,38 @@ export class TickArrayUtil {
});
}
/**
* Return a string containing all of the uninitialized arrays in the provided addresses.
* Useful for creating error messages.
*
* @param tickArrayAddrs - A list of tick-array addresses to verify.
* @param fetcher - {@link AccountFetcher}
* @param refresh - If true, always fetch the latest on-chain data
* @returns A string of all uninitialized tick array addresses, delimited by ",". Falsy value if all arrays are initialized.
*/
public static async getUninitializedArraysString(
tickArrayAddrs: Address[],
fetcher: AccountFetcher,
refresh: boolean
) {
const taAddrs = AddressUtil.toPubKeys(tickArrayAddrs);
const tickArrayData = await fetcher.listTickArrays(taAddrs, refresh);
// Verify tick arrays are initialized if the user provided them.
if (tickArrayData) {
const uninitializedIndices = TickArrayUtil.getUninitializedArrays(tickArrayData);
if (uninitializedIndices.length > 0) {
const uninitializedArrays = uninitializedIndices
.map((index) => taAddrs[index].toBase58())
.join(", ");
return uninitializedArrays;
}
}
return null;
}
public static async getUninitializedArraysPDAs(
ticks: number[],
programId: PublicKey,