UNPKG

goodrdotfun-sdk

Version:

SDK for interacting with goodr.fun and Sonic on Solana

901 lines (900 loc) 44.5 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.GoodrFunProgramBase = exports.MPL_TOKEN_METADATA_PROGRAM_ID = exports.BONDING_CURVE_SEED = exports.GLOBAL_SEED = void 0; const GoodrFunIDL = __importStar(require("./idl/idl.json")); const anchor = __importStar(require("@coral-xyz/anchor")); const anchor_1 = require("@coral-xyz/anchor"); const bignumber_js_1 = require("bignumber.js"); const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const helper_1 = require("./helpers/helper"); const states_1 = require("./states"); const constant_1 = require("./constant"); exports.GLOBAL_SEED = 'global'; exports.BONDING_CURVE_SEED = 'bonding_curve'; exports.MPL_TOKEN_METADATA_PROGRAM_ID = new web3_js_1.PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'); class GoodrFunProgramBase { constructor(connection) { this.connection = connection; this.idl = Object.assign({}, GoodrFunIDL); } get program() { return new anchor.Program(this.idl, { connection: this.connection, }); } get accounts() { return this.program.account; } /** * Adds an event listener for the 'createEvent' event. * @param handler - The function to handle the event. */ onCreateEvent(handler) { return this.program.addEventListener('createEvent', handler); } /** * Adds an event listener for the 'completeEvent' event. * @param handler - The function to handle the event. */ onCompleteEvent(handler) { return this.program.addEventListener('completeEvent', handler); } /** * Adds an event listener for the 'tradeEvent' event. * @param handler - The function to handle the event. */ onTradeEvent(handler) { return this.program.addEventListener('tradeEvent', handler); } /** * Adds an event listener for the 'setParamsEvent' event. * @param handler - The function to handle the event. */ onSetParamsEvent(handler) { return this.program.addEventListener('setParamsEvent', handler); } /** * Removes event listeners by their ids. * @param eventIds - The ids of the event listeners to remove. */ removeListeners(eventIds) { console.log('Removing event listeners: ', eventIds); eventIds.forEach(eventId => { this.program.removeEventListener(eventId); }); } /** * Returns the metadata PDA for a given mint. * @param mint - The mint to get the metadata PDA for. * @returns The metadata PDA. */ metadataPDA({ mint }) { return web3_js_1.PublicKey.findProgramAddressSync([ Buffer.from('metadata'), new web3_js_1.PublicKey(exports.MPL_TOKEN_METADATA_PROGRAM_ID).toBuffer(), mint.toBuffer(), ], new web3_js_1.PublicKey(exports.MPL_TOKEN_METADATA_PROGRAM_ID))[0]; } /** * Returns the global PDA. * @returns The global PDA. */ get globalPDA() { return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(exports.GLOBAL_SEED)], this.program.programId)[0]; } /** * Returns the bonding curve PDA for a given mint. * @param mint - The mint to get the bonding curve PDA for. * @returns The bonding curve PDA. */ bondingCurvePDA({ mint }) { return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(exports.BONDING_CURVE_SEED), mint.toBuffer()], this.program.programId)[0]; } /** * Returns the bonding curve V2 PDA for a given mint (SONIC operations). * @param mint - The mint to get the bonding curve V2 PDA for. * @returns The bonding curve V2 PDA. */ bondingCurveV2PDA({ mint }) { return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constant_1.BONDING_CURVE_V2_SEED), mint.toBuffer()], this.program.programId)[0]; } /** * Helper function to determine token program for a mint. * @param mint - The mint to check * @returns The appropriate token program ID */ async getTokenProgramForMint(mint) { // SONIC tokens are always in TOKEN_2022_PROGRAM_ID const SONIC_MAINNET_MINT = 'mrujEYaN1oyQXDHeYNxBYpxWKVkQ2XsGxfznpifu4aL'; const SONIC_TESTNET_MINT = 'Dp3dL14gJMQDCxE2XZKd4Jz42TShQLY7gGzU2YgmVY2J'; const mintString = mint.toString(); // Force TOKEN_2022_PROGRAM_ID for SONIC tokens if (mintString === SONIC_MAINNET_MINT || mintString === SONIC_TESTNET_MINT) { console.log('🔧 SDK: Forcing TOKEN_2022_PROGRAM_ID for SONIC mint:', mintString); return spl_token_1.TOKEN_2022_PROGRAM_ID; } try { const mintInfo = await this.connection.getAccountInfo(mint); if (!mintInfo) { console.log('⚠️ SDK: Mint account not found, defaulting to TOKEN_2022_PROGRAM_ID'); return spl_token_1.TOKEN_2022_PROGRAM_ID; } if (mintInfo.owner.equals(spl_token_1.TOKEN_2022_PROGRAM_ID)) { console.log('✅ SDK: Detected TOKEN_2022_PROGRAM_ID for mint:', mintString); return spl_token_1.TOKEN_2022_PROGRAM_ID; } else if (mintInfo.owner.equals(spl_token_1.TOKEN_PROGRAM_ID)) { console.log('✅ SDK: Detected TOKEN_PROGRAM_ID for mint:', mintString); return spl_token_1.TOKEN_PROGRAM_ID; } else { console.log('❌ SDK: Invalid mint owner program, defaulting to TOKEN_2022_PROGRAM_ID'); return spl_token_1.TOKEN_2022_PROGRAM_ID; } } catch (error) { console.log('⚠️ SDK: Error detecting token program, defaulting to TOKEN_2022_PROGRAM_ID:', error); return spl_token_1.TOKEN_2022_PROGRAM_ID; } } /** * Returns the global state. * @returns The global state. */ async getGlobalState() { try { return await this.program.account.global.fetch(this.globalPDA); } catch (error) { return null; } } /** * Returns the global account. * @returns The global account. */ async getGlobalAccount() { try { const globalState = await this.program.account.global.fetch(this.globalPDA); const globalAccount = new states_1.GlobalAccount(globalState.initialized, globalState.authority, globalState.operatingWallet, new bignumber_js_1.BigNumber(globalState.initialVirtualTokenReserves.toString()), new bignumber_js_1.BigNumber(globalState.initialVirtualSolReserves.toString()), new bignumber_js_1.BigNumber(globalState.initialRealTokenReserves.toString()), new bignumber_js_1.BigNumber(globalState.tokenTotalSupply.toString()), globalState.operatingFeeBasisPoints.toNumber(), globalState.creatorFeeBasisPoints.toNumber()); return globalAccount; } catch (error) { return null; } } /** * Returns the bonding curve state for a given mint. * @param mint - The mint to get the bonding curve state for. * @returns The bonding curve state. */ async getBondingCurveState({ mint, }) { try { return await this.program.account.bondingCurve.fetch(this.bondingCurvePDA({ mint })); } catch (error) { return null; } } /** * Returns the bonding curve V2 state for a given mint (SONIC operations). * @param mint - The mint to get the bonding curve V2 state for. * @returns The bonding curve V2 state. */ async getBondingCurveV2State({ mint, }) { try { return await this.program.account.bondingCurveV2.fetch(this.bondingCurveV2PDA({ mint })); } catch (error) { return null; } } /** * Returns the bonding curve state for a given mint. * @param mint - The mint to get the bonding curve state for. * @returns The bonding curve state. */ async getBondingCurveAccount({ mint, }) { try { const bondingCurveState = await this.program.account.bondingCurve.fetch(this.bondingCurvePDA({ mint })); const bondingCurveAccount = new states_1.BondingCurveAccount(bondingCurveState.creator, new bignumber_js_1.BigNumber(bondingCurveState.virtualTokenReserves.toNumber()), new bignumber_js_1.BigNumber(bondingCurveState.virtualSolReserves.toNumber()), new bignumber_js_1.BigNumber(bondingCurveState.realTokenReserves.toNumber()), new bignumber_js_1.BigNumber(bondingCurveState.realSolReserves.toNumber()), new bignumber_js_1.BigNumber(bondingCurveState.tokenTotalSupply.toNumber()), bondingCurveState.complete); return bondingCurveAccount; } catch (error) { return null; } } /** * Initializes the global state. * @param authority - The authority to initialize the global state. * @returns The transaction. */ async initialize({ authority, }) { return await this.program.methods .initialize() .accountsPartial({ authority, global: this.globalPDA }) .transaction(); } /** * Sets the parameters for the global state. * @param authority - The authority to set the parameters. * @returns The transaction. */ async setParams({ authority, operatingWallet, initialVirtualTokenReserves, initialVirtualSolReserves, initialRealTokenReserves, tokenTotalSupply, operatingFeeBasisPoints, creatorFeeBasisPoints, }) { return await this.program.methods .setParams(operatingWallet, initialVirtualTokenReserves, initialVirtualSolReserves, initialRealTokenReserves, tokenTotalSupply, operatingFeeBasisPoints, creatorFeeBasisPoints) .accountsPartial({ authority, global: this.globalPDA }) .transaction(); } async updateAuthority({ authority, newAuthority, }) { return await this.program.methods .updateAuthority(newAuthority) .accountsPartial({ authority, global: this.globalPDA }) .transaction(); } /** * Adds donation destinations to the global state. * @param authority - The authority to add the donation destinations. * @param donationDestinations - The donation destinations to add. * @returns The transaction. */ async addDonationDestinations({ authority, donationDestinations, }) { return await this.program.methods .addDonationDestinations(donationDestinations) .accountsPartial({ authority, global: this.globalPDA }) .transaction(); } /** * Removes donation destinations from the global state. * @param authority - The authority to remove the donation destinations. * @param donationDestinations - The donation destinations to remove. * @returns The transaction. */ async removeDonationDestinations({ authority, donationDestinations, }) { return await this.program.methods .removeDonationDestinations(donationDestinations) .accountsPartial({ authority, global: this.globalPDA }) .transaction(); } /** * Creates a new bonding curve. * @param user - The user to create the bonding curve. * @param mint - The mint to create the bonding curve for. * @param name - The name of the bonding curve. * @param symbol - The symbol of the bonding curve. * @param uri - The URI of the bonding curve. * @param donationDestination - The donation destination for the bonding curve. * @param donationAmount - The donation amount for the bonding curve. * @returns The transaction. */ async create({ user, mint, name, symbol, uri, donationDestination, donationAmount, }) { const tx = new web3_js_1.Transaction(); const createTx = await this.program.methods .create(name, symbol, uri, donationAmount) .accountsPartial({ user, global: this.globalPDA, mint: mint.publicKey, associatedBondingCurve: (0, spl_token_1.getAssociatedTokenAddressSync)(mint.publicKey, this.bondingCurvePDA({ mint: mint.publicKey }), true, spl_token_1.TOKEN_2022_PROGRAM_ID), bondingCurve: this.bondingCurvePDA({ mint: mint.publicKey }), tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID, donationDestination: donationDestination, associatedDonationDestination: (0, spl_token_1.getAssociatedTokenAddressSync)(mint.publicKey, donationDestination, true, spl_token_1.TOKEN_2022_PROGRAM_ID), associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: web3_js_1.SystemProgram.programId, }) .transaction(); tx.add(createTx); const hash = await this.connection.getLatestBlockhash(); tx.recentBlockhash = hash.blockhash; tx.feePayer = user; tx.partialSign(mint); return tx; } /** * Creates a new bonding curve with SPL token (SONIC) as base currency. * @param user - The user to create the bonding curve. * @param mint - The mint to create the bonding curve for. * @param sonicMint - The SONIC mint address. * @param name - The name of the bonding curve. * @param symbol - The symbol of the bonding curve. * @param uri - The URI of the bonding curve. * @param donationDestination - The donation destination for the bonding curve. * @param donationAmount - The donation amount for the bonding curve. * @returns The transaction. */ async createWithSpl({ user, mint, sonicMint, name, symbol, uri, donationDestination, donationAmount, }) { // Determine the token program for SONIC mint const sonicTokenProgram = await this.getTokenProgramForMint(sonicMint); const tx = new web3_js_1.Transaction(); const createTx = await this.program.methods .createWithSpl(name, symbol, uri, donationAmount) .accountsPartial({ user, global: this.globalPDA, sonicMint, mint: mint.publicKey, donationDestination: donationDestination, associatedBondingCurve: (0, spl_token_1.getAssociatedTokenAddressSync)(mint.publicKey, this.bondingCurveV2PDA({ mint: mint.publicKey }), true, spl_token_1.TOKEN_2022_PROGRAM_ID), associatedDonationDestination: (0, spl_token_1.getAssociatedTokenAddressSync)(mint.publicKey, donationDestination, true, spl_token_1.TOKEN_2022_PROGRAM_ID), bondingCurveSonicAccount: (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, this.bondingCurveV2PDA({ mint: mint.publicKey }), true, sonicTokenProgram), bondingCurve: this.bondingCurveV2PDA({ mint: mint.publicKey }), associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID, splTokenProgram: sonicTokenProgram, systemProgram: web3_js_1.SystemProgram.programId, }) .transaction(); tx.add(createTx); const hash = await this.connection.getLatestBlockhash(); tx.recentBlockhash = hash.blockhash; tx.feePayer = user; tx.partialSign(mint); return tx; } /** * Buys tokens from the bonding curve by SOL amount. * @param user - The user to buy the tokens. * @param mint - The mint to buy the tokens for. * @param buyAmountSol - The amount of SOL to buy. * @param slippageBasisPoints - The slippage basic points. * @returns The transaction. */ async buyBySolAmount(user, mint, buyAmountSol, slippageBasisPoints = 500) { const globalState = await this.getGlobalState(); const bondingCurvePDA = this.bondingCurvePDA({ mint }); const bondingCurveAccount = await this.getBondingCurveAccount({ mint }); if (!bondingCurveAccount) { throw new Error('Bonding curve account not found'); } const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurvePDA, true, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedUser = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, true, spl_token_1.TOKEN_2022_PROGRAM_ID); const buyAmount = bondingCurveAccount.getBuyPrice(new bignumber_js_1.BigNumber(buyAmountSol)); const buyAmountWithSlippage = (0, helper_1.calculateWithSlippageBuy)(buyAmountSol, slippageBasisPoints); return await this.program.methods .buy(new anchor_1.BN(buyAmount.toNumber()), new anchor_1.BN(buyAmountWithSlippage.toNumber())) .accountsPartial({ user, global: this.globalPDA, associatedBondingCurve, associatedUser, bondingCurve: bondingCurvePDA, mint: mint, operatingWallet: globalState?.operatingWallet, creatorWallet: bondingCurveAccount.creator, }) .transaction(); } /** * Buys tokens from the bonding curve. * @param user - The user to buy the tokens. * @param mint - The mint to buy the tokens for. * @param amount - The amount of tokens to buy. * @param maxCostSol - The maximum cost in SOL to buy the tokens. * @returns The transaction. */ async buy({ user, mint, amount, maxCostSol, creatorWallet, }) { const globalState = await this.getGlobalState(); const bondingCurvePDA = this.bondingCurvePDA({ mint }); const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurvePDA, true, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedUser = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, false, spl_token_1.TOKEN_2022_PROGRAM_ID); return await this.program.methods .buy(amount, maxCostSol) .accountsPartial({ user, global: this.globalPDA, associatedBondingCurve: associatedBondingCurve, bondingCurve: bondingCurvePDA, mint: mint, associatedUser: associatedUser, operatingWallet: globalState?.operatingWallet, creatorWallet: creatorWallet, }) .transaction(); } /** * Sells tokens to the bonding curve by token amount. * @param user - The user to sell the tokens. * @param mint - The mint to sell the tokens for. * @param sellTokenAmount - The amount of tokens to sell. * @param slippageBasisPoints - The slippage basic points. * @returns The transaction. */ async sellByTokenAmount(user, mint, sellTokenAmount, slippageBasisPoints = 500) { const globalAccount = await this.getGlobalAccount(); const bondingCurveAccount = await this.getBondingCurveAccount({ mint }); const bondingCurvePDA = this.bondingCurvePDA({ mint }); const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurvePDA, true, spl_token_1.TOKEN_2022_PROGRAM_ID); if (!bondingCurveAccount || !globalAccount) { throw new Error('Bonding curve account or global account not found'); } const minSolOutput = bondingCurveAccount.getSellPrice(sellTokenAmount, globalAccount.operatingFeeBasisPoints, globalAccount.creatorFeeBasisPoints); const sellAmountWithSlippage = (0, helper_1.calculateWithSlippageSell)(minSolOutput, slippageBasisPoints); const associatedUser = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, false, spl_token_1.TOKEN_2022_PROGRAM_ID); return await this.program.methods .sell(new anchor_1.BN(sellTokenAmount.toNumber()), new anchor_1.BN(Math.floor(sellAmountWithSlippage.toNumber()))) .accountsPartial({ user, global: this.globalPDA, mint: mint, bondingCurve: bondingCurvePDA, associatedBondingCurve: associatedBondingCurve, associatedUser: associatedUser, operatingWallet: globalAccount.operatingWallet, creatorWallet: bondingCurveAccount.creator, }) .transaction(); } /** * Buys tokens from the bonding curve using SPL token (SONIC). * @param user - The user to buy the tokens. * @param mint - The mint to buy the tokens for. * @param sonicMint - The SONIC mint address. * @param amount - The amount of tokens to buy. * @param maxCostSonic - The maximum cost in SONIC to buy the tokens. * @param creatorWallet - The creator wallet to receive fees. * @returns The transaction. */ async buyWithSpl({ user, mint, sonicMint, amount, maxCostSonic, creatorWallet, }) { const globalState = await this.getGlobalState(); const bondingCurveV2PDA = this.bondingCurveV2PDA({ mint }); // Determine the token program for SONIC mint const sonicTokenProgram = await this.getTokenProgramForMint(sonicMint); const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurveV2PDA, true, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedBondingCurveSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, bondingCurveV2PDA, true, sonicTokenProgram); const associatedUser = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, false, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedUserSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, user, false, sonicTokenProgram); if (!globalState?.operatingWallet) { throw new Error('Global state or operating wallet not found'); } const operatingWalletSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, globalState.operatingWallet, true, // allowOwnerOffCurve for operating wallet sonicTokenProgram); const creatorWalletSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, creatorWallet, false, sonicTokenProgram); return await this.program.methods .buyWithSpl(amount, maxCostSonic) .accountsPartial({ user, global: this.globalPDA, sonicMint, creatorWallet, operatingWallet: globalState?.operatingWallet, userSonicAccount: associatedUserSonic, creatorSonicAccount: creatorWalletSonic, operatingSonicAccount: operatingWalletSonic, bondingCurveTokenAccount: associatedBondingCurve, bondingCurveSonicAccount: associatedBondingCurveSonic, bondingCurve: bondingCurveV2PDA, userTokenAccount: associatedUser, mint, tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID, sonicTokenProgram: sonicTokenProgram, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: web3_js_1.SystemProgram.programId, }) .transaction(); } /** * Sells tokens to the bonding curve for SPL token (SONIC). * @param user - The user to sell the tokens. * @param mint - The mint to sell the tokens for. * @param sonicMint - The SONIC mint address. * @param amount - The amount of tokens to sell. * @param minSonicReceived - The minimum amount of SONIC to receive. * @param creatorWallet - The creator wallet to receive fees. * @returns The transaction. */ async sellWithSpl({ user, mint, sonicMint, amount, minSonicReceived, creatorWallet, }) { const globalState = await this.getGlobalState(); const bondingCurveV2PDA = this.bondingCurveV2PDA({ mint }); // Determine the token program for SONIC mint const sonicTokenProgram = await this.getTokenProgramForMint(sonicMint); const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurveV2PDA, true, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedBondingCurveSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, bondingCurveV2PDA, true, sonicTokenProgram); const associatedUser = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, false, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedUserSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, user, false, sonicTokenProgram); if (!globalState?.operatingWallet) { throw new Error('Global state or operating wallet not found'); } const operatingWalletSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, globalState.operatingWallet, true, // allowOwnerOffCurve for operating wallet sonicTokenProgram); const creatorWalletSonic = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, creatorWallet, false, sonicTokenProgram); return await this.program.methods .sellWithSpl(amount, minSonicReceived) .accountsPartial({ user, global: this.globalPDA, sonicMint, mint, operatingWallet: globalState?.operatingWallet, creatorWallet, userSonicAccount: associatedUserSonic, creatorSonicAccount: creatorWalletSonic, operatingSonicAccount: operatingWalletSonic, bondingCurveTokenAccount: associatedBondingCurve, bondingCurveSonicAccount: associatedBondingCurveSonic, bondingCurve: bondingCurveV2PDA, userTokenAccount: associatedUser, tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID, sonicTokenProgram: sonicTokenProgram, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: web3_js_1.SystemProgram.programId, }) .transaction(); } /** * Withdraws tokens from the bonding curve. * @param user - The user to withdraw the tokens. * @param mint - The mint to withdraw the tokens for. * @returns The transaction. */ async withdraw({ user, mint, }) { const globalState = await this.getGlobalState(); const bondingCurveState = this.bondingCurvePDA({ mint }); const bondingCurve = await this.program.account.bondingCurve.fetch(bondingCurveState); const associatedUser = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, false, spl_token_1.TOKEN_2022_PROGRAM_ID); const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurveState, true, spl_token_1.TOKEN_2022_PROGRAM_ID); return await this.program.methods .withdraw() .accountsPartial({ user, global: this.globalPDA, associatedBondingCurve: associatedBondingCurve, bondingCurve: bondingCurveState, mint: mint, operatingWallet: globalState?.operatingWallet, associatedUser: associatedUser, creatorWallet: bondingCurve?.creator, tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, }) .transaction(); } /** * Withdraws tokens and SONIC from a completed bonding curve V2. * @param user - The user (authority) to withdraw the tokens. * @param mint - The token mint to withdraw. * @param sonicMint - The SONIC mint address. * @returns The transaction. */ async withdrawSpl({ user, mint, sonicMint, }) { const bondingCurveV2State = this.bondingCurveV2PDA({ mint }); // Determine the token program for SONIC mint const sonicTokenProgram = await this.getTokenProgramForMint(sonicMint); // Bonding curve token accounts const bondingCurveTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurveV2State, true, spl_token_1.TOKEN_2022_PROGRAM_ID); const bondingCurveSonicAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, bondingCurveV2State, true, sonicTokenProgram); // Authority (user) token accounts const authorityTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, user, false, spl_token_1.TOKEN_2022_PROGRAM_ID); const authoritySonicAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(sonicMint, user, false, sonicTokenProgram); return await this.program.methods .withdrawSpl() .accountsPartial({ global: this.globalPDA, mint: mint, sonicMint: sonicMint, bondingCurveTokenAccount: bondingCurveTokenAccount, bondingCurveSonicAccount: bondingCurveSonicAccount, authorityTokenAccount: authorityTokenAccount, authoritySonicAccount: authoritySonicAccount, bondingCurve: bondingCurveV2State, user: user, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID, systemProgram: web3_js_1.SystemProgram.programId, rent: anchor.web3.SYSVAR_RENT_PUBKEY, }) .transaction(); } /** * Calculates the amount of tokens to buy with improved slippage protection. * @param mint - The mint to buy the tokens for. * @param amountSol - The amount of SOL to buy the tokens with. * @param slippage - The slippage percentage (e.g., 5 = 5%, capped at 20% for safety). * @returns The amount of tokens to buy and the maximum cost in SOL. */ async calculateBuyTokenAmount({ mint, amountSol, slippage, }) { console.log('🔧 SDK: Fixed calculateBuyTokenAmount called', { mint: mint.toString(), amountSol: amountSol.toString(), slippage, }); const bondingCurveState = await this.getBondingCurveState({ mint }); if (bondingCurveState) { const virtualSolReserves = bondingCurveState?.virtualSolReserves; const virtualTokenReserves = bondingCurveState?.virtualTokenReserves; const constant = virtualSolReserves.mul(virtualTokenReserves); let deltaToken = virtualTokenReserves.sub(constant.div(virtualSolReserves.add(amountSol))); if (deltaToken.gt(bondingCurveState.realTokenReserves)) { deltaToken = bondingCurveState.realTokenReserves; } return { amountToken: deltaToken, maxCostSol: amountSol .mul(new anchor_1.BN(100 + Math.min(slippage, 20))) .div(new anchor_1.BN(100)), }; } else { const globalState = await this.getGlobalState(); const virtualSolReserves = globalState?.initialVirtualSolReserves; const virtualTokenReserves = globalState?.initialVirtualTokenReserves; if (!virtualSolReserves || !virtualTokenReserves) { throw new Error('Virtual reserves not found'); } const constant = virtualSolReserves.mul(virtualTokenReserves); const deltaToken = virtualTokenReserves.sub(constant.div(virtualSolReserves.add(amountSol))); return { amountToken: deltaToken, maxCostSol: amountSol .mul(new anchor_1.BN(100 + Math.min(slippage, 20))) .div(new anchor_1.BN(100)), }; } } /** * Calculates the amount of tokens to sell to the bonding curve. * @param mint - The mint to sell the tokens for. * @param amountToken - The amount of tokens to sell. * @param slippage - The slippage percentage. * @returns The amount of tokens to sell and the minimum amount of SOL to receive. */ async calculateSellTokenAmount({ mint, amountToken, slippage, }) { const bondingCurveState = await this.getBondingCurveState({ mint }); if (!bondingCurveState) { throw new Error('Bonding curve state not found'); } const virtualSolReserves = bondingCurveState?.virtualSolReserves; const virtualTokenReserves = bondingCurveState?.virtualTokenReserves; if (!virtualSolReserves || !virtualTokenReserves) { throw new Error('Virtual reserves not found'); } const constant = virtualSolReserves.mul(virtualTokenReserves); const deltaSol = virtualSolReserves.sub(constant.div(virtualTokenReserves.add(amountToken))); return { amountToken: amountToken, minSolReceived: deltaSol.mul(new anchor_1.BN(100 - slippage)).div(new anchor_1.BN(100)), }; } async getPriceAndMarketcap(mint) { const bondingCurveState = await this.getBondingCurveState({ mint }); const virtualSolReserves = bondingCurveState?.virtualSolReserves.toNumber(); const virtualTokenReserves = bondingCurveState?.virtualTokenReserves.toNumber(); const tokenTotalSupply = bondingCurveState?.tokenTotalSupply.toNumber(); if (!virtualSolReserves || !virtualTokenReserves || !tokenTotalSupply) { throw new Error('Bonding curve state not found'); } const constant = virtualSolReserves * virtualTokenReserves; const deltaSol = virtualSolReserves - constant / (virtualTokenReserves + 1); return { price: deltaSol, marketcap: deltaSol * tokenTotalSupply, }; } async getBondingCurveProgress(mint) { const bondingCurveState = await this.getBondingCurveState({ mint }); if (!bondingCurveState) { throw new Error('Bonding curve state not found'); } const globalState = await this.getGlobalState(); if (!globalState) { throw new Error('Global state not found'); } const progress = bondingCurveState.realTokenReserves.toNumber() / globalState.initialRealTokenReserves.toNumber(); const progressPercentage = (1 - progress) * 100; return progressPercentage; } async getPriceAndMarketcapV2(mint) { const bondingCurveV2State = await this.getBondingCurveV2State({ mint }); const virtualBaseReserves = bondingCurveV2State?.virtualBaseReserves.toNumber(); const virtualTokenReserves = bondingCurveV2State?.virtualTokenReserves.toNumber(); const tokenTotalSupply = bondingCurveV2State?.tokenTotalSupply.toNumber(); if (!virtualBaseReserves || !virtualTokenReserves || !tokenTotalSupply) { throw new Error('Bonding curve V2 state not found'); } const constant = virtualBaseReserves * virtualTokenReserves; const deltaBase = virtualBaseReserves - constant / (virtualTokenReserves + 1); return { price: deltaBase, marketcap: deltaBase * tokenTotalSupply, }; } async getBondingCurveProgressV2(mint) { const bondingCurveV2State = await this.getBondingCurveV2State({ mint }); if (!bondingCurveV2State) { throw new Error('Bonding curve V2 state not found'); } const globalState = await this.getGlobalState(); if (!globalState) { throw new Error('Global state not found'); } const progress = bondingCurveV2State.realTokenReserves.toNumber() / globalState.sonicInitialRealTokenReserves.toNumber(); const progressPercentage = (1 - progress) * 100; return progressPercentage; } /** * Calculates the amount of tokens to buy from the bonding curve using SPL token (SONIC). * @param mint - The mint to buy the tokens for. * @param sonicMint - The SONIC mint address. * @param amountSonic - The amount of SONIC to buy the tokens with. * @param slippage - The slippage percentage. * @returns The amount of tokens to buy and the maximum cost in SONIC. */ async calculateBuyTokenAmountWithSpl({ mint, sonicMint: _sonicMint, amountSonic, slippage, }) { const bondingCurveV2State = await this.getBondingCurveV2State({ mint }); if (bondingCurveV2State) { const virtualSonicReserves = bondingCurveV2State.virtualBaseReserves; const virtualTokenReserves = bondingCurveV2State.virtualTokenReserves; const constant = virtualSonicReserves.mul(virtualTokenReserves); let deltaToken = virtualTokenReserves.sub(constant.div(virtualSonicReserves.add(amountSonic))); if (deltaToken.gt(bondingCurveV2State.realTokenReserves)) { deltaToken = bondingCurveV2State.realTokenReserves; } return { amountToken: deltaToken, maxCostSonic: amountSonic .mul(new anchor.BN(100 + Math.min(slippage, 20))) .div(new anchor.BN(100)), }; } else { const globalState = await this.getGlobalState(); const virtualSonicReserves = globalState?.sonicInitialVirtualBaseReserves; const virtualTokenReserves = globalState?.sonicInitialVirtualTokenReserves; if (!virtualSonicReserves || !virtualTokenReserves) { throw new Error('SONIC virtual reserves not found in global state'); } const constant = virtualSonicReserves.mul(virtualTokenReserves); const deltaToken = virtualTokenReserves.sub(constant.div(virtualSonicReserves.add(amountSonic))); return { amountToken: deltaToken, maxCostSonic: amountSonic .mul(new anchor.BN(100 + Math.min(slippage, 20))) .div(new anchor.BN(100)), }; } } /** * Calculates the amount of tokens to sell to the bonding curve for SPL token (SONIC). * @param mint - The mint to sell the tokens for. * @param sonicMint - The SONIC mint address. * @param amountToken - The amount of tokens to sell. * @param slippage - The slippage percentage. * @returns The amount of tokens to sell and the minimum amount of SONIC to receive. */ async calculateSellTokenAmountWithSpl({ mint, sonicMint: _sonicMint, amountToken, slippage, }) { const bondingCurveV2State = await this.getBondingCurveV2State({ mint }); if (bondingCurveV2State) { const virtualSonicReserves = bondingCurveV2State.virtualBaseReserves; const virtualTokenReserves = bondingCurveV2State.virtualTokenReserves; const realSonicReserves = bondingCurveV2State.realBaseReserves; // Get global state for fees const globalState = await this.getGlobalState(); if (!globalState) { throw new Error('Global state not found'); } // Use constant product formula (x*y=k) - same as buy operations and updated contract const constant = virtualSonicReserves.mul(virtualTokenReserves); const newVirtualTokenReserves = virtualTokenReserves.add(amountToken); const newVirtualSonicReserves = constant.div(newVirtualTokenReserves); const sonicReceived = virtualSonicReserves.sub(newVirtualSonicReserves); // Cap by real reserves (same as contract) const cappedSonicReceived = sonicReceived.gt(realSonicReserves) ? realSonicReserves : sonicReceived; // Subtract fees (operating + creator) const operatingFee = cappedSonicReceived .mul(globalState.operatingFeeBasisPoints) .div(new anchor.BN(10000)); const creatorFee = cappedSonicReceived .mul(globalState.creatorFeeBasisPoints) .div(new anchor.BN(10000)); const sonicAfterFees = cappedSonicReceived .sub(operatingFee) .sub(creatorFee); // Apply slippage to the after-fee amount return { amountToken: amountToken, minSonicReceived: sonicAfterFees .mul(new anchor.BN(100 - slippage)) .div(new anchor.BN(100)), }; } else { const globalState = await this.getGlobalState(); const virtualSonicReserves = globalState?.sonicInitialVirtualBaseReserves; const virtualTokenReserves = globalState?.sonicInitialVirtualTokenReserves; if (!virtualSonicReserves || !virtualTokenReserves) { throw new Error('SONIC virtual reserves not found in global state'); } const constant = virtualSonicReserves.mul(virtualTokenReserves); const deltaSonic = virtualSonicReserves.sub(constant.div(virtualTokenReserves.add(amountToken))); return { amountToken: amountToken, minSonicReceived: deltaSonic .mul(new anchor.BN(100 - slippage)) .div(new anchor.BN(100)), }; } } /** * Helper method to calculate tokens for given SONIC amount * @private */ calculateTokensForSonic(bondingCurve, sonicAmount) { const virtualSonicReserves = bondingCurve.virtualBaseReserves; const virtualTokenReserves = bondingCurve.virtualTokenReserves; const constant = virtualSonicReserves.mul(virtualTokenReserves); let deltaToken = virtualTokenReserves.sub(constant.div(virtualSonicReserves.add(sonicAmount))); // Cap at available tokens if (deltaToken.gt(bondingCurve.realTokenReserves)) { deltaToken = bondingCurve.realTokenReserves; } return { tokens: deltaToken, actualCost: sonicAmount, }; } /** * Helper method to calculate SONIC needed for specific token amount * @private */ calculateSonicForTokens(bondingCurve, tokenAmount) { const virtualSonicReserves = bondingCurve.virtualBaseReserves; const virtualTokenReserves = bondingCurve.virtualTokenReserves; const constant = virtualSonicReserves.mul(virtualTokenReserves); const newTokenReserves = virtualTokenReserves.sub(tokenAmount); const newSonicReserves = constant.div(newTokenReserves); return newSonicReserves.sub(virtualSonicReserves); } /** * Helper method to calculate fees * @private */ calculateFees(baseAmount, operatingFeeBasisPoints, creatorFeeBasisPoints) { const operatingFee = baseAmount .mul(new anchor.BN(operatingFeeBasisPoints)) .div(new anchor.BN(10000)); const creatorFee = baseAmount .mul(new anchor.BN(creatorFeeBasisPoints)) .div(new anchor.BN(10000)); return { operatingFee, creatorFee }; } } exports.GoodrFunProgramBase = GoodrFunProgramBase;