UNPKG

@candy-swap/candy-sdk

Version:

Candy SDK for Solana

386 lines (385 loc) 18.2 kB
"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;