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

[Pool] Support Pool Deposit / Withdraw #35

Merged
10 commits merged into from
Sep 8, 2021
166 changes: 164 additions & 2 deletions src/model/orca/pool/orca-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import {
TransactionBuilder,
OrcaPool,
OrcaToken,
OrcaPoolToken,
Quote,
TransactionPayload,
Percentage,
resolveOrCreateAssociatedTokenAddress,
} from "../../../public";
import {
createApprovalInstruction,
createDepositInstruction,
createSwapInstruction,
createWithdrawInstruction,
} from "../../../public/utils/web3/instructions/pool-instructions";
import { QuotePoolParams, QuoteBuilderFactory } from "../../quote/quote-builder";
import { OrcaPoolParams } from "./pool-types";
Expand All @@ -35,12 +38,12 @@ export class OrcaPoolImpl implements OrcaPool {
this.poolParams = config;
}

public getTokenA(): OrcaToken {
public getTokenA(): OrcaPoolToken {
const tokenId = this.poolParams.tokenIds[0];
return this.poolParams.tokens[tokenId];
}

public getTokenB(): OrcaToken {
public getTokenB(): OrcaPoolToken {
const tokenId = this.poolParams.tokenIds[1];
return this.poolParams.tokens[tokenId];
}
Expand Down Expand Up @@ -180,4 +183,163 @@ export class OrcaPoolImpl implements OrcaPool {
.addInstruction(swapInstruction)
.build();
}

public async deposit(
owner: Keypair,
poolTokenAmount: Decimal | OrcaU64,
maximumTokenA: Decimal | OrcaU64,
maximumTokenB: Decimal | OrcaU64
): Promise<TransactionPayload> {
const ownerAddress = owner.publicKey;
const tokenA = this.getTokenA();
const tokenB = this.getTokenB();

const maximumTokenA_U64 = U64Utils.toTokenU64(maximumTokenA, tokenA, "maximumTokenA");
const maximumTokenB_U64 = U64Utils.toTokenU64(maximumTokenB, tokenB, "maximumTokenB");
const poolTokenAmount_U64 = U64Utils.toPoolU64(
poolTokenAmount,
this.poolParams,
"poolTokenAmount"
);

// If tokenA is SOL, this will create a new wSOL account
// Otherwise, get tokenA's associated token account
const { address: userTokenAPublicKey, ...resolveTokenAInstrucitons } =
await resolveOrCreateAssociatedTokenAddress(
this.connection,
owner,
tokenA.mint,
maximumTokenA_U64
);

// If tokenB is SOL, this will create a new wSOL account
// Otherwise, get tokenB's associated token account
const { address: userTokenBPublicKey, ...resolveTokenBInstrucitons } =
await resolveOrCreateAssociatedTokenAddress(
this.connection,
owner,
tokenB.mint,
maximumTokenB_U64
);

// If the user lacks the pool token account, create it
const { address: userPoolTokenPublicKey, ...resolvePoolTokenInstructions } =
await resolveOrCreateAssociatedTokenAddress(
this.connection,
owner,
this.poolParams.poolTokenMint
);

// Approve transfer of the tokens being deposited
const { userTransferAuthority, ...transferTokenAInstruction } = createApprovalInstruction(
ownerAddress,
maximumTokenA_U64,
userTokenAPublicKey
);
const { ...transferTokenBInstruction } = createApprovalInstruction(
ownerAddress,
maximumTokenB_U64,
userTokenBPublicKey,
userTransferAuthority
);

// Create the deposit instruction
const depositInstruction = await createDepositInstruction(
this.poolParams,
userTransferAuthority.publicKey,
userTokenAPublicKey,
userTokenBPublicKey,
userPoolTokenPublicKey,
poolTokenAmount_U64,
maximumTokenA_U64,
maximumTokenB_U64,
tokenA.addr,
tokenB.addr,
owner
);

return await new TransactionBuilder(this.connection, ownerAddress)
.addInstruction(resolveTokenAInstrucitons)
.addInstruction(resolveTokenBInstrucitons)
.addInstruction(resolvePoolTokenInstructions)
.addInstruction(transferTokenAInstruction)
.addInstruction(transferTokenBInstruction)
.addInstruction(depositInstruction)
.build();
}

public async withdraw(
owner: Keypair,
poolTokenAmount: Decimal | OrcaU64,
minimumTokenA: Decimal | OrcaU64,
minimumTokenB: Decimal | OrcaU64
): Promise<TransactionPayload> {
const ownerAddress = owner.publicKey;
const tokenA = this.getTokenA();
const tokenB = this.getTokenB();

const minimumTokenA_U64 = U64Utils.toTokenU64(minimumTokenA, tokenA, "minimumTokenA");
const minimumTokenB_U64 = U64Utils.toTokenU64(minimumTokenB, tokenB, "minimumTokenB");
const poolTokenAmount_U64 = U64Utils.toPoolU64(
poolTokenAmount,
this.poolParams,
"poolTokenAmount"
);

// Create a token account for tokenA, if necessary
const { address: userTokenAPublicKey, ...resolveTokenAInstrucitons } =
await resolveOrCreateAssociatedTokenAddress(
this.connection,
owner,
tokenA.mint,
minimumTokenA_U64
);

// Create a token account for tokenB, if necessary
const { address: userTokenBPublicKey, ...resolveTokenBInstrucitons } =
await resolveOrCreateAssociatedTokenAddress(
this.connection,
owner,
tokenB.mint,
minimumTokenB_U64
);

// Get user's poolToken token account
const { address: userPoolTokenPublicKey, ...resolvePoolTokenInstructions } =
await resolveOrCreateAssociatedTokenAddress(
this.connection,
owner,
this.poolParams.poolTokenMint
);

// Approve transfer of pool token
const { userTransferAuthority, ...transferPoolTokenInstruction } = createApprovalInstruction(
ownerAddress,
poolTokenAmount_U64,
userPoolTokenPublicKey
);

// Create the withdraw instruction
const withdrawInstruction = await createWithdrawInstruction(
this.poolParams,
userTransferAuthority.publicKey,
userTokenAPublicKey,
userTokenBPublicKey,
userPoolTokenPublicKey,
poolTokenAmount_U64,
minimumTokenA_U64,
minimumTokenB_U64,
tokenA.addr,
tokenB.addr,
owner
);

return await new TransactionBuilder(this.connection, ownerAddress)
.addInstruction(resolveTokenAInstrucitons)
.addInstruction(resolveTokenBInstrucitons)
.addInstruction(resolvePoolTokenInstructions)
.addInstruction(transferPoolTokenInstruction)
.addInstruction(withdrawInstruction)
.build();
}
}
10 changes: 1 addition & 9 deletions src/model/orca/pool/pool-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PublicKey } from "@solana/web3.js";
import { OrcaToken } from "../../..";
import { OrcaPoolToken } from "../../..";
import { Percentage } from "../../../public";

/**
Expand Down Expand Up @@ -28,14 +28,6 @@ export type OrcaPoolParams = {
feeStructure: FeeStructure;
};

/**
* An Orca Token within an OrcaPool
* @param addr The public key for this token for this Orca Pool
*/
export type OrcaPoolToken = OrcaToken & {
addr: PublicKey;
};

export enum CurveType {
ConstantProduct,
ConstantPrice,
Expand Down
56 changes: 53 additions & 3 deletions src/public/pools/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Keypair, PublicKey, TransactionSignature } from "@solana/web3.js";
import { Keypair, PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { OrcaU64 } from "..";
import { TransactionPayload } from "../utils";
Expand All @@ -11,13 +11,13 @@ export type OrcaPool = {
* Query the token of tokenA in this pool.
* @returns Returns the token id of tokenA in this pool
*/
getTokenA: () => OrcaToken;
getTokenA: () => OrcaPoolToken;

/**
* Query the token of tokenB in this pool.
* @returns Returns the token id of tokenB in this pool
*/
getTokenB: () => OrcaToken;
getTokenB: () => OrcaPoolToken;

/**
* Query the balance for an user address
Expand Down Expand Up @@ -68,6 +68,48 @@ export type OrcaPool = {
amountIn: Decimal | OrcaU64,
minimumAmountOut: Decimal | OrcaU64
) => Promise<TransactionPayload>;

/**
* 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.
*
* NOTE:
* 1. Associated Token Address initialization instructions will be appended if the ATA of the specified token does not exist in the user's wallet
* 2. OrcaU64 must have the same scale as the corresponding token scale value
*
* @param owner The keypair for the user's wallet
* @param poolTokenAmount The amount of poolToken to receive
* @param maximumTokenA The maximum amount of tokenA to send
* @param maximumTokenB The maximum amount of tokenB to send
* @return The transaction signature of the deposit instruction
*/
deposit: (
owner: Keypair,
poolTokenAmount: Decimal | OrcaU64,
maximumTokenA: Decimal | OrcaU64,
maximumTokenB: Decimal | OrcaU64
) => Promise<TransactionPayload>;

/**
* Perform a withdraw: send poolToken, and receive tokenA and tokenB in return.
* Fee for the transaction will be paid by the owner's wallet.
*
* NOTE:
* 1. Associated Token Address initialization instructions will be appended if the ATA of the specified token does not exist in the user's wallet
* 2. OrcaU64 must have the same scale as the corresponding token scale value
*
* @param owner The keypair for the user's wallet
* @param poolTokenAmount The amount of poolToken to send
* @param minimumTokenA The minimum amount of tokenA to receive
* @param minimumTokenB The minimum amount of tokenB to receive
* @return The transaction signature of the withdraw instruction
*/
withdraw: (
owner: Keypair,
poolTokenAmount: Decimal | OrcaU64,
minimumTokenA: Decimal | OrcaU64,
minimumTokenB: Decimal | OrcaU64
) => Promise<TransactionPayload>;
};

/**
Expand All @@ -84,6 +126,14 @@ export type OrcaToken = {
scale: number;
};

/**
* An Orca Token within an OrcaPool
* @param addr The public key for this token for this Orca Pool
*/
export type OrcaPoolToken = OrcaToken & {
addr: PublicKey;
};

export type Quote = {
/**
* Returns the rate of exchange given the trade amount. Fees are included.
Expand Down
14 changes: 14 additions & 0 deletions src/public/utils/numbers/u64-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { u64 } from "@solana/spl-token";
import Decimal from "decimal.js";
import { OrcaToken, OrcaU64 } from "../..";
import { OrcaPoolParams } from "../../../model/orca/pool/pool-types";
import { DecimalUtil } from "./decimal-utils";

export const ZERO = new u64(0);
Expand All @@ -20,6 +21,19 @@ export class U64Utils {
return DecimalUtil.toU64(input, token.scale);
}

public static toPoolU64(input: Decimal | OrcaU64, pool: OrcaPoolParams, varName: string) {
if (input instanceof OrcaU64) {
if (input.scale !== pool.poolTokenDecimals) {
throw new Error(
`${varName}'s scale of ${input.scale} does not match pool's decimal of ${pool.poolTokenDecimals}`
);
}
return input.toU64();
}

return DecimalUtil.toU64(input, pool.poolTokenDecimals);
}

// Note: divisor input variable modified in place
// https://github.com/solana-labs/solana-program-library/blob/master/libraries/math/src/checked_ceil_div.rs#L5-L22
public static ceilingDivision(dividend: u64, divisor: u64): [u64, u64] {
Expand Down
3 changes: 2 additions & 1 deletion src/public/utils/web3/get-token-count.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { u64 } from "@solana/spl-token";
import { Connection } from "@solana/web3.js";
import { OrcaPoolParams, OrcaPoolToken } from "../../../model/orca/pool/pool-types";
import { OrcaPoolToken } from "../..";
import { OrcaPoolParams } from "../../../model/orca/pool/pool-types";
import { deserializeAccount } from "./deserialize-account";

export type PoolTokenCount = {
Expand Down
Loading