Skip to content

Instantly share code, notes, and snippets.

@cloakd
Last active February 9, 2024 10:34
Show Gist options
  • Save cloakd/2e1fe5e6b3b44493308a711abc66828f to your computer and use it in GitHub Desktop.
Save cloakd/2e1fe5e6b3b44493308a711abc66828f to your computer and use it in GitHub Desktop.
import {
createAssociatedTokenAccountInstruction,
createInitializeMint2Instruction,
createSyncNativeInstruction,
createTransferCheckedInstruction,
getAssociatedTokenAddressSync,
getMinimumBalanceForRentExemptMint,
MINT_SIZE,
TOKEN_2022_PROGRAM_ID
} from "@solana/spl-token";
import {BN, web3} from "@project-serum/anchor";
import {blob, struct, u64, u8} from "@/api/marshmallow";
import {Numberu64, PoolConfig, TokenInput, TokenSwapLayout} from "./layouts";
import {SWAP_PROGRAM_ID, WSOL} from "./constants";
export const SWAP_PROGRAM_OWNER_FEE_ADDRESS = new web3.PublicKey("beamazjPnFT3JQoe16HjUxkpmHFfsHY6dTqf3VwBXzq");
export const TRADING_FEE_NUMERATOR = 20;
export const TRADING_FEE_DENOMINATOR = 10000;
export const OWNER_TRADING_FEE_NUMERATOR = 5;
export const OWNER_TRADING_FEE_DENOMINATOR = 10000;
export const OWNER_WITHDRAW_FEE_NUMERATOR = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 0 : 1;
export const OWNER_WITHDRAW_FEE_DENOMINATOR = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 0 : 10000;
export const HOST_FEE_NUMERATOR = 20;
export const HOST_FEE_DENOMINATOR = 10000;
export class CreateTokenPool {
poolTokenProgramId = TOKEN_2022_PROGRAM_ID //The program ID of the token program for the pool tokens
connection;
config;
payer;
owner;
tokenA;
tokenB;
tokenSwapAccount = web3.Keypair.generate()
tokenPoolMint = web3.Keypair.generate()
tokenAccountPool;
feeAccountAta;
//Filled when init called
tokenAccountA: web3.PublicKey = web3.PublicKey.default
tokenAccountB: web3.PublicKey = web3.PublicKey.default
authority: web3.PublicKey
constructor(connection: web3.Connection, payer: web3.PublicKey, owner: web3.PublicKey, tokenA: TokenInput, tokenB: TokenInput, config: PoolConfig) {
this.connection = connection
this.payer = payer
this.owner = owner
this.config = config
this.tokenA = tokenA
this.tokenB = tokenB
//Create ATAs
const [authority] = web3.PublicKey.findProgramAddressSync([this.tokenSwapAccount.publicKey.toBuffer()], SWAP_PROGRAM_ID);
this.authority = authority
//LP ATA
this.tokenAccountPool = getAssociatedTokenAddressSync(this.tokenPoolMint.publicKey, this.owner, true, TOKEN_2022_PROGRAM_ID)
const ownerKey = SWAP_PROGRAM_OWNER_FEE_ADDRESS || owner;
this.feeAccountAta = getAssociatedTokenAddressSync(this.tokenPoolMint.publicKey, ownerKey, false, TOKEN_2022_PROGRAM_ID)
}
async initializeTransaction() {
const {transaction, signers} = await this.initializeTransactionInstruction()
const bhash = await this.connection.getLatestBlockhash("confirmed")
transaction.recentBlockhash = bhash.blockhash
transaction.feePayer = this.payer
return {transaction, signers}
}
async createTransaction() {
const {transaction, signers} = await this.createTransactionInstruction()
const bhash = await this.connection.getLatestBlockhash("confirmed")
transaction.recentBlockhash = bhash.blockhash
transaction.feePayer = this.payer
return {transaction, signers}
}
async initializeTransactionInstruction() {
const transaction = new web3.Transaction();
const signers = [
this.tokenPoolMint,
];
//Create the pool mint
transaction.add(
web3.SystemProgram.createAccount({
fromPubkey: this.payer,
newAccountPubkey: this.tokenPoolMint.publicKey,
space: MINT_SIZE,
lamports: await getMinimumBalanceForRentExemptMint(this.connection),
programId: TOKEN_2022_PROGRAM_ID,
}),
createInitializeMint2Instruction(this.tokenPoolMint.publicKey, 2, this.authority, null, TOKEN_2022_PROGRAM_ID)
)
//Create pool account
transaction.add(
createAssociatedTokenAccountInstruction(
this.payer,
this.tokenAccountPool,
this.owner,
this.tokenPoolMint.publicKey,
TOKEN_2022_PROGRAM_ID
)
)
//Create fee account
const ownerKey = SWAP_PROGRAM_OWNER_FEE_ADDRESS || this.owner;
transaction.add(
createAssociatedTokenAccountInstruction(
this.payer,
this.feeAccountAta,
ownerKey,
this.tokenPoolMint.publicKey,
TOKEN_2022_PROGRAM_ID
),
);
//Are the tokens native sol?
const tokenANative = this.tokenA.mint.equals(WSOL);
const tokenBNative = this.tokenB.mint.equals(WSOL);
//Create pool ATAs
this.tokenAccountA = getAssociatedTokenAddressSync(this.tokenA.mint, this.authority, true, this.tokenA.programID)
this.tokenAccountB = getAssociatedTokenAddressSync(this.tokenB.mint, this.authority, true, this.tokenB.programID)
if (tokenANative) {
transaction.add(
createAssociatedTokenAccountInstruction(this.payer, this.tokenAccountA, this.authority, this.tokenA.mint, this.tokenA.programID),
createSyncNativeInstruction(this.tokenAccountA))
} else {
transaction.add(
createAssociatedTokenAccountInstruction(this.payer, this.tokenAccountA, this.authority, this.tokenA.mint, this.tokenA.programID),
)
}
if (tokenBNative) {
transaction.add(
createAssociatedTokenAccountInstruction(this.payer, this.tokenAccountB, this.authority, this.tokenB.mint, this.tokenB.programID),
createSyncNativeInstruction(this.tokenAccountB))
} else {
transaction.add(
createAssociatedTokenAccountInstruction(this.payer, this.tokenAccountB, this.authority, this.tokenB.mint, this.tokenB.programID),
)
}
return {
transaction: transaction,
signers
}
}
async createTransactionInstruction() {
const transaction = new web3.Transaction()
// Create the pool
const balanceNeeded = await this.getMinBalanceRentForExemptTokenSwap(this.connection);
transaction.add(
web3.SystemProgram.createAccount({
fromPubkey: this.payer,
newAccountPubkey: this.tokenSwapAccount.publicKey,
lamports: balanceNeeded,
space: TokenSwapLayout.span,
programId: SWAP_PROGRAM_ID,
}),
);
//Transfer initial liquidity
const payerAtaA = getAssociatedTokenAddressSync(this.tokenA.mint, this.payer, false, this.tokenA.programID)
const payerAtaB = getAssociatedTokenAddressSync(this.tokenB.mint, this.payer, false, this.tokenB.programID)
const mintAInfo = await this.tokenA.getMintInfo(this.connection)
const mintBInfo = await this.tokenB.getMintInfo(this.connection)
if (this.tokenA.mint.equals(WSOL)) {
transaction.add(
web3.SystemProgram.transfer({
fromPubkey: this.payer,
toPubkey: this.tokenAccountA,
lamports: this.tokenA.amount,
}),
createSyncNativeInstruction(this.tokenAccountA),
)
} else {
transaction.add(
createTransferCheckedInstruction(payerAtaA, this.tokenA.mint, this.tokenAccountA, this.payer, this.tokenA.amount, mintAInfo.value.decimals, [], this.tokenA.programID))
}
if (this.tokenB.mint.equals(WSOL)) {
transaction.add(
web3.SystemProgram.transfer({
fromPubkey: this.payer,
toPubkey: this.tokenAccountB,
lamports: this.tokenB.amount,
}),
createSyncNativeInstruction(this.tokenAccountB),
)
} else {
transaction.add(
createTransferCheckedInstruction(payerAtaB, this.tokenB.mint, this.tokenAccountB, this.payer, this.tokenB.amount, mintBInfo.value.decimals, [], this.tokenB.programID))
}
const curveParam = this.config.curveType === 1 ? new Numberu64(1) : undefined
transaction.add(
this.createInitSwapInstruction(
this.tokenSwapAccount,
this.authority,
this.tokenAccountA,
this.tokenAccountB,
this.tokenPoolMint.publicKey,
this.feeAccountAta,
this.tokenAccountPool,
this.poolTokenProgramId,
SWAP_PROGRAM_ID,
this.config.tradeFee.denominator ? this.config.tradeFee.numerator : TRADING_FEE_NUMERATOR,
this.config.tradeFee.denominator || TRADING_FEE_DENOMINATOR,
this.config.ownerTradeFee.denominator ? this.config.ownerTradeFee.numerator : OWNER_TRADING_FEE_NUMERATOR,
this.config.ownerTradeFee.denominator || OWNER_TRADING_FEE_DENOMINATOR,
this.config.ownerWithdrawFee.denominator ? this.config.ownerWithdrawFee.numerator : OWNER_WITHDRAW_FEE_NUMERATOR,
this.config.ownerWithdrawFee.denominator || OWNER_WITHDRAW_FEE_DENOMINATOR,
this.config.hostFee.denominator ? this.config.hostFee.numerator : HOST_FEE_NUMERATOR,
this.config.hostFee.denominator || HOST_FEE_DENOMINATOR,
this.config.curveType,
curveParam
),
)
return {
transaction: transaction,
signers: [
this.tokenSwapAccount,
]
}
}
async getMinBalanceRentForExemptTokenSwap(
connection: web3.Connection,
): Promise<number> {
return await connection.getMinimumBalanceForRentExemption(
TokenSwapLayout.span,
);
}
createInitSwapInstruction(
tokenSwapAccount: web3.Keypair,
authority: web3.PublicKey,
tokenAccountA: web3.PublicKey,
tokenAccountB: web3.PublicKey,
tokenPool: web3.PublicKey,
feeAccount: web3.PublicKey,
tokenAccountPool: web3.PublicKey,
poolTokenProgramId: web3.PublicKey,
swapProgramId: web3.PublicKey,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
hostFeeNumerator: number,
hostFeeDenominator: number,
curveType: number,
curveParameters: Numberu64 = new Numberu64(0),
): web3.TransactionInstruction {
const keys = [
{pubkey: tokenSwapAccount.publicKey, isSigner: false, isWritable: true},
{pubkey: authority, isSigner: false, isWritable: false},
{pubkey: tokenAccountA, isSigner: false, isWritable: false},
{pubkey: tokenAccountB, isSigner: false, isWritable: false},
{pubkey: tokenPool, isSigner: false, isWritable: true},
{pubkey: feeAccount, isSigner: false, isWritable: false},
{pubkey: tokenAccountPool, isSigner: false, isWritable: true},
{pubkey: poolTokenProgramId, isSigner: false, isWritable: false},
];
const commandDataLayout = struct([
u8('instruction'),
u64('tradeFeeNumerator'),
u64('tradeFeeDenominator'),
u64('ownerTradeFeeNumerator'),
u64('ownerTradeFeeDenominator'),
u64('ownerWithdrawFeeNumerator'),
u64('ownerWithdrawFeeDenominator'),
u64('hostFeeNumerator'),
u64('hostFeeDenominator'),
u8('curveType'),
blob(32, 'curveParameters'),
])
let data = Buffer.alloc(1024);
// package curve parameters
// NOTE: currently assume all curves take a single parameter, u64 int
// the remaining 24 of the 32 bytes available are filled with 0s
const curveParamsBuffer = Buffer.alloc(16);
curveParameters.toBuffer().copy(curveParamsBuffer);
{
const encodeLength = commandDataLayout.encode(
{
instruction: 0, // InitializeSwap instruction
tradeFeeNumerator: new BN(tradeFeeNumerator),
tradeFeeDenominator: new BN(tradeFeeDenominator),
ownerTradeFeeNumerator: new BN(ownerTradeFeeNumerator),
ownerTradeFeeDenominator: new BN(ownerTradeFeeDenominator),
ownerWithdrawFeeNumerator: new BN(ownerWithdrawFeeNumerator),
ownerWithdrawFeeDenominator: new BN(ownerWithdrawFeeDenominator),
hostFeeNumerator: new BN(hostFeeNumerator),
hostFeeDenominator: new BN(hostFeeDenominator),
curveType,
curveParameters: curveParamsBuffer,
},
data,
);
data = data.slice(0, encodeLength);
}
return new web3.TransactionInstruction({
keys,
programId: swapProgramId,
data,
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment