New SwapUtil. getSwapParamsFromQuote to make calling WhirlpoolIx.swap simpler (#76)
\
This commit is contained in:
parent
1fc24cedff
commit
bdb267efe2
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./collect-all-txn";
|
||||
export * from "./collect-protocol-fees";
|
||||
export * from "./swap-async";
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue