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"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/decimal.js": "^7.4.0",
"@types/jest": "^26.0.24",
"@types/mocha": "^9.0.0",

View File

@ -1667,6 +1667,21 @@
"code": 6037,
"name": "AmountInAboveMaximum",
"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,
"name": "AmountInAboveMaximum",
"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,
"name": "AmountInAboveMaximum",
"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,
deriveATA,
Percentage,
resolveOrCreateATA,
resolveOrCreateATAs,
TransactionBuilder,
ZERO,
} 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(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");
@ -215,20 +221,18 @@ export class WhirlpoolImpl implements Whirlpool {
);
txBuilder.addInstruction(positionIx).addSigner(positionMintKeypair);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA(
const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection,
sourceWallet,
whirlpool.tokenMintA,
() => this.fetcher.getAccountRentExempt(),
tokenMaxA
);
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA(
this.ctx.connection,
sourceWallet,
whirlpool.tokenMintB,
() => this.fetcher.getAccountRentExempt(),
tokenMaxB
[
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: tokenMaxA },
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: tokenMaxB },
],
() => this.fetcher.getAccountRentExempt()
);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
txBuilder.addInstruction(tokenOwnerAccountAIx);
txBuilder.addInstruction(tokenOwnerAccountBIx);
@ -257,6 +261,8 @@ export class WhirlpoolImpl implements Whirlpool {
liquidityAmount: liquidity,
tokenMaxA,
tokenMaxB,
tokenEstA,
tokenEstB,
whirlpool: this.address,
positionAuthority: positionWallet,
position: positionPda.publicKey,
@ -311,18 +317,16 @@ export class WhirlpoolImpl implements Whirlpool {
const txBuilder = new TransactionBuilder(this.ctx.provider);
const resolvedAssociatedTokenAddresses: Record<string, PublicKey> = {};
const { address: tokenOwnerAccountA, ...createTokenOwnerAccountAIx } = await resolveOrCreateATA(
const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection,
destinationWallet,
whirlpool.tokenMintA,
() => this.fetcher.getAccountRentExempt()
);
const { address: tokenOwnerAccountB, ...createTokenOwnerAccountBIx } = await resolveOrCreateATA(
this.ctx.connection,
destinationWallet,
whirlpool.tokenMintB,
[{ tokenMint: whirlpool.tokenMintA }, { tokenMint: whirlpool.tokenMintB }],
() => this.fetcher.getAccountRentExempt()
);
const { address: tokenOwnerAccountA, ...createTokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...createTokenOwnerAccountBIx } = ataB;
txBuilder.addInstruction(createTokenOwnerAccountAIx).addInstruction(createTokenOwnerAccountBIx);
resolvedAssociatedTokenAddresses[whirlpool.tokenMintA.toBase58()] = tokenOwnerAccountA;
resolvedAssociatedTokenAddresses[whirlpool.tokenMintB.toBase58()] = tokenOwnerAccountB;
@ -351,6 +355,8 @@ export class WhirlpoolImpl implements Whirlpool {
liquidityAmount: decreaseLiqQuote.liquidityAmount,
tokenMinA: decreaseLiqQuote.tokenMinA,
tokenMinB: decreaseLiqQuote.tokenMinB,
tokenEstA: decreaseLiqQuote.tokenEstA,
tokenEstB: decreaseLiqQuote.tokenEstB,
whirlpool: position.whirlpool,
positionAuthority: positionWallet,
position: positionAddress,
@ -390,22 +396,20 @@ export class WhirlpoolImpl implements Whirlpool {
const whirlpool = this.data;
const txBuilder = new TransactionBuilder(this.ctx.provider);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA(
const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection,
wallet,
whirlpool.tokenMintA,
() => this.fetcher.getAccountRentExempt(),
aToB ? estimatedAmountIn : ZERO
[
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: aToB ? estimatedAmountIn : ZERO },
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: !aToB ? estimatedAmountIn : ZERO },
],
() => this.fetcher.getAccountRentExempt()
);
txBuilder.addInstruction(tokenOwnerAccountAIx);
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA(
this.ctx.connection,
wallet,
whirlpool.tokenMintB,
() => this.fetcher.getAccountRentExempt(),
!aToB ? estimatedAmountIn : ZERO
);
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
txBuilder.addInstruction(tokenOwnerAccountAIx);
txBuilder.addInstruction(tokenOwnerAccountBIx);
const targetSqrtPriceLimitX64 = sqrtPriceLimit || this.getDefaultSqrtPriceLimit(aToB);

View File

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

View File

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

View File

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

View File

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

View File

@ -703,6 +703,13 @@
dependencies:
"@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":
version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"