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
110 changes: 107 additions & 3 deletions src/model/orca/pool/orca-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import {
} from "../../../public";
import {
createApprovalInstruction,
createDepositInstruction,
createSwapInstruction,
} from "../../../public/utils/web3/instructions/pool-instructions";
import { QuotePoolParams, QuoteBuilderFactory } from "../../quote/quote-builder";
import { OrcaPoolParams } from "./pool-types";
import { OrcaPoolParams, OrcaPoolToken } from "./pool-types";

export class OrcaPoolImpl implements OrcaPool {
private connection: Connection;
Expand All @@ -35,12 +36,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 +181,107 @@ 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,
poolTokenAmount_U64
);

// 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(transferTokenBInstruction)
.addInstruction(depositInstruction)
.build();
}

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

// TODO
// 1. Create the user's token account for the tokens being withdrawn, if necessary
// 2. Approve transfer of the pool token
// 3. Create the withdraw function
// 4. Add above instructions into TransactionBuilder and build the payload

return await new TransactionBuilder(this.connection, ownerAddress).build();
}
}
30 changes: 30 additions & 0 deletions src/public/pools/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,36 @@ export type OrcaPool = {
amountIn: Decimal | OrcaU64,
minimumAmountOut: Decimal | OrcaU64
) => Promise<TransactionPayload>;

/**
*
* @param owner
* @param poolTokenAmount
* @param maximumTokenA
* @param maximumTokenB
* @return
*/
deposit: (
owner: Keypair,
poolTokenAmount: Decimal | OrcaU64,
maximumTokenA: Decimal | OrcaU64,
maximumTokenB: Decimal | OrcaU64
) => Promise<TransactionPayload>;

/**
*
* @param owner
* @param poolTokenAmount
* @param minimumTokenA
* @param minimumTokenB
* @return
*/
withdraw: (
owner: Keypair,
poolTokenAmount: Decimal | OrcaU64,
minimumTokenA: Decimal | OrcaU64,
minimumTokenB: Decimal | OrcaU64
) => Promise<TransactionPayload>;
};

/**
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
43 changes: 41 additions & 2 deletions src/public/utils/web3/instructions/pool-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
import { TokenSwap } from "@solana/spl-token-swap";
import { Keypair, PublicKey } from "@solana/web3.js";
import { OrcaPoolParams, OrcaPoolToken } from "../../../../model/orca/pool/pool-types";
import { OrcaToken } from "../../../pools";
import { ORCA_TOKEN_SWAP_ID } from "../../constants";
import { Instruction } from "../../models";

export const createApprovalInstruction = (
ownerAddress: PublicKey,
approveAmount: u64,
tokenUserAddress: PublicKey
tokenUserAddress: PublicKey,
userTransferAuthority?: Keypair
): { userTransferAuthority: Keypair } & Instruction => {
const userTransferAuthority = new Keypair();
userTransferAuthority = userTransferAuthority || new Keypair();

const approvalInstruction = Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
Expand Down Expand Up @@ -78,3 +80,40 @@ export const createSwapInstruction = async (
signers: [owner],
};
};

export const createDepositInstruction = async (
poolParams: OrcaPoolParams,
userTransferAuthority: PublicKey,
userTokenAPublicKey: PublicKey,
userTokenBPublicKey: PublicKey,
userPoolTokenPublicKey: PublicKey,
poolTokenAmount: u64,
maximumTokenA: u64,
maximumTokenB: u64,
tokenAPublicKey: PublicKey,
tokenBPublicKey: PublicKey,
owner: Keypair
): Promise<Instruction> => {
const depositInstruction = TokenSwap.depositAllTokenTypesInstruction(
poolParams.address,
poolParams.authority,
userTransferAuthority,
userTokenAPublicKey,
userTokenBPublicKey,
tokenAPublicKey,
tokenBPublicKey,
userPoolTokenPublicKey,
poolParams.poolTokenMint,
ORCA_TOKEN_SWAP_ID,
TOKEN_PROGRAM_ID,
poolTokenAmount,
maximumTokenA,
maximumTokenB
);

return {
instructions: [depositInstruction],
cleanupInstructions: [],
signers: [owner],
};
};