UNPKG

goodrdotfun-sdk

Version:

SDK for interacting with goodr.fun and Sonic on Solana

670 lines (669 loc) 29.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GoodrFunSDK = void 0; const web3_js_1 = require("@solana/web3.js"); const program_1 = require("./base/program"); const types_1 = require("./types"); const bn_js_1 = require("bn.js"); const constant_1 = require("./base/constant"); const helper_1 = require("./base/helpers/helper"); const spl_token_1 = require("@solana/spl-token"); const bignumber_js_1 = require("bignumber.js"); const common_1 = require("./base/types/common"); /** * SDK for interacting with the GoodrFun program on Solana and Sonic */ class GoodrFunSDK { /** * Creates a new instance of GoodrFunSDK * @param rpcEndpoint - The Solana RPC endpoint URL */ constructor(chainType, rpcEndpoint) { this.program = new program_1.GoodrFunProgram(rpcEndpoint); this.chainType = chainType; } /** * Gets the program ID * @returns The program ID */ get programId() { return this.program.program.programId; } /** * Calculate buy token amount with slippage for SOL (public method for testing) * @param params - Calculation parameters * @returns Token amount and max cost */ async calculateBuyTokenAmountWithSlippage(params) { return this.program.calculateBuyTokenAmount(params); } /** * Calculate buy token amount with slippage for SONIC (public method for testing) * @param params - Calculation parameters * @returns Token amount and max cost */ async calculateBuyTokenAmountWithSplSlippage(params) { return this.program.calculateBuyTokenAmountWithSpl(params); } /** * Gets the IDL * @returns The IDL of the program */ get idl() { return this.program.program.idl; } /** * Gets the global account * @returns The global account */ async getGlobalAccount() { return await this.program.getGlobalAccount(); } /** * Gets the bonding curve account * @returns The bonding curve account */ async getBondingCurveAccount(mint) { return await this.program.getBondingCurveAccount({ mint }); } /** * Adds a callback for the Create event * @param callback - Function to be called when a Create event occurs * @returns Event listener ID that can be used to remove the listener */ addOnCreateEvent(callback) { return this.program.onCreateEvent((event, slot, signature) => { callback({ ...event, currency: event.currency, }, slot, signature); }); } /** * Adds a callback for the Trade event * @param callback - Function to be called when a Trade event occurs * @returns Event listener ID that can be used to remove the listener */ addOnTradeEvent(callback) { return this.program.onTradeEvent((event, slot, signature) => { callback({ mint: event.mint, solAmount: new bignumber_js_1.BigNumber(event.solAmount.toString()), tokenAmount: new bignumber_js_1.BigNumber(event.tokenAmount.toString()), isBuy: event.isBuy, user: event.user, timestamp: new bignumber_js_1.BigNumber(event.timestamp.toString()), virtualSolReserves: new bignumber_js_1.BigNumber(event.virtualSolReserves.toString()), virtualTokenReserves: new bignumber_js_1.BigNumber(event.virtualTokenReserves.toString()), realTokenReserves: new bignumber_js_1.BigNumber(event.realTokenReserves.toString()), realSolReserves: new bignumber_js_1.BigNumber(event.realSolReserves.toString()), currency: event.currency, }, slot, signature); }); } /** * Adds a callback for the Complete event * @param callback - Function to be called when a Complete event occurs * @returns Event listener ID that can be used to remove the listener */ addOnCompleteEvent(callback) { return this.program.onCompleteEvent((event, slot, signature) => { callback({ user: event.user, mint: event.mint, bondingCurve: event.bondingCurve, timestamp: new bignumber_js_1.BigNumber(event.timestamp.toString()), }, slot, signature); }); } /** * Adds a callback for the SetParams event * @param callback - Function to be called when a SetParams event occurs * @returns Event listener ID that can be used to remove the listener */ addOnSetParamsEvent(callback) { return this.program.onSetParamsEvent((event, slot, signature) => { callback({ operatingWallet: event.operatingWallet, initialVirtualTokenReserves: new bignumber_js_1.BigNumber(event.initialVirtualTokenReserves.toString()), initialVirtualSolReserves: new bignumber_js_1.BigNumber(event.initialVirtualSolReserves.toString()), initialRealTokenReserves: new bignumber_js_1.BigNumber(event.initialRealTokenReserves.toString()), tokenTotalSupply: new bignumber_js_1.BigNumber(event.tokenTotalSupply.toString()), operatingFeeBasisPoints: new bignumber_js_1.BigNumber(event.operatingFeeBasisPoints.toString()), creatorFeeBasisPoints: new bignumber_js_1.BigNumber(event.creatorFeeBasisPoints.toString()), }, slot, signature); }); } /** * Removes one or more event listeners * @param eventIds - List of event listener IDs to remove */ removeEventListener(...eventIds) { this.program.removeListeners(eventIds); } /** * Creates a new token and buys tokens in a single transaction * @param creator - The keypair of the creator * @param params - Parameters for creating and buying tokens * @returns Transaction result containing signature and slot */ async createAndBuy(creator, params) { const tx = await this.createAndBuyTx(creator.publicKey, params); const hash = await this.program.connection.getLatestBlockhash(); tx.feePayer = creator.publicKey; tx.recentBlockhash = hash.blockhash; const result = await (0, helper_1.sendTx)(this.program.connection, tx, creator.publicKey, [creator, params.mint]); return result; } /** * Creates a transaction for creating a new token and buying tokens * @param creator - The public key of the creator * @param params - Parameters for creating and buying tokens * @returns Transaction object ready to be signed and sent */ async createAndBuyTx(creator, params) { const slippageBasisPoints = params.slippageBasisPoints <= 0 ? constant_1.DEFAULT_SLIPPAGE_BASIS_POINTS : params.slippageBasisPoints; const tx = new web3_js_1.Transaction(); const memeDestination = (0, types_1.getMemeDonationDestinationFromName)(params.meme); const createTokenTx = await this.program.create({ user: creator, mint: params.mint, name: params.metadata.name, symbol: params.metadata.symbol, uri: params.metadata.metadataUri, donationDestination: memeDestination.address, donationAmount: new bn_js_1.BN(memeDestination.donationAmount * 10 ** this.program.decimals), }); tx.add(createTokenTx); if (params.buySolAmount.gt(0)) { const { amountToken, maxCostSol } = await this.program.calculateBuyTokenAmount({ mint: params.mint.publicKey, amountSol: new bn_js_1.BN(params.buySolAmount.toNumber()), slippage: slippageBasisPoints / 100, // Convert basis points to percentage }); const initialBuyTx = await this.program.buy({ user: creator, mint: params.mint.publicKey, amount: amountToken, maxCostSol: maxCostSol, creatorWallet: creator, }); tx.add(initialBuyTx); } return tx; } /** * Buys tokens for a given amount of SOL * @param creator - The keypair of the buyer * @param params - Parameters for buying tokens * @returns Transaction result containing signature and slot */ async buy(creator, params) { const tx = await this.buyTx(creator.publicKey, params); const hash = await this.program.connection.getLatestBlockhash(); tx.feePayer = creator.publicKey; tx.recentBlockhash = hash.blockhash; const result = await (0, helper_1.sendTx)(this.program.connection, tx, creator.publicKey, [creator]); if (!result.success) { if (result.results) checkProgramErrorInLogs(result.results); if (hasErrorMessage(result.error)) { for (const [code, errorMsg] of Object.entries(common_1.ProgramErrorCodeTs)) { if (result.error.message.includes(errorMsg)) { throw new common_1.ProgramError(code, errorMsg); } } } } else { if (result.results) checkProgramErrorInLogs(result.results); } return result; } /** * Creates a transaction for buying tokens * @param creator - The public key of the buyer * @param params - Parameters for buying tokens * @returns Transaction object ready to be signed and sent */ async buyTx(creator, params) { const slippageBasisPoints = params.slippageBasisPoints <= 0 ? constant_1.DEFAULT_SLIPPAGE_BASIS_POINTS : params.slippageBasisPoints; const buyTokenTx = await this.program.buyBySolAmount(creator, params.mint, params.solAmount, slippageBasisPoints); return buyTokenTx; } /** * Sells tokens for SOL * @param creator - The keypair of the seller * @param params - Parameters for selling tokens * @returns Transaction result containing signature and slot */ async sell(creator, params) { const tx = await this.sellTx(creator.publicKey, params); const hash = await this.program.connection.getLatestBlockhash(); tx.feePayer = creator.publicKey; tx.recentBlockhash = hash.blockhash; const result = await (0, helper_1.sendTx)(this.program.connection, tx, creator.publicKey, [creator]); if (!result.success) { if (result.results) checkProgramErrorInLogs(result.results); if (hasErrorMessage(result.error)) { for (const [code, errorMsg] of Object.entries(common_1.ProgramErrorCodeTs)) { if (result.error.message.includes(errorMsg)) { throw new common_1.ProgramError(code, errorMsg); } } } } else { if (result.results) checkProgramErrorInLogs(result.results); } return result; } /** * Creates a transaction for selling tokens * @param creator - The public key of the seller * @param params - Parameters for selling tokens * @returns Transaction object ready to be signed and sent */ async sellTx(creator, params) { const slippageBasisPoints = params.slippageBasisPoints <= 0 ? constant_1.DEFAULT_SLIPPAGE_BASIS_POINTS : params.slippageBasisPoints; const sellTokenTx = await this.program.sellByTokenAmount(creator, params.mint, params.tokenAmount, slippageBasisPoints); return sellTokenTx; } // SONIC-specific methods /** * Creates a new token with SONIC and buys tokens in a single transaction * @param creator - The keypair of the creator * @param params - Parameters for creating and buying tokens with SONIC * @returns Transaction result containing signature and slot */ async createAndBuyWithSonic(creator, params) { const tx = await this.createAndBuyWithSonicTx(creator.publicKey, params); const hash = await this.program.connection.getLatestBlockhash(); tx.feePayer = creator.publicKey; tx.recentBlockhash = hash.blockhash; const result = await (0, helper_1.sendTx)(this.program.connection, tx, creator.publicKey, [creator, params.mint]); return result; } /** * Creates a transaction for creating a new token with SONIC and buying tokens * @param creator - The public key of the creator * @param params - Parameters for creating and buying tokens with SONIC * @returns Transaction object ready to be signed and sent */ async createAndBuyWithSonicTx(creator, params) { const slippageBasisPoints = params.slippageBasisPoints <= 0 ? constant_1.DEFAULT_SLIPPAGE_BASIS_POINTS : params.slippageBasisPoints; const tx = new web3_js_1.Transaction(); const memeDestination = (0, types_1.getMemeDonationDestinationFromName)(params.meme); const createTokenTx = await this.program.createWithSpl({ user: creator, mint: params.mint, sonicMint: params.baseCurrencyMint, name: params.metadata.name, symbol: params.metadata.symbol, uri: params.metadata.metadataUri, donationDestination: memeDestination.address, donationAmount: new bn_js_1.BN(memeDestination.donationAmount * 10 ** this.program.decimals), }); tx.add(createTokenTx); if (params.buySonicAmount.gt(0)) { const { amountToken, maxCostSonic } = await this.program.calculateBuyTokenAmountWithSpl({ mint: params.mint.publicKey, sonicMint: params.baseCurrencyMint, amountSonic: new bn_js_1.BN(params.buySonicAmount.toNumber()), slippage: slippageBasisPoints / 100, // Convert basis points to percentage }); const initialBuyTx = await this.program.buyWithSpl({ user: creator, mint: params.mint.publicKey, sonicMint: params.baseCurrencyMint, amount: amountToken, maxCostSonic: maxCostSonic, creatorWallet: creator, }); tx.add(initialBuyTx); } return tx; } /** * Buys tokens for a given amount of SONIC * @param creator - The keypair of the buyer * @param params - Parameters for buying tokens with SONIC * @returns Transaction result containing signature and slot */ async buyWithSonic(creator, params) { const tx = await this.buyWithSonicTx(creator.publicKey, params); const hash = await this.program.connection.getLatestBlockhash(); tx.feePayer = creator.publicKey; tx.recentBlockhash = hash.blockhash; const result = await (0, helper_1.sendTx)(this.program.connection, tx, creator.publicKey, [creator]); if (!result.success) { if (result.results) checkProgramErrorInLogs(result.results); if (hasErrorMessage(result.error)) { for (const [code, errorMsg] of Object.entries(common_1.ProgramErrorCodeTs)) { if (result.error.message.includes(errorMsg)) { throw new common_1.ProgramError(code, errorMsg); } } } } else { if (result.results) checkProgramErrorInLogs(result.results); } return result; } /** * Creates a transaction for buying tokens with SONIC * @param creator - The public key of the buyer * @param params - Parameters for buying tokens with SONIC * @returns Transaction object ready to be signed and sent */ async buyWithSonicTx(creator, params) { const slippageBasisPoints = params.slippageBasisPoints <= 0 ? constant_1.DEFAULT_SLIPPAGE_BASIS_POINTS : params.slippageBasisPoints; // Fetch the bonding curve to get the actual token creator (SONIC tokens use V2) const bondingCurveAccount = await this.program.getBondingCurveV2State({ mint: params.mint, }); if (!bondingCurveAccount) { throw new Error(`Bonding curve V2 not found for mint: ${params.mint.toString()}`); } const { amountToken, maxCostSonic } = await this.program.calculateBuyTokenAmountWithSpl({ mint: params.mint, sonicMint: params.baseCurrencyMint, amountSonic: new bn_js_1.BN(params.sonicAmount.toString()), slippage: slippageBasisPoints / 100, // Convert basis points to percentage }); const buyTokenTx = await this.program.buyWithSpl({ user: creator, mint: params.mint, sonicMint: params.baseCurrencyMint, amount: amountToken, maxCostSonic: maxCostSonic, creatorWallet: bondingCurveAccount.creator, // Use the actual token creator }); return buyTokenTx; } /** * Sells tokens for SONIC * @param creator - The keypair of the seller * @param params - Parameters for selling tokens for SONIC * @returns Transaction result containing signature and slot */ async sellWithSonic(creator, params) { const tx = await this.sellWithSonicTx(creator.publicKey, params); const hash = await this.program.connection.getLatestBlockhash(); tx.feePayer = creator.publicKey; tx.recentBlockhash = hash.blockhash; const result = await (0, helper_1.sendTx)(this.program.connection, tx, creator.publicKey, [creator]); if (!result.success) { if (result.results) checkProgramErrorInLogs(result.results); if (hasErrorMessage(result.error)) { for (const [code, errorMsg] of Object.entries(common_1.ProgramErrorCodeTs)) { if (result.error.message.includes(errorMsg)) { throw new common_1.ProgramError(code, errorMsg); } } } } else { if (result.results) checkProgramErrorInLogs(result.results); } return result; } /** * Creates a transaction for selling tokens for SONIC * @param creator - The public key of the seller * @param params - Parameters for selling tokens for SONIC * @returns Transaction object ready to be signed and sent */ async sellWithSonicTx(creator, params) { const slippageBasisPoints = params.slippageBasisPoints <= 0 ? constant_1.DEFAULT_SLIPPAGE_BASIS_POINTS : params.slippageBasisPoints; // Fetch the bonding curve to get the actual token creator (SONIC tokens use V2) const bondingCurveAccount = await this.program.getBondingCurveV2State({ mint: params.mint, }); if (!bondingCurveAccount) { throw new Error(`Bonding curve V2 not found for mint: ${params.mint.toString()}`); } const { amountToken, minSonicReceived } = await this.program.calculateSellTokenAmountWithSpl({ mint: params.mint, sonicMint: params.baseCurrencyMint, amountToken: new bn_js_1.BN(params.tokenAmount.toString()), slippage: slippageBasisPoints / 100, // Convert basis points to percentage }); const sellTokenTx = await this.program.sellWithSpl({ user: creator, mint: params.mint, sonicMint: params.baseCurrencyMint, amount: amountToken, minSonicReceived: minSonicReceived, creatorWallet: bondingCurveAccount.creator, // Use the actual token creator }); return sellTokenTx; } async withdraw({ authority, mint }) { const withdrawTx = await this.program.withdraw({ user: authority.publicKey, mint, }); const txHash = await (0, web3_js_1.sendAndConfirmTransaction)(this.program.connection, withdrawTx, [authority]); return txHash; } /** * Withdraws tokens and SONIC from a completed bonding curve V2 * @param authority - The authority (signer) to withdraw the tokens * @param mint - The token mint address to withdraw * @param sonicMint - The SONIC token mint address * @returns Transaction hash of the withdrawal */ async withdrawSpl({ authority, mint, sonicMint, }) { const withdrawTx = await this.program.withdrawSpl({ user: authority.publicKey, mint, sonicMint, }); const txHash = await (0, web3_js_1.sendAndConfirmTransaction)(this.program.connection, withdrawTx, [authority]); return txHash; } /** * Gets the token account balance for a given mint and authority * @param mint - The token mint address * @param authority - The authority's public key * @returns The token balance as a BigNumber */ async getTokenAccountBalance(mint, authority) { const accountInfo = await this.program.connection.getAccountInfo(mint); if (!accountInfo) throw new Error('Account info is not found'); const programId = accountInfo.owner; const tokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, authority, true, programId); const balance = await this.program.connection.getTokenAccountBalance(tokenAccount); return new bignumber_js_1.BigNumber(balance.value.amount).div(new bignumber_js_1.BigNumber(10).pow(balance.value.decimals)); } /** * Calculates the amount of tokens that can be bought for a given amount of SOL * @param params - Parameters containing mint and SOL amount * @returns Object containing the calculated token amount */ async calculateBuyTokenAmount({ mint, amountSol, }) { const bondingCurveAccount = await this.program.getBondingCurveAccount({ mint, }); if (!bondingCurveAccount) { throw new Error('Bonding curve account not found'); } const buyPrice = bondingCurveAccount.getBuyPrice(amountSol); return { amountToken: buyPrice.dividedBy(new bignumber_js_1.BigNumber(10).pow(constant_1.TOKEN_DECIMALS)), }; } /** * Calculates the amount of SOL that can be received for a given amount of tokens * @param params - Parameters containing mint and token amount * @returns Object containing the calculated SOL amount */ async calculateSellTokenAmount({ mint, amountToken, }) { const bondingCurveAccount = await this.program.getBondingCurveAccount({ mint, }); const globalAccount = await this.program.getGlobalAccount(); if (!globalAccount) { throw new Error('Global account not found'); } if (!bondingCurveAccount) { throw new Error('Bonding curve account not found'); } const sellPrice = bondingCurveAccount.getSellPrice(amountToken, globalAccount.operatingFeeBasisPoints, globalAccount.creatorFeeBasisPoints); return { amountSol: sellPrice.dividedBy(web3_js_1.LAMPORTS_PER_SOL), }; } /** * Calculates the amount of tokens that can be bought for a given amount of SONIC * @param params - Parameters containing mint, base currency mint and SONIC amount * @returns Object containing the calculated token amount */ async calculateBuyTokenAmountWithSonic({ mint, baseCurrencyMint, amountSonic, }) { const result = await this.program.calculateBuyTokenAmountWithSpl({ mint, sonicMint: baseCurrencyMint, amountSonic: new bn_js_1.BN(amountSonic.toNumber()), slippage: 0, }); return { amountToken: new bignumber_js_1.BigNumber(result.amountToken.toString()).dividedBy(new bignumber_js_1.BigNumber(10).pow(constant_1.TOKEN_DECIMALS)), }; } /** * Calculates the amount of SONIC that can be received for a given amount of tokens * @param params - Parameters containing mint, base currency mint and token amount * @returns Object containing the calculated SONIC amount */ async calculateSellTokenAmountWithSonic({ mint, baseCurrencyMint, amountToken, }) { const result = await this.program.calculateSellTokenAmountWithSpl({ mint, sonicMint: baseCurrencyMint, amountToken: new bn_js_1.BN(amountToken.toNumber()), slippage: 0, }); const decimals = 9; // SONIC has 9 decimals return { amountSonic: new bignumber_js_1.BigNumber(result.minSonicReceived.toString()).dividedBy(new bignumber_js_1.BigNumber(10).pow(decimals)), }; } /** * Gets the current price and market cap data for a token * @param mint - The token mint address * @returns Price data including current price and market cap */ async getPriceAndMarketcapData(mint) { // Try V2 (SONIC) bonding curve first, then V1 (SOL) const bondingCurveV2State = await this.program.getBondingCurveV2State({ mint, }); if (bondingCurveV2State) { // SONIC token - use V2 method return this.program.getPriceDataFromStateV2(bondingCurveV2State); } else { // Try V1 (SOL) bonding curve const bondingCurveState = await this.program.getBondingCurveState({ mint, }); if (!bondingCurveState) throw new Error('Bonding curve state is not found'); return this.program.getPriceDataFromState(bondingCurveState); } } /** * Gets the current state of a token including price data and bonding curve progress * @param mint - The token mint address * @returns Token state including price data, bonding curve progress, and total supply */ async getCurrentState(mint) { const globalState = await this.program.getGlobalState(); if (!globalState) throw new Error('Global state is not found'); // Try V2 (SONIC) bonding curve first, then V1 (SOL) const bondingCurveState = await this.program.getBondingCurveV2State({ mint, }); let priceData; let bondingCurveProgress; let totalSupplyBN; if (bondingCurveState) { // SONIC token - use V2 methods const priceAndMarketcap = await this.program.getPriceAndMarketcapV2(mint); bondingCurveProgress = await this.program.getBondingCurveProgressV2(mint); totalSupplyBN = new bignumber_js_1.BigNumber(bondingCurveState.tokenTotalSupply.toString()).div(new bignumber_js_1.BigNumber(10).pow(this.program.decimals)); // For SONIC (V2), price includes 10^3 scaling factor from contract, marketcap needs conversion by SONIC decimals priceData = { price: new bignumber_js_1.BigNumber(priceAndMarketcap.price).div(new bignumber_js_1.BigNumber(10).pow(3)), marketCap: new bignumber_js_1.BigNumber(priceAndMarketcap.marketcap).div(new bignumber_js_1.BigNumber(10).pow(9)), totalSupply: totalSupplyBN, }; } else { // Try V1 (SOL) bonding curve const bondingCurveV1State = await this.program.getBondingCurveState({ mint, }); if (!bondingCurveV1State) throw new Error('Bonding curve state is not found'); // SOL token - use V1 methods const priceAndMarketcap = await this.program.getPriceAndMarketcap(mint); bondingCurveProgress = await this.program.getBondingCurveProgress(mint); totalSupplyBN = new bignumber_js_1.BigNumber(bondingCurveV1State.tokenTotalSupply.toString()).div(new bignumber_js_1.BigNumber(10).pow(this.program.decimals)); // For SOL (V1), price includes 10^3 scaling factor from contract, marketcap needs conversion from lamports priceData = { price: new bignumber_js_1.BigNumber(priceAndMarketcap.price).div(new bignumber_js_1.BigNumber(10).pow(3)), marketCap: new bignumber_js_1.BigNumber(priceAndMarketcap.marketcap).div(new bignumber_js_1.BigNumber(10).pow(9)), totalSupply: totalSupplyBN, }; } return { priceData, bondingCurveProgress, totalSupply: totalSupplyBN, currencyType: bondingCurveState ? 'SONIC' : 'SOL', }; } } exports.GoodrFunSDK = GoodrFunSDK; function checkProgramErrorInLogs(results) { if (!results || typeof results !== 'object' || results === null) return; const resultsObj = results; if (!resultsObj.meta || !resultsObj.meta.logMessages) return; const logs = resultsObj.meta.logMessages; for (const [code, errorMsg] of Object.entries(common_1.ProgramErrorCodeTs)) { if (logs.some(log => log.includes(errorMsg))) { throw new common_1.ProgramError(code, errorMsg); } } } function hasErrorMessage(e) { return (typeof e === 'object' && e !== null && 'message' in e && typeof e.message === 'string'); }