Skip to content
This repository has been archived by the owner on Jul 17, 2023. It is now read-only.

Pool add getDepositQuote, getWithdrawQuote api's #47

Merged
10 commits merged into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions src/model/orca/pool/orca-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import {
TransactionPayload,
Percentage,
resolveOrCreateAssociatedTokenAddress,
ZERO,
TokenInAmount,
getTokenInAmount,
DepositQuoteOutput,
WithdrawQuoteAmount,
WithdrawQuoteOutput,
getPoolTokenInAmount,
DecimalUtil,
} from "../../../public";
import {
createApprovalInstruction,
Expand Down Expand Up @@ -184,6 +192,57 @@ export class OrcaPoolImpl implements OrcaPool {
.build();
}

public async getDepositQuote(
maxTokenAIn: TokenInAmount,
maxTokenBIn: TokenInAmount,
slippage?: Decimal
): Promise<DepositQuoteOutput> {
const slippageTolerance =
slippage === undefined ? defaultSlippagePercentage : Percentage.fromDecimal(slippage);

const maxTokenAIn_U64 = await getTokenInAmount(this.connection, this.getTokenA(), maxTokenAIn);
const maxTokenBIn_U64 = await getTokenInAmount(this.connection, this.getTokenB(), maxTokenBIn);

const { inputTokenCount: tokenAAmount, outputTokenCount: tokenBAmount } = await getTokenCount(
this.connection,
this.poolParams,
this.getTokenA(),
this.getTokenB()
);
const lpSupply = await this.getLPSupply();

if (tokenAAmount.eq(ZERO) || tokenBAmount.eq(ZERO)) {
return {
minPoolTokenAmountOut: OrcaU64.fromU64(ZERO, lpSupply.scale),
maxTokenAIn: OrcaU64.fromU64(maxTokenAIn_U64, this.getTokenA().scale),
maxTokenBIn: OrcaU64.fromU64(maxTokenBIn_U64, this.getTokenB().scale),
};
}

const poolTokenAmountWithA = maxTokenAIn_U64
.mul(slippageTolerance.denominator)
.mul(lpSupply.toU64())
.div(tokenAAmount)
.div(slippageTolerance.numerator.add(slippageTolerance.denominator));

const poolTokenAmountWithB = maxTokenBIn_U64
.mul(slippageTolerance.denominator)
.mul(lpSupply.toU64())
.div(tokenBAmount)
.div(slippageTolerance.numerator.add(slippageTolerance.denominator));

// Pick the smaller value of the two to calculate the minimum poolTokenAmount out
const minPoolTokenAmountOut_U64 = poolTokenAmountWithA.gt(poolTokenAmountWithB)
? poolTokenAmountWithB
: poolTokenAmountWithA;

return {
minPoolTokenAmountOut: OrcaU64.fromU64(minPoolTokenAmountOut_U64, lpSupply.scale),
maxTokenAIn: OrcaU64.fromU64(maxTokenAIn_U64, this.getTokenA().scale),
maxTokenBIn: OrcaU64.fromU64(maxTokenBIn_U64, this.getTokenB().scale),
};
}

public async deposit(
owner: Keypair | PublicKey,
maxTokenAIn: Decimal | OrcaU64,
Expand Down Expand Up @@ -270,6 +329,82 @@ export class OrcaPoolImpl implements OrcaPool {
.build();
}

public async getWithdrawQuote(
amountIn: WithdrawQuoteAmount,
slippage?: Decimal
): Promise<WithdrawQuoteOutput> {
const slippageTolerance =
slippage === undefined ? defaultSlippagePercentage : Percentage.fromDecimal(slippage);

const lpSupply = await this.getLPSupply();
const { inputTokenCount: tokenAAmount, outputTokenCount: tokenBAmount } = await getTokenCount(
this.connection,
this.poolParams,
this.getTokenA(),
this.getTokenB()
);

let poolTokenIn_U64 = ZERO;
if (amountIn.type === "constraintToken") {
const constraintToken = this.getTokenA().mint.equals(amountIn.constraintTokenMint)
? this.getTokenA()
: this.getTokenB();
const constraintTokenAmount = this.getTokenA().mint.equals(amountIn.constraintTokenMint)
? tokenAAmount
: tokenBAmount;
const constraintTokenIn_U64 = await getTokenInAmount(
this.connection,
constraintToken,
amountIn.constraintTokenAmountIn
);
const constraintRatio = DecimalUtil.fromU64(constraintTokenIn_U64, constraintToken.scale).div(
DecimalUtil.fromU64(constraintTokenAmount, constraintToken.scale)
);
poolTokenIn_U64 = DecimalUtil.toU64(
constraintRatio.mul(lpSupply.toDecimal()),
lpSupply.scale
);
} else {
poolTokenIn_U64 = await getPoolTokenInAmount(
this.connection,
this.poolParams,
amountIn.poolTokenAmountIn
);
}

if (poolTokenIn_U64.eq(ZERO)) {
return {
maxPoolTokenAmountIn: OrcaU64.fromU64(poolTokenIn_U64, lpSupply.scale),
minTokenAOut: OrcaU64.fromU64(ZERO, this.getTokenA().scale),
minTokenBOut: OrcaU64.fromU64(ZERO, this.getTokenB().scale),
};
}

const minTokenAOut = new OrcaU64(
poolTokenIn_U64
.mul(slippageTolerance.denominator)
.mul(tokenAAmount)
.div(lpSupply.toU64())
.div(slippageTolerance.numerator.add(slippageTolerance.denominator)),
this.getTokenA().scale
);

const minTokenBOut = new OrcaU64(
poolTokenIn_U64
.mul(slippageTolerance.denominator)
.mul(tokenBAmount)
.div(lpSupply.toU64())
.div(slippageTolerance.numerator.add(slippageTolerance.denominator)),
this.getTokenB().scale
);

return {
maxPoolTokenAmountIn: OrcaU64.fromU64(poolTokenIn_U64, lpSupply.scale),
minTokenAOut,
minTokenBOut,
};
}

public async withdraw(
owner: Keypair | PublicKey,
poolTokenAmountIn: Decimal | OrcaU64,
Expand Down
71 changes: 69 additions & 2 deletions src/public/pools/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,47 @@ import Decimal from "decimal.js";
import { OrcaU64 } from "..";
import { TransactionPayload } from "../utils";

/* TokenInAmount types */
export type TokenInAmount = PercentInAmount | CountInAmount;

export type PercentInAmount = {
value: Decimal | OrcaU64;
ownerPublicKey: PublicKey;
type: "percent";
};

export type CountInAmount = {
value: Decimal | OrcaU64;
type: "count";
};

/* DepositQuote types */
export type DepositQuoteOutput = {
minPoolTokenAmountOut: OrcaU64;
maxTokenAIn: OrcaU64;
maxTokenBIn: OrcaU64;
};

/* WithdrawQuote types */
export type WithdrawQuoteAmount = PoolTokenInAmount | ConstraintTokenInAmount;

export type PoolTokenInAmount = {
poolTokenAmountIn: TokenInAmount;
type: "poolToken";
};

export type ConstraintTokenInAmount = {
constraintTokenAmountIn: TokenInAmount;
constraintTokenMint: PublicKey;
type: "constraintToken";
};

export type WithdrawQuoteOutput = {
minTokenAOut: OrcaU64;
minTokenBOut: OrcaU64;
maxPoolTokenAmountIn: OrcaU64;
};

/**
* Allows interactions with an Orca liquidity pool.
*/
Expand Down Expand Up @@ -39,13 +80,13 @@ export type OrcaPool = {
*
* @param inputTokenId The token you want to trade from
* @param inputAmount The amount of token you would to trade
* @param slippage The slippage in percentage you are willing to take in this trade
* @param slippage An optional slippage in percentage you are willing to take in this trade (default: 0.1%)
* @return Returns a quote on the exchanged token based on the input token amount
*/
getQuote: (
inputToken: OrcaToken,
inputAmount: Decimal | OrcaU64,
slippage: Decimal
slippage?: Decimal
) => Promise<Quote>;

/**
Expand All @@ -69,6 +110,20 @@ export type OrcaPool = {
minimumAmountOut: Decimal | OrcaU64
) => Promise<TransactionPayload>;

/**
* Get minPoolTokenAmountOut to be used for deposit.
*
* @param maxTokenAIn The amount of token to deposit for token A (either raw count or percentage of owned)
* @param maxTokenBIn The amount of token to deposit for token B (either raw count or percentgae of owned)
* @param slippage An optional slippage in percentage you are willing to take in deposit (default: 0.1%)
* @return Input for deposit
*/
getDepositQuote: (
maxTokenAIn: TokenInAmount,
maxTokenBIn: TokenInAmount,
slippage?: Decimal
) => Promise<DepositQuoteOutput>;

/**
* Perform a deposit: send tokenA and tokenB, and receive a poolToken in return.
* Fee for the transaction will be paid by the owner's wallet.
Expand All @@ -90,6 +145,18 @@ export type OrcaPool = {
minPoolTokenAmountOut: Decimal | OrcaU64
) => Promise<TransactionPayload>;

/**
* Get minTokenAOut and mintTokenBOut amounts to be used for withdraw.
*
* @param amountIn The amount of pool tokens to withdraw, amount expressed either directly in pool token or in one of the paired tokens
* @param slippage An optional slippage in percentage you are willing to take in withdraw (default: 0.1%)
* @return Input for withdraw
*/
getWithdrawQuote: (
amountIn: WithdrawQuoteAmount,
slippage?: Decimal
) => Promise<WithdrawQuoteOutput>;

/**
* Perform a withdraw: send poolToken, and receive tokenA and tokenB in return.
* Fee for the transaction will be paid by the owner's wallet.
Expand Down
79 changes: 76 additions & 3 deletions src/public/utils/web3/get-token-count.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { u64 } from "@solana/spl-token";
import { Connection } from "@solana/web3.js";
import { OrcaPoolToken } from "../..";
import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
import { Connection, PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { DecimalUtil, OrcaU64, SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, U64Utils } from "..";
import { OrcaPoolToken, TokenInAmount } from "../..";
import { solToken } from "../../../constants/tokens";
import { OrcaPoolParams } from "../../../model/orca/pool/pool-types";
import { deserializeAccount } from "./deserialize-account";

Expand Down Expand Up @@ -44,3 +47,73 @@ export async function getTokenCount(
outputTokenCount: new u64(outputTokenAccount.amount),
};
}

async function getUserTokenCount(
connection: Connection,
ownerPublicKey: PublicKey,
tokenMint: PublicKey,
tokenScale: number
): Promise<Decimal> {
// Special case: SOL doesn't have ATA
if (tokenMint === solToken.mint) {
const balance = await connection.getBalance(ownerPublicKey);
return DecimalUtil.fromU64(new u64(balance), solToken.scale);
}

const ownerATAPublicKey = (
await PublicKey.findProgramAddress(
[ownerPublicKey.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMint.toBuffer()],
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
)
)[0];
const accountInfo = await connection.getAccountInfo(ownerATAPublicKey);
const tokenAccount = accountInfo && deserializeAccount(accountInfo.data);

if (!tokenAccount) {
throw new Error(`Unable to fetch user account for token with mint ${tokenMint}`);
}

return DecimalUtil.fromU64(tokenAccount.amount, tokenScale);
}

export async function getTokenInAmount(
connection: Connection,
token: OrcaPoolToken,
tokenInAmount: TokenInAmount
): Promise<u64> {
if (tokenInAmount.type === "count") {
return U64Utils.toTokenU64(tokenInAmount.value, token, `tokenInAmount-${token.name}`);
}

const totalUserTokenCount = await getUserTokenCount(
connection,
tokenInAmount.ownerPublicKey,
token.mint,
token.scale
);
const percentage =
tokenInAmount.value instanceof OrcaU64 ? tokenInAmount.value.toDecimal() : tokenInAmount.value;
const amount = totalUserTokenCount.mul(percentage);
return U64Utils.toTokenU64(amount, token, `tokenInAmount-${token.name}`);
}

export async function getPoolTokenInAmount(
connection: Connection,
pool: OrcaPoolParams,
tokenInAmount: TokenInAmount
): Promise<u64> {
if (tokenInAmount.type === "count") {
return U64Utils.toPoolU64(tokenInAmount.value, pool, "tokenInAmount");
}

const totalUserTokenCount = await getUserTokenCount(
connection,
tokenInAmount.ownerPublicKey,
pool.poolTokenMint,
pool.poolTokenDecimals
);
const percentage =
tokenInAmount.value instanceof OrcaU64 ? tokenInAmount.value.toDecimal() : tokenInAmount.value;
const amount = totalUserTokenCount.mul(percentage);
return U64Utils.toPoolU64(amount, pool, "tokenInAmount");
}