@candy-swap/candy-sdk
Version:
Candy SDK for Solana
386 lines (385 loc) • 18.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CandySDK = void 0;
const anchor = __importStar(require("@coral-xyz/anchor"));
const anchor_1 = require("@coral-xyz/anchor");
const web3_js_1 = require("@solana/web3.js");
const bn_js_1 = __importDefault(require("bn.js"));
const spl_token_1 = require("@solana/spl-token");
const utils_1 = require("./utils");
const types_1 = require("./idl/types");
const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2");
__exportStar(require("./utils"), exports);
const SWAP_THRESHOLD = 30000000000; // 30 SOL in lamports
class CandySDK {
constructor(programId, provider) {
this.cluster = "mainnet"; // 'mainnet' | 'devnet'
this.program = new anchor.Program(types_1.IDL, programId, provider);
this.provider = provider;
}
/**
* Create new token
* @param name Token name
* @param symbol Token symbol
* @param uri Token URI
* @param decimals Decimal places
* @param creator Creator public key
* @param mintPk Token mint public key
* @param swapFee Swap fee
* @param swapAdmin Swap admin public key
* @returns Transaction to be signed and related account information
*/
async createMint(name, symbol, uri, decimals, creator, mintPk, devFeeReciever) {
const [pool] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_pool"), mintPk.toBuffer()], this.program.programId);
const [dexCfgAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("CurveConfiguration")], this.program.programId);
const [cashier] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("cashier")], this.program.programId);
const [liquidityAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_account"), mintPk.toBuffer()], this.program.programId);
const token_account = await (0, spl_token_1.getAssociatedTokenAddress)(mintPk, liquidityAccount, true);
const tokenMetadataProgram = new anchor_1.web3.PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
const [metadataKeyPair] = anchor.web3.PublicKey.findProgramAddressSync([Buffer.from("metadata"), tokenMetadataProgram.toBuffer(), mintPk.toBuffer()], tokenMetadataProgram);
const [cfg] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("TokenCfg"), mintPk.toBuffer()], this.program.programId);
const tx = new anchor_1.web3.Transaction();
tx.add(await this.program.methods.mintInitialize()
.accounts({
mint: mintPk,
dexConfigurationAccount: dexCfgAccount,
cfg: cfg,
admin: creator,
systemProgram: anchor_1.web3.SystemProgram.programId,
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
devFeeReciever: devFeeReciever ?? null,
}).instruction());
// create liquidity account token associated account
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(creator, token_account, liquidityAccount, //owner
mintPk, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
// 添加创建代币指令
const createIx = await this.program.methods
.createMint({
name: name, symbol: symbol, uri: uri,
decimals: decimals,
})
.accounts({
mint: mintPk,
payer: creator,
metadata: metadataKeyPair,
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
systemProgram: anchor_1.web3.SystemProgram.programId,
rent: anchor_1.web3.SYSVAR_RENT_PUBKEY,
tokenMetadataProgram: tokenMetadataProgram,
associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
tokenAccount: token_account,
cashier: cashier,
liquidityAccount: liquidityAccount,
pool: pool,
dexConfigurationAccount: dexCfgAccount,
}).instruction();
tx.add(createIx);
return {
liquidityAta: token_account,
pool,
instructions: tx.instructions,
mint: mintPk,
tokenAccount: token_account,
dexConfigurationAccount: dexCfgAccount,
cfg: cfg,
};
}
/**
* Swap tokens
* @param mint Token mint address
* @param amount Swap amount
* @param user User address
* @param style Trading style
* @param minOut Minimum output amount
* @returns Unsigned transaction and related account information
*/
async swap(mint, params, user, swapAdmin, devFeeReceiver) {
const [pool] = await (0, utils_1.getLiquidityPoolAddress)(this.program.programId, mint);
const [liquidityAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_account"), mint.toBuffer()], this.program.programId);
const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(mint, user);
const poolTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(mint, liquidityAccount, true);
const [dexConfigurationAccount] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("CurveConfiguration")], this.program.programId);
const [swapAdminCfg] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("SwapAdmin"), mint.toBuffer()], this.program.programId);
const [cfg] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("TokenCfg"), mint.toBuffer()], this.program.programId);
const dexConfiguration = await this.program.account.curveConfiguration.fetch(dexConfigurationAccount);
const instructions = [];
const swapInstruction = await this.program.methods
.swap(params)
.accounts({
dexConfigurationAccount,
pool,
cfg: cfg,
liquidityAccount: liquidityAccount,
mintTokenOne: mint,
poolTokenAccountOne: poolTokenAccount,
userTokenAccountOne: userTokenAccount,
user,
rent: web3_js_1.SYSVAR_RENT_PUBKEY,
systemProgram: web3_js_1.SystemProgram.programId,
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
swapAdmin: swapAdmin,
devFeeReceiver: devFeeReceiver ?? null,
sysFeeReceiver: dexConfiguration.sysFeeReceiver,
})
.instruction();
instructions.push(swapInstruction);
return {
instructions,
userTokenAccount,
poolTokenAccount,
};
}
/**
* Set active status
* @param mint Token mint address
* @param active Active status
* @returns Transaction to be signed and related account information
*/
async setActive(mint, active) {
const [dexCfgAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("CurveConfiguration")], this.program.programId);
const [pool] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_pool"), mint.toBuffer()], this.program.programId);
const tx = new anchor_1.web3.Transaction();
tx.add(await this.program.methods.setActive({ active }).accounts({
dexConfigurationAccount: dexCfgAccount,
pool: pool,
mint,
payer: this.provider.publicKey,
systemProgram: web3_js_1.SystemProgram.programId,
}).instruction());
return tx;
}
/**
* Set configuration
* @param params Configuration parameters
* @returns Transaction to be signed and related account information
*/
async setCfg(params) {
const [dexCfgAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("CurveConfiguration")], this.program.programId);
const [cfg] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("TokenCfg"), params.mint.toBuffer()], this.program.programId);
const tx = new anchor_1.web3.Transaction();
tx.add(await this.program.methods.setCfg({
swapAdmin: params.swapAdmin,
swapFee: params.swapFee,
developer: params.devFeeReceiver,
}).accounts({
dexConfigurationAccount: dexCfgAccount,
cfg: cfg,
mint: params.mint,
admin: this.provider.publicKey,
systemProgram: web3_js_1.SystemProgram.programId,
}).instruction());
return tx;
}
/**
* Set curve configuration
* @param params Curve configuration parameters
* @returns Transaction to be signed and related account information
*/
async setCurveCfg({ swapFee, swapAdmin, systemFee, mintFees, admin, }) {
const [dexCfgAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("CurveConfiguration")], this.program.programId);
const tx = new anchor_1.web3.Transaction();
tx.add(await this.program.methods.setCurveCfg({
swapFee,
swapAdmin: swapAdmin ?? null,
systemFee,
mintFees,
admin,
}).accounts({
dexConfigurationAccount: dexCfgAccount,
admin: this.provider.publicKey,
systemProgram: web3_js_1.SystemProgram.programId,
}).instruction());
return tx;
}
/**
* Get liquidity pool information
* @param mint Token mint address
*/
async getPoolInfo(mint) {
const [pool] = await (0, utils_1.getLiquidityPoolAddress)(this.program.programId, mint);
return await this.program.account.liquidityPool.fetch(pool);
}
/**
* Get project information, price and current progress
* @param mint Token mint address
* @returns Project information
*/
async getProjectInfo(mint) {
const [pool] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("liquidity_pool"), mint.toBuffer()], this.program.programId);
const [liquidityAccount] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("liquidity_account"), mint.toBuffer()], this.program.programId);
const poolInfo = await this.program.account.liquidityPool.fetch(pool);
const liquidityBalance = await this.program.provider.connection.getBalance(liquidityAccount);
const minRent = await this.program.provider.connection.getMinimumBalanceForRentExemption(0);
const progress = Math.min(100, ((liquidityBalance - minRent) * 100.0 / SWAP_THRESHOLD));
return {
currentPrice: {
reserveOne: new bn_js_1.default(poolInfo.reserveOne.toString()),
reserveTwo: new bn_js_1.default(poolInfo.reserveTwo.toString()),
},
progress: Math.max(0, progress),
liquidity: liquidityBalance,
};
}
/**
* Create CPMM pool
* @param baseToken Base token
* @param baseAmount Base token amount
* @param quoteAmount Quote token amount
* @returns
*/
async createCpmmPool(baseToken, baseAmount, quoteAmount) {
const raydium = await this.initSdk({ loadToken: true });
const mintA = await raydium.token.getTokenInfo(baseToken);
const mintB = await raydium.token.getTokenInfo(spl_token_1.NATIVE_MINT.toBase58());
const feeConfigs = await raydium.api.getCpmmConfigs();
if (raydium.cluster === 'devnet') {
feeConfigs.forEach((config) => {
config.id = (0, raydium_sdk_v2_1.getCpmmPdaAmmConfigId)(raydium_sdk_v2_1.DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_PROGRAM, config.index).publicKey.toBase58();
});
}
const programId = raydium.cluster === 'devnet' ? raydium_sdk_v2_1.DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_PROGRAM : raydium_sdk_v2_1.CREATE_CPMM_POOL_PROGRAM;
const poolFeeAccount = raydium.cluster === 'devnet' ? raydium_sdk_v2_1.DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_FEE_ACC : raydium_sdk_v2_1.CREATE_CPMM_POOL_FEE_ACC;
const { execute, extInfo } = await raydium.cpmm.createPool({
programId: programId, // devnet: DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_PROGRAM
poolFeeAccount: poolFeeAccount, // devnet: DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_PROGRAM
mintA,
mintB,
mintAAmount: baseAmount,
mintBAmount: quoteAmount,
startTime: new bn_js_1.default(0),
feeConfig: feeConfigs[0],
associatedOnly: false,
ownerInfo: {
useSOLBalance: true,
},
txVersion: 0,
});
// don't want to wait confirm, set sendAndConfirm to false or don't pass any params to execute
const { txId } = await execute({ sendAndConfirm: true });
console.log('pool created', {
txId,
poolKeys: Object.keys(extInfo.address).reduce((acc, cur) => ({
...acc,
[cur]: extInfo.address[cur].toString(),
}), {}),
});
return { pool: extInfo.address };
}
async initSdk(params) {
if (this.raydium)
return this.raydium;
const cluster = this.cluster;
const connection = this.provider.connection;
const owner = this.provider.wallet.publicKey;
this.raydium = await raydium_sdk_v2_1.Raydium.load({
owner,
connection,
cluster,
disableFeatureCheck: true,
disableLoadToken: !params?.loadToken,
blockhashCommitment: 'finalized',
});
return this.raydium;
}
/**
* Prepare liquidity
* @param mint Token mint address
* @returns
*/
async ammPrepare(mint) {
const [cashier] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("cashier")], this.program.programId);
const userAta = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.provider.publicKey);
const target = this.provider.publicKey;
const [liquidityAccount] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_account"), mint.toBuffer()], this.program.programId);
const liquidityTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, liquidityAccount, true);
const [pool] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_pool"), mint.toBuffer()], this.program.programId);
const tx = await this.program.methods.ammPrepare().accounts({
mint: mint,
payer: this.provider.publicKey,
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
systemProgram: anchor_1.web3.SystemProgram.programId,
rent: anchor_1.web3.SYSVAR_RENT_PUBKEY,
cashier: cashier,
target: target,
liquidityAccount: liquidityAccount,
poolTokenAccountOne: liquidityTokenAccount,
pool: pool,
targetTokenAccount: userAta,
}).signers([]).rpc();
return tx;
}
/**
* Calculate output amount
* @param mint Token mint address
* @param amountIn Input amount
* @param direction Trading direction
* @param slippage Slippage percentage
* @returns Output amount
*/
async computeAmountOut(mint, amountIn, direction, slippage = 0.01) {
if (slippage <= 0 || slippage >= 1) {
throw new Error('Slippage must be between 0 and 1');
}
const [pool] = anchor_1.web3.PublicKey.findProgramAddressSync([Buffer.from("liquidity_pool"), mint.toBuffer()], this.program.programId);
const poolInfo = await this.program.account.liquidityPool.fetch(pool);
const r1 = poolInfo.reserveOne; // token
const r2 = poolInfo.reserveTwo; // sol
let mintOut = new bn_js_1.default(0);
// buy
// r1 * r2 = (r2 + amountIn) * (r2 - mintOut)
// mintOut = r1 * r2 / (r2 + amountIn)
// sell
// r1 * r2 = (r2 - amountIn) * (r2 + mintOut)
// mintOut = r1 * r2 / (r2 - amountIn)
if (direction === 'buy') {
mintOut = r1.mul(r2).div(r2.add(amountIn));
}
else {
mintOut = r1.mul(r2).div(r2.sub(amountIn));
}
return mintOut.sub(mintOut.mul(new bn_js_1.default(slippage)));
}
}
exports.CandySDK = CandySDK;
exports.default = CandySDK;