@saberhq/stableswap-sdk
Version:
Solana SDK for Saber's StableSwap program.
384 lines (361 loc) • 10.1 kB
text/typescript
import { getProgramAddress } from "@saberhq/solana-contrib";
import type { ProgramAccount } from "@saberhq/token-utils";
import { TOKEN_PROGRAM_ID } from "@saberhq/token-utils";
import type { Connection, TransactionInstruction } from "@solana/web3.js";
import { PublicKey } from "@solana/web3.js";
import { SWAP_PROGRAM_ID } from "./constants.js";
import type { IExchange } from "./entities/exchange.js";
import type { StableSwapConfig } from "./instructions/index.js";
import * as instructions from "./instructions/index.js";
import type { StableSwapState } from "./state/index.js";
import { decodeSwap } from "./state/index.js";
import { StableSwapLayout } from "./state/layout.js";
import { loadProgramAccount } from "./util/account.js";
export interface StableSwapInfo {
config: StableSwapConfig;
state: StableSwapState;
}
/**
* Swap token A for token B
* @param userSource
* @param poolSource
* @param poolDestination
* @param userDestination
* @param amountIn
* @param minimumAmountOut
*/
export function createSaberSwapInstruction(
{ config, state }: StableSwapInfo,
args: Pick<
instructions.SwapInstruction,
| "userAuthority"
| "userSource"
| "userDestination"
| "poolSource"
| "poolDestination"
| "amountIn"
| "minimumAmountOut"
>,
): TransactionInstruction {
const adminDestination = args.poolDestination.equals(state.tokenA.reserve)
? state.tokenA.adminFeeAccount
: state.tokenB.adminFeeAccount;
return instructions.swapInstruction({
config: config,
...args,
adminDestination,
});
}
/**
* Deposit tokens into the pool.
*/
export function createSaberDepositInstruction(
{ config, state }: StableSwapInfo,
args: Pick<
instructions.DepositInstruction,
| "userAuthority"
| "sourceA"
| "sourceB"
| "poolTokenAccount"
| "tokenAmountA"
| "tokenAmountB"
| "minimumPoolTokenAmount"
>,
): TransactionInstruction {
return instructions.depositInstruction({
config: config,
tokenAccountA: state.tokenA.reserve,
tokenAccountB: state.tokenB.reserve,
poolTokenMint: state.poolTokenMint,
...args,
});
}
/**
* Withdraw tokens from the pool
*/
export function createSaberWithdrawInstruction(
{ config, state }: StableSwapInfo,
args: Pick<
instructions.WithdrawInstruction,
| "userAuthority"
| "userAccountA"
| "userAccountB"
| "sourceAccount"
| "poolTokenAmount"
| "minimumTokenA"
| "minimumTokenB"
>,
): TransactionInstruction {
return instructions.withdrawInstruction({
config: config,
poolMint: state.poolTokenMint,
tokenAccountA: state.tokenA.reserve,
tokenAccountB: state.tokenB.reserve,
adminFeeAccountA: state.tokenA.adminFeeAccount,
adminFeeAccountB: state.tokenB.adminFeeAccount,
...args,
});
}
/**
* Withdraw tokens from the pool
*/
export function createSaberWithdrawOneInstruction(
{ config, state }: StableSwapInfo,
args: Pick<
instructions.WithdrawOneInstruction,
| "userAuthority"
| "baseTokenAccount"
| "destinationAccount"
| "sourceAccount"
| "poolTokenAmount"
| "minimumTokenAmount"
>,
): TransactionInstruction {
const [quoteTokenAccount, adminDestinationAccount] =
args.baseTokenAccount.equals(state.tokenA.reserve)
? [state.tokenB.reserve, state.tokenA.adminFeeAccount]
: [state.tokenA.reserve, state.tokenB.adminFeeAccount];
return instructions.withdrawOneInstruction({
config: config,
poolMint: state.poolTokenMint,
quoteTokenAccount,
adminDestinationAccount,
...args,
});
}
export class StableSwap implements StableSwapInfo {
/**
* Constructor for new StableSwap client object.
* @param config
* @param state
*/
constructor(
readonly config: StableSwapConfig,
readonly state: StableSwapState,
) {}
/**
* Get the minimum balance for the token swap account to be rent exempt
*
* @return Number of lamports required
*/
static async getMinBalanceRentForExemptStableSwap(
connection: Connection,
): Promise<number> {
return await connection.getMinimumBalanceForRentExemption(
StableSwapLayout.span,
);
}
/**
* Load an onchain StableSwap program.
*
* @param connection A {@link Connection} to use.
* @param swapAccount The {@link PublicKey} of the swap account to load. You can obtain this pubkey by visiting [app.saber.so](https://app.saber.so/], navigating to the pool you want to load, and getting the "swap account" key.
* @param programID Address of the onchain StableSwap program.
*/
static async load(
connection: Connection,
swapAccount: PublicKey,
programID: PublicKey = SWAP_PROGRAM_ID,
): Promise<StableSwap> {
const data = await loadProgramAccount(connection, swapAccount, programID);
const authority = getSwapAuthorityKey(swapAccount, programID);
return StableSwap.loadWithData(swapAccount, data, authority, programID);
}
/**
* Loads an onchain StableSwap program from an {@link IExchange}.
*
* @param connection
* @param exchange
* @returns
*/
static async loadFromExchange(
connection: Connection,
exchange: IExchange,
): Promise<StableSwap> {
return StableSwap.load(
connection,
exchange.swapAccount,
exchange.programID,
);
}
/**
* Loads the swap object from a program account.
* @param data
* @returns
*/
static async fromProgramAccount(
data: ProgramAccount<StableSwapState>,
): Promise<StableSwap> {
const [authority] = await findSwapAuthorityKey(data.publicKey);
return StableSwap.fromProgramAccountWithAuthority(data, authority);
}
/**
* Loads the swap object from a program account.
* @param data
* @returns
*/
static fromData(data: ProgramAccount<StableSwapState>): StableSwap {
const authority = getSwapAuthorityKey(data.publicKey);
return StableSwap.fromProgramAccountWithAuthority(data, authority);
}
/**
* Loads the swap object from a program account, with the swap authority loaded.
* @param data
* @returns
*/
static fromProgramAccountWithAuthority(
data: ProgramAccount<StableSwapState>,
authority: PublicKey,
): StableSwap {
return new StableSwap(
{
swapAccount: data.publicKey,
swapProgramID: SWAP_PROGRAM_ID,
tokenProgramID: TOKEN_PROGRAM_ID,
authority,
},
data.account,
);
}
/**
* Loads a StableSwap instance with data.
*
* @param programID The program ID.
* @param swapAccount The address of the swap.
* @param swapAccountData The data of the swapAccount.
* @param authority The swap's authority.
* @returns
*/
static loadWithData(
swapAccount: PublicKey,
swapAccountData: Buffer,
authority: PublicKey,
programID: PublicKey = SWAP_PROGRAM_ID,
): StableSwap {
try {
const state = decodeSwap(swapAccountData);
if (!state.isInitialized) {
throw new Error(`Invalid token swap state`);
}
return new StableSwap(
{
swapAccount: swapAccount,
swapProgramID: programID,
tokenProgramID: TOKEN_PROGRAM_ID,
authority,
},
state,
);
} catch (e) {
throw Error(e as string);
}
}
/**
* Swap token A for token B
* @param userSource
* @param poolSource
* @param poolDestination
* @param userDestination
* @param amountIn
* @param minimumAmountOut
*/
swap(
args: Pick<
instructions.SwapInstruction,
| "userAuthority"
| "userSource"
| "userDestination"
| "poolSource"
| "poolDestination"
| "amountIn"
| "minimumAmountOut"
>,
): TransactionInstruction {
return createSaberSwapInstruction(this, args);
}
/**
* Deposit tokens into the pool.
*/
deposit(
args: Pick<
instructions.DepositInstruction,
| "userAuthority"
| "sourceA"
| "sourceB"
| "poolTokenAccount"
| "tokenAmountA"
| "tokenAmountB"
| "minimumPoolTokenAmount"
>,
): TransactionInstruction {
return createSaberDepositInstruction(this, args);
}
/**
* Withdraw tokens from the pool
*/
withdraw(
args: Pick<
instructions.WithdrawInstruction,
| "userAuthority"
| "userAccountA"
| "userAccountB"
| "sourceAccount"
| "poolTokenAmount"
| "minimumTokenA"
| "minimumTokenB"
>,
): TransactionInstruction {
return createSaberWithdrawInstruction(this, args);
}
/**
* Withdraw tokens from the pool
*/
withdrawOne(
args: Pick<
instructions.WithdrawOneInstruction,
| "userAuthority"
| "baseTokenAccount"
| "destinationAccount"
| "sourceAccount"
| "poolTokenAmount"
| "minimumTokenAmount"
>,
): TransactionInstruction {
return createSaberWithdrawOneInstruction(this, args);
}
}
/**
* Finds the swap authority address that is used to sign transactions on behalf of the swap.
*
* @param swapAccount
* @param swapProgramID
* @returns
*/
export const findSwapAuthorityKey = (
swapAccount: PublicKey,
swapProgramID: PublicKey = SWAP_PROGRAM_ID,
): Promise<[PublicKey, number]> =>
PublicKey.findProgramAddress([swapAccount.toBuffer()], swapProgramID);
/**
* Finds the swap authority address that is used to sign transactions on behalf of the swap.
*
* @param swapAccount
* @param swapProgramID
* @returns
*/
export const findSwapAuthorityKeySync = (
swapAccount: PublicKey,
swapProgramID: PublicKey = SWAP_PROGRAM_ID,
): [PublicKey, number] =>
PublicKey.findProgramAddressSync([swapAccount.toBuffer()], swapProgramID);
/**
* Finds the swap authority address that is used to sign transactions on behalf of the swap.
*
* @param swapAccount
* @param swapProgramID
* @returns
*/
export const getSwapAuthorityKey = (
swapAccount: PublicKey,
swapProgramID: PublicKey = SWAP_PROGRAM_ID,
): PublicKey => getProgramAddress([swapAccount.toBuffer()], swapProgramID);