Add token estimates for liquidity quotes, group token resolution (#12)

- Add tokenEst to the increase/decrease liquidity quotes
- Add group token resolution to minimize RPC calls when resolving ATAs
This commit is contained in:
Phil Chen 2022-05-24 01:49:08 +09:00 committed by GitHub
parent f3f0ce24e7
commit f79dde5570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 82 deletions

View File

@ -14,6 +14,7 @@
"tiny-invariant": "^1.2.0" "tiny-invariant": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/decimal.js": "^7.4.0", "@types/decimal.js": "^7.4.0",
"@types/jest": "^26.0.24", "@types/jest": "^26.0.24",
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",

View File

@ -1667,6 +1667,21 @@
"code": 6037, "code": 6037,
"name": "AmountInAboveMaximum", "name": "AmountInAboveMaximum",
"msg": "Amount in above maximum threshold" "msg": "Amount in above maximum threshold"
},
{
"code": 6038,
"name": "TickArraySequenceInvalidIndex",
"msg": "Invalid index for tick array sequence"
},
{
"code": 6039,
"name": "AmountCalcOverflow",
"msg": "Amount calculated overflows"
},
{
"code": 6040,
"name": "AmountRemainingOverflow",
"msg": "Amount remaining overflows"
} }
] ]
} }

View File

@ -1667,6 +1667,21 @@ export type Whirlpool = {
"code": 6037, "code": 6037,
"name": "AmountInAboveMaximum", "name": "AmountInAboveMaximum",
"msg": "Amount in above maximum threshold" "msg": "Amount in above maximum threshold"
},
{
"code": 6038,
"name": "TickArraySequenceInvalidIndex",
"msg": "Invalid index for tick array sequence"
},
{
"code": 6039,
"name": "AmountCalcOverflow",
"msg": "Amount calculated overflows"
},
{
"code": 6040,
"name": "AmountRemainingOverflow",
"msg": "Amount remaining overflows"
} }
] ]
}; };
@ -3340,6 +3355,21 @@ export const IDL: Whirlpool = {
"code": 6037, "code": 6037,
"name": "AmountInAboveMaximum", "name": "AmountInAboveMaximum",
"msg": "Amount in above maximum threshold" "msg": "Amount in above maximum threshold"
},
{
"code": 6038,
"name": "TickArraySequenceInvalidIndex",
"msg": "Invalid index for tick array sequence"
},
{
"code": 6039,
"name": "AmountCalcOverflow",
"msg": "Amount calculated overflows"
},
{
"code": 6040,
"name": "AmountRemainingOverflow",
"msg": "Amount remaining overflows"
} }
] ]
}; };

View File

@ -2,7 +2,7 @@ import {
AddressUtil, AddressUtil,
deriveATA, deriveATA,
Percentage, Percentage,
resolveOrCreateATA, resolveOrCreateATAs,
TransactionBuilder, TransactionBuilder,
ZERO, ZERO,
} from "@orca-so/common-sdk"; } from "@orca-so/common-sdk";
@ -168,7 +168,13 @@ export class WhirlpoolImpl implements Whirlpool {
invariant(TickUtil.checkTickInBounds(tickLower), "tickLower is out of bounds."); invariant(TickUtil.checkTickInBounds(tickLower), "tickLower is out of bounds.");
invariant(TickUtil.checkTickInBounds(tickUpper), "tickUpper is out of bounds."); invariant(TickUtil.checkTickInBounds(tickUpper), "tickUpper is out of bounds.");
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput; const {
liquidityAmount: liquidity,
tokenMaxA,
tokenMaxB,
tokenEstA,
tokenEstB,
} = liquidityInput;
invariant(liquidity.gt(new u64(0)), "liquidity must be greater than zero"); invariant(liquidity.gt(new u64(0)), "liquidity must be greater than zero");
@ -215,20 +221,18 @@ export class WhirlpoolImpl implements Whirlpool {
); );
txBuilder.addInstruction(positionIx).addSigner(positionMintKeypair); txBuilder.addInstruction(positionIx).addSigner(positionMintKeypair);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA( const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection, this.ctx.connection,
sourceWallet, sourceWallet,
whirlpool.tokenMintA, [
() => this.fetcher.getAccountRentExempt(), { tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: tokenMaxA },
tokenMaxA { tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: tokenMaxB },
); ],
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA( () => this.fetcher.getAccountRentExempt()
this.ctx.connection,
sourceWallet,
whirlpool.tokenMintB,
() => this.fetcher.getAccountRentExempt(),
tokenMaxB
); );
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
txBuilder.addInstruction(tokenOwnerAccountAIx); txBuilder.addInstruction(tokenOwnerAccountAIx);
txBuilder.addInstruction(tokenOwnerAccountBIx); txBuilder.addInstruction(tokenOwnerAccountBIx);
@ -257,6 +261,8 @@ export class WhirlpoolImpl implements Whirlpool {
liquidityAmount: liquidity, liquidityAmount: liquidity,
tokenMaxA, tokenMaxA,
tokenMaxB, tokenMaxB,
tokenEstA,
tokenEstB,
whirlpool: this.address, whirlpool: this.address,
positionAuthority: positionWallet, positionAuthority: positionWallet,
position: positionPda.publicKey, position: positionPda.publicKey,
@ -311,18 +317,16 @@ export class WhirlpoolImpl implements Whirlpool {
const txBuilder = new TransactionBuilder(this.ctx.provider); const txBuilder = new TransactionBuilder(this.ctx.provider);
const resolvedAssociatedTokenAddresses: Record<string, PublicKey> = {}; const resolvedAssociatedTokenAddresses: Record<string, PublicKey> = {};
const { address: tokenOwnerAccountA, ...createTokenOwnerAccountAIx } = await resolveOrCreateATA( const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection, this.ctx.connection,
destinationWallet, destinationWallet,
whirlpool.tokenMintA, [{ tokenMint: whirlpool.tokenMintA }, { tokenMint: whirlpool.tokenMintB }],
() => this.fetcher.getAccountRentExempt()
);
const { address: tokenOwnerAccountB, ...createTokenOwnerAccountBIx } = await resolveOrCreateATA(
this.ctx.connection,
destinationWallet,
whirlpool.tokenMintB,
() => this.fetcher.getAccountRentExempt() () => this.fetcher.getAccountRentExempt()
); );
const { address: tokenOwnerAccountA, ...createTokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...createTokenOwnerAccountBIx } = ataB;
txBuilder.addInstruction(createTokenOwnerAccountAIx).addInstruction(createTokenOwnerAccountBIx); txBuilder.addInstruction(createTokenOwnerAccountAIx).addInstruction(createTokenOwnerAccountBIx);
resolvedAssociatedTokenAddresses[whirlpool.tokenMintA.toBase58()] = tokenOwnerAccountA; resolvedAssociatedTokenAddresses[whirlpool.tokenMintA.toBase58()] = tokenOwnerAccountA;
resolvedAssociatedTokenAddresses[whirlpool.tokenMintB.toBase58()] = tokenOwnerAccountB; resolvedAssociatedTokenAddresses[whirlpool.tokenMintB.toBase58()] = tokenOwnerAccountB;
@ -351,6 +355,8 @@ export class WhirlpoolImpl implements Whirlpool {
liquidityAmount: decreaseLiqQuote.liquidityAmount, liquidityAmount: decreaseLiqQuote.liquidityAmount,
tokenMinA: decreaseLiqQuote.tokenMinA, tokenMinA: decreaseLiqQuote.tokenMinA,
tokenMinB: decreaseLiqQuote.tokenMinB, tokenMinB: decreaseLiqQuote.tokenMinB,
tokenEstA: decreaseLiqQuote.tokenEstA,
tokenEstB: decreaseLiqQuote.tokenEstB,
whirlpool: position.whirlpool, whirlpool: position.whirlpool,
positionAuthority: positionWallet, positionAuthority: positionWallet,
position: positionAddress, position: positionAddress,
@ -390,22 +396,20 @@ export class WhirlpoolImpl implements Whirlpool {
const whirlpool = this.data; const whirlpool = this.data;
const txBuilder = new TransactionBuilder(this.ctx.provider); const txBuilder = new TransactionBuilder(this.ctx.provider);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA( const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection, this.ctx.connection,
wallet, wallet,
whirlpool.tokenMintA, [
() => this.fetcher.getAccountRentExempt(), { tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: aToB ? estimatedAmountIn : ZERO },
aToB ? estimatedAmountIn : ZERO { tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: !aToB ? estimatedAmountIn : ZERO },
],
() => this.fetcher.getAccountRentExempt()
); );
txBuilder.addInstruction(tokenOwnerAccountAIx);
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA( const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
this.ctx.connection, const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
wallet,
whirlpool.tokenMintB, txBuilder.addInstruction(tokenOwnerAccountAIx);
() => this.fetcher.getAccountRentExempt(),
!aToB ? estimatedAmountIn : ZERO
);
txBuilder.addInstruction(tokenOwnerAccountBIx); txBuilder.addInstruction(tokenOwnerAccountBIx);
const targetSqrtPriceLimitX64 = sqrtPriceLimit || this.getDefaultSqrtPriceLimit(aToB); const targetSqrtPriceLimitX64 = sqrtPriceLimit || this.getDefaultSqrtPriceLimit(aToB);

View File

@ -42,6 +42,8 @@ export type DecreaseLiquidityParams = {
export type DecreaseLiquidityInput = { export type DecreaseLiquidityInput = {
tokenMinA: BN; tokenMinA: BN;
tokenMinB: BN; tokenMinB: BN;
tokenEstA: BN;
tokenEstB: BN;
liquidityAmount: BN; liquidityAmount: BN;
}; };

View File

@ -50,6 +50,8 @@ export type IncreaseLiquidityParams = {
export type IncreaseLiquidityInput = { export type IncreaseLiquidityInput = {
tokenMaxA: u64; tokenMaxA: u64;
tokenMaxB: u64; tokenMaxB: u64;
tokenEstA: u64;
tokenEstB: u64;
liquidityAmount: u64; liquidityAmount: u64;
}; };

View File

@ -106,15 +106,14 @@ function quotePositionBelowRange(param: DecreaseLiquidityQuoteParam): DecreaseLi
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
const minTokenA = adjustForSlippage( const tokenEstA = getTokenAFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false);
getTokenAFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false), const tokenMinA = adjustForSlippage(tokenEstA, slippageTolerance, false);
slippageTolerance,
false
);
return { return {
tokenMinA: minTokenA, tokenMinA,
tokenMinB: ZERO, tokenMinB: ZERO,
tokenEstA,
tokenEstB: ZERO,
liquidityAmount: liquidity, liquidityAmount: liquidity,
}; };
} }
@ -126,20 +125,16 @@ function quotePositionInRange(param: DecreaseLiquidityQuoteParam): DecreaseLiqui
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
const minTokenA = adjustForSlippage( const tokenEstA = getTokenAFromLiquidity(liquidity, sqrtPriceX64, sqrtPriceUpperX64, false);
getTokenAFromLiquidity(liquidity, sqrtPriceX64, sqrtPriceUpperX64, false), const tokenMinA = adjustForSlippage(tokenEstA, slippageTolerance, false);
slippageTolerance, const tokenEstB = getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceX64, false);
false const tokenMinB = adjustForSlippage(tokenEstB, slippageTolerance, false);
);
const minTokenB = adjustForSlippage(
getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceX64, false),
slippageTolerance,
false
);
return { return {
tokenMinA: minTokenA, tokenMinA,
tokenMinB: minTokenB, tokenMinB,
tokenEstA,
tokenEstB,
liquidityAmount: liquidity, liquidityAmount: liquidity,
}; };
} }
@ -150,15 +145,14 @@ function quotePositionAboveRange(param: DecreaseLiquidityQuoteParam): DecreaseLi
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
const minTokenB = adjustForSlippage( const tokenEstB = getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false);
getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false), const tokenMinB = adjustForSlippage(tokenEstB, slippageTolerance, false);
slippageTolerance,
false
);
return { return {
tokenMinA: ZERO, tokenMinA: ZERO,
tokenMinB: minTokenB, tokenMinB,
tokenEstA: ZERO,
tokenEstB,
liquidityAmount: liquidity, liquidityAmount: liquidity,
}; };
} }

View File

@ -129,6 +129,8 @@ function quotePositionBelowRange(param: IncreaseLiquidityQuoteParam): IncreaseLi
return { return {
tokenMaxA: ZERO, tokenMaxA: ZERO,
tokenMaxB: ZERO, tokenMaxB: ZERO,
tokenEstA: ZERO,
tokenEstB: ZERO,
liquidityAmount: ZERO, liquidityAmount: ZERO,
}; };
} }
@ -143,16 +145,19 @@ function quotePositionBelowRange(param: IncreaseLiquidityQuoteParam): IncreaseLi
false false
); );
const maxTokenA = adjustForSlippage( const tokenEstA = getTokenAFromLiquidity(
getTokenAFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, true), liquidityAmount,
slippageTolerance, sqrtPriceLowerX64,
sqrtPriceUpperX64,
true true
); );
const maxTokenB = ZERO; const tokenMaxA = adjustForSlippage(tokenEstA, slippageTolerance, true);
return { return {
tokenMaxA: maxTokenA, tokenMaxA,
tokenMaxB: maxTokenB, tokenMaxB: ZERO,
tokenEstA,
tokenEstB: ZERO,
liquidityAmount, liquidityAmount,
}; };
} }
@ -172,30 +177,32 @@ function quotePositionInRange(param: IncreaseLiquidityQuoteParam): IncreaseLiqui
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
let [tokenAmountA, tokenAmountB] = tokenMintA.equals(inputTokenMint) let [tokenEstA, tokenEstB] = tokenMintA.equals(inputTokenMint)
? [inputTokenAmount, undefined] ? [inputTokenAmount, undefined]
: [undefined, inputTokenAmount]; : [undefined, inputTokenAmount];
let liquidityAmount: BN; let liquidityAmount: BN;
if (tokenAmountA) { if (tokenEstA) {
liquidityAmount = getLiquidityFromTokenA(tokenAmountA, sqrtPriceX64, sqrtPriceUpperX64, false); liquidityAmount = getLiquidityFromTokenA(tokenEstA, sqrtPriceX64, sqrtPriceUpperX64, false);
tokenAmountA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true); tokenEstA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true);
tokenAmountB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true); tokenEstB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true);
} else if (tokenAmountB) { } else if (tokenEstB) {
liquidityAmount = getLiquidityFromTokenB(tokenAmountB, sqrtPriceLowerX64, sqrtPriceX64, false); liquidityAmount = getLiquidityFromTokenB(tokenEstB, sqrtPriceLowerX64, sqrtPriceX64, false);
tokenAmountA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true); tokenEstA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true);
tokenAmountB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true); tokenEstB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true);
} else { } else {
throw new Error("invariant violation"); throw new Error("invariant violation");
} }
const maxTokenA = adjustForSlippage(tokenAmountA, slippageTolerance, true); const tokenMaxA = adjustForSlippage(tokenEstA, slippageTolerance, true);
const maxTokenB = adjustForSlippage(tokenAmountB, slippageTolerance, true); const tokenMaxB = adjustForSlippage(tokenEstB, slippageTolerance, true);
return { return {
tokenMaxA: maxTokenA, tokenMaxA,
tokenMaxB: maxTokenB, tokenMaxB,
tokenEstA: tokenEstA!,
tokenEstB: tokenEstB!,
liquidityAmount, liquidityAmount,
}; };
} }
@ -214,6 +221,8 @@ function quotePositionAboveRange(param: IncreaseLiquidityQuoteParam): IncreaseLi
return { return {
tokenMaxA: ZERO, tokenMaxA: ZERO,
tokenMaxB: ZERO, tokenMaxB: ZERO,
tokenEstA: ZERO,
tokenEstB: ZERO,
liquidityAmount: ZERO, liquidityAmount: ZERO,
}; };
} }
@ -227,16 +236,19 @@ function quotePositionAboveRange(param: IncreaseLiquidityQuoteParam): IncreaseLi
false false
); );
const maxTokenA = ZERO; const tokenEstB = getTokenBFromLiquidity(
const maxTokenB = adjustForSlippage( liquidityAmount,
getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, true), sqrtPriceLowerX64,
slippageTolerance, sqrtPriceUpperX64,
true true
); );
const tokenMaxB = adjustForSlippage(tokenEstB, slippageTolerance, true);
return { return {
tokenMaxA: maxTokenA, tokenMaxA: ZERO,
tokenMaxB: maxTokenB, tokenMaxB,
tokenEstA: ZERO,
tokenEstB,
liquidityAmount, liquidityAmount,
}; };
} }

View File

@ -703,6 +703,13 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/bn.js@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68"
integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==
dependencies:
"@types/node" "*"
"@types/connect@^3.4.33": "@types/connect@^3.4.33":
version "3.4.35" version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"