@saberhq/stableswap-sdk
Version:
Solana SDK for Saber's StableSwap program.
259 lines (238 loc) • 7.05 kB
text/typescript
import type { Provider } from "@saberhq/solana-contrib";
import { TransactionEnvelope } from "@saberhq/solana-contrib";
import type { TokenAmount } from "@saberhq/token-utils";
import {
buildCreateTokenAccountTX,
createATAInstruction,
createInitMintTX,
getATAAddressSync,
SPLToken,
TOKEN_PROGRAM_ID,
} from "@saberhq/token-utils";
import type { PublicKey } from "@solana/web3.js";
import { Keypair } from "@solana/web3.js";
import type { InitializeSwapInstruction } from "../index.js";
import { findSwapAuthorityKeySync, SWAP_PROGRAM_ID } from "../index.js";
import type { SwapTokenInfo } from "../instructions/swap.js";
import type { InitializeNewStableSwapArgs } from "./initialize.js";
import { createInitializeStableSwapInstructionsRaw } from "./initialize.js";
const initializeSwapTokenInfoSync = ({
provider,
mint,
authority,
admin,
rentExemptTokenAccountBalance,
}: {
rentExemptTokenAccountBalance: number;
provider: Provider;
mint: PublicKey;
authority: PublicKey;
admin: PublicKey;
}): {
info: SwapTokenInfo;
instructions: TransactionEnvelope;
} => {
// Create Swap Token Account
const { key: tokenAccount, tx: createSwapTokenAccountInstructions } =
buildCreateTokenAccountTX({
provider,
mint,
owner: authority,
payer: provider.wallet.publicKey,
rentExemptAccountBalance: rentExemptTokenAccountBalance,
});
// Create Admin Fee Account
const { key: adminFeeAccount, tx: createAdminFeeAccountInstructions } =
buildCreateTokenAccountTX({
provider,
mint,
owner: admin,
payer: provider.wallet.publicKey,
rentExemptAccountBalance: rentExemptTokenAccountBalance,
});
return {
info: {
mint,
reserve: tokenAccount,
adminFeeAccount: adminFeeAccount,
},
instructions: createSwapTokenAccountInstructions.combine(
createAdminFeeAccountInstructions,
),
};
};
export type InitializeNewStableSwapSimpleArgs = Omit<
InitializeNewStableSwapArgs,
| "swapProgramID"
| "tokenAMint"
| "tokenBMint"
| "useAssociatedAccountForInitialLP"
> & {
reservesA: TokenAmount;
reservesB: TokenAmount;
};
/**
* Creates a set of instructions to create a new StableSwap instance.
*
* After calling this, you must sign this transaction with the accounts:
* - payer -- Account that holds the SOL to seed the account.
* - args.config.stableSwapAccount -- This account is used once then its key is no longer relevant
* - all returned signers
*/
export const createInitializeStableSwapInstructionsSimple = async ({
provider,
adminAccount,
ampFactor,
fees,
reservesA,
reservesB,
initialLiquidityProvider = adminAccount,
swapAccountSigner = Keypair.generate(),
poolTokenMintSigner = Keypair.generate(),
seedPoolAccounts,
}: InitializeNewStableSwapSimpleArgs): Promise<{
initializeArgs: InitializeSwapInstruction;
/**
* Lamports needed to be rent exempt.
*/
balanceNeeded: number;
instructions: {
/**
* Create accounts for the LP token
*/
createLPTokenMint: TransactionEnvelope;
/**
* Create LP token account for the initial LP
*/
createInitialLPTokenAccount: TransactionEnvelope;
/**
* Create accounts for swap token A
*/
createSwapTokenAAccounts: TransactionEnvelope;
/**
* Create accounts for swap token B
*/
createSwapTokenBAccounts: TransactionEnvelope;
/**
* Seed the accounts for the pool
*/
seedPoolAccounts: TransactionEnvelope;
/**
* Initialize the swap
*/
initializeSwap: TransactionEnvelope;
};
}> => {
const rentExemptTokenAccountBalance =
await SPLToken.getMinBalanceRentForExemptAccount(provider.connection);
const rentExemptMintBalance = await SPLToken.getMinBalanceRentForExemptMint(
provider.connection,
);
// Create swap account if not specified
const swapAccount = swapAccountSigner.publicKey;
// Create authority and nonce
const [authority, nonce] = findSwapAuthorityKeySync(swapAccount);
// Create LP token mint
const { decimals } = reservesA.token;
if (reservesA.token.decimals !== reservesB.token.decimals) {
throw new Error("decimals mismatch");
}
const createLPTokenMint = createInitMintTX({
provider,
mintKP: poolTokenMintSigner,
mintAuthority: authority,
decimals,
rentExemptMintBalance,
});
const poolTokenMint = poolTokenMintSigner.publicKey;
// Create initial LP token account
const initialLPAccount = getATAAddressSync({
mint: poolTokenMint,
owner: initialLiquidityProvider,
});
const createInitialLPTokenAccount = new TransactionEnvelope(provider, [
createATAInstruction({
address: getATAAddressSync({
mint: poolTokenMint,
owner: initialLiquidityProvider,
}),
mint: poolTokenMint,
owner: initialLiquidityProvider,
payer: provider.wallet.publicKey,
}),
]);
// Create Swap Token A account
const { info: tokenA, instructions: createSwapTokenAAccounts } =
initializeSwapTokenInfoSync({
provider,
mint: reservesA.token.mintAccount,
authority,
admin: adminAccount,
rentExemptTokenAccountBalance,
});
// Create Swap Token B account
const { info: tokenB, instructions: createSwapTokenBAccounts } =
initializeSwapTokenInfoSync({
provider,
mint: reservesB.token.mintAccount,
authority,
admin: adminAccount,
rentExemptTokenAccountBalance,
});
// Seed the swap's Token A and token B accounts with tokens
// On testnet, this is usually a mint.
// On mainnet, this is usually a token transfer.
const seedPoolAccountsResult = seedPoolAccounts({
tokenAAccount: tokenA.reserve,
tokenBAccount: tokenB.reserve,
});
const seedPoolAccountsTX = new TransactionEnvelope(
provider,
[...seedPoolAccountsResult.instructions],
[...seedPoolAccountsResult.signers],
);
const initializeSwapInstruction: InitializeSwapInstruction = {
config: {
swapAccount: swapAccount,
authority,
swapProgramID: SWAP_PROGRAM_ID,
tokenProgramID: TOKEN_PROGRAM_ID,
},
adminAccount,
tokenA,
tokenB,
poolTokenMint,
destinationPoolTokenAccount: initialLPAccount,
nonce,
ampFactor,
fees,
};
const {
balanceNeeded: swapBalanceNeeded,
instructions: initializeStableSwapInstructions,
} = await createInitializeStableSwapInstructionsRaw({
provider,
initializeSwapInstruction,
});
const initializeSwap = new TransactionEnvelope(
provider,
[...initializeStableSwapInstructions],
[swapAccountSigner],
);
const instructions = {
createLPTokenMint,
createInitialLPTokenAccount,
createSwapTokenAAccounts,
createSwapTokenBAccounts,
seedPoolAccounts: seedPoolAccountsTX,
initializeSwap,
};
return {
initializeArgs: initializeSwapInstruction,
balanceNeeded:
rentExemptMintBalance +
swapBalanceNeeded +
rentExemptTokenAccountBalance * 2,
instructions,
};
};