├── src ├── utils │ ├── sleep.ts │ ├── metadata.ts │ ├── x.ts │ ├── types.ts │ ├── logger.ts │ ├── web3.ts │ └── xapi.ts ├── core │ ├── global │ │ └── index.ts │ ├── config │ │ └── env.ts │ └── constants │ │ └── index.ts ├── idl │ ├── index.ts │ └── pump-fun.ts ├── models │ ├── blacklistModel.ts │ ├── boughtModel.ts │ ├── ammBoughtModel.ts │ └── tokenModel.ts ├── lib │ ├── pumpswap │ │ ├── constants.ts │ │ ├── types.ts │ │ ├── sell-pumpswap.ts │ │ ├── buy-pumpswap.ts │ │ └── pumpswap.ts │ ├── jupiter │ │ ├── buy-token-jup.ts │ │ └── sell-token-jup.ts │ ├── pumpfun │ │ ├── sell-token.ts │ │ ├── buy-token.ts │ │ ├── globalAccount.ts │ │ ├── bondingCurveAccount.ts │ │ └── pumpfun.ts │ ├── jito.ts │ ├── create-pool.ts │ ├── remove-liquidity.ts │ └── create-market.ts ├── app.ts ├── server.ts ├── handler │ ├── handle-migrate.ts │ └── handle-create.ts ├── checker │ └── check-metadata.ts ├── cron │ └── watch-pools.ts └── streaming │ └── new-tokens.ts ├── .gitignore ├── eslint.config.mjs ├── .env.template ├── tsconfig.json ├── package.json └── README.md /src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number): Promise => { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | .yarn 8 | .env 9 | keys 10 | script 11 | dist 12 | logs 13 | yarn.lock -------------------------------------------------------------------------------- /src/core/global/index.ts: -------------------------------------------------------------------------------- 1 | let hasEnoughBalance: boolean = true; // Initial value 2 | 3 | export const getHasEnoughBalance = () => hasEnoughBalance; 4 | export const setHasEnoughBalance = (value: boolean) => hasEnoughBalance = value; 5 | -------------------------------------------------------------------------------- /src/idl/index.ts: -------------------------------------------------------------------------------- 1 | import { PumpFun } from "./pump-fun"; 2 | import PumpFunIDL from "./pump-fun.json"; 3 | import { PumpSwap } from "./pump-swap"; 4 | import PumpSwapIDL from "./pump-swap.json"; 5 | 6 | export { PumpFun, PumpFunIDL, PumpSwap, PumpSwapIDL }; 7 | -------------------------------------------------------------------------------- /src/models/blacklistModel.ts: -------------------------------------------------------------------------------- 1 | import { default as mongoose, Schema } from 'mongoose'; 2 | 3 | const BlacklistSchema = new Schema({ 4 | handle: { 5 | type: String, 6 | required: true 7 | } 8 | }); 9 | 10 | export default mongoose.model("Blacklist", BlacklistSchema); 11 | -------------------------------------------------------------------------------- /src/lib/pumpswap/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | const PROTOCOL_FEE_RECIPIENT = new PublicKey("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV") 4 | const GLOBAL_CONFIG_SEED = 'global_config' 5 | const LP_MINT_SEED = 'pool_lp_mint' 6 | const POOL_SEED = 'pool' 7 | export { 8 | PROTOCOL_FEE_RECIPIENT, 9 | GLOBAL_CONFIG_SEED, 10 | LP_MINT_SEED, 11 | POOL_SEED 12 | } -------------------------------------------------------------------------------- /src/models/boughtModel.ts: -------------------------------------------------------------------------------- 1 | import { default as mongoose, Schema } from 'mongoose'; 2 | 3 | const BoughtSchema = new Schema({ 4 | mint: { 5 | type: String, 6 | required: true 7 | }, 8 | createdAt: { 9 | type: Date, 10 | default: Date.now, 11 | }, 12 | handle: { 13 | type: String, 14 | required: true 15 | } 16 | }); 17 | 18 | export default mongoose.model("Bought", BoughtSchema); 19 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | 6 | /** @type {import('eslint').Linter.Config[]} */ 7 | export default [ 8 | {files: ["**/*.{js,mjs,cjs,ts}"]}, 9 | {files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}}, 10 | {languageOptions: { globals: globals.browser }}, 11 | pluginJs.configs.recommended, 12 | ...tseslint.configs.recommended, 13 | ]; 14 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | DEFAULT_API_PREFIX=/api/v1 3 | NODE_ENV=development 4 | 5 | # test mongo db, you can replace to your private db 6 | MONGO_URI=mongodb+srv://testuser:2RXSHPiplHLZirD2@cluster0.tquvepg.mongodb.net/ 7 | 8 | # public rpc, you can use your private rpc 9 | SOLANA_MAINNET_RPC=https://api.mainnet-beta.solana.com 10 | 11 | # filepath of your bot wallet 12 | PRIVATE_KEY_PATH=../id.json 13 | 14 | # test XAPI, you can use your api key 15 | XAPI_KEY=d4119277b5c14b8caca599e529fc4a54 16 | XAPI_URL=https://api.twitterapi.io/twitter 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["node_modules", "dist", "src/**/*.test.ts"], 3 | "include": ["src/**/*", "src/models/.ts"], 4 | "compilerOptions": { 5 | "types": ["node", "express"], 6 | "target": "ESNext", 7 | "module": "CommonJS", 8 | "rootDir": "./src", 9 | "moduleResolution": "Node", 10 | "typeRoots": ["./node_modules/@types"], 11 | "sourceMap": true, 12 | "outDir": "dist/", 13 | "resolveJsonModule": true, 14 | "esModuleInterop": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "strict": true, 17 | "strictNullChecks": true, 18 | "skipLibCheck": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/models/ammBoughtModel.ts: -------------------------------------------------------------------------------- 1 | import { default as mongoose, Schema } from 'mongoose'; 2 | 3 | const AmmBoughtSchema = new Schema({ 4 | mint: { 5 | type: String, 6 | required: true 7 | }, 8 | pool: { 9 | type: String, 10 | required: true 11 | }, 12 | creator: { 13 | type: String, 14 | required: true 15 | }, 16 | solReserve: { 17 | type: String, 18 | required: true 19 | }, 20 | tokenReserve: { 21 | type: String, 22 | required: true 23 | }, 24 | buyAt: { 25 | type: Date, 26 | default: Date.now, 27 | } 28 | }); 29 | 30 | export default mongoose.model("AmmBought", AmmBoughtSchema); 31 | -------------------------------------------------------------------------------- /src/lib/pumpswap/types.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | interface CreatePoolType { 4 | baseMint: PublicKey, 5 | quoteMint: PublicKey, 6 | baseTokenProgram: PublicKey, 7 | quoteTokenProgram: PublicKey, 8 | creator: PublicKey, 9 | } 10 | 11 | 12 | interface TradeType { 13 | baseMint: PublicKey, 14 | quoteMint: PublicKey, 15 | pool: PublicKey, 16 | baseTokenProgram: PublicKey, 17 | quoteTokenProgram: PublicKey, 18 | user: PublicKey, 19 | coinCreator: PublicKey 20 | } 21 | 22 | interface WithdrawType { 23 | index: number, 24 | creator: PublicKey, 25 | baseMint: PublicKey, 26 | quoteMint: PublicKey, 27 | user: PublicKey, 28 | } 29 | 30 | interface DepositType { 31 | pool: PublicKey, 32 | baseMint: PublicKey, 33 | quoteMint: PublicKey, 34 | user: PublicKey, 35 | } 36 | 37 | export { 38 | TradeType, 39 | CreatePoolType, 40 | WithdrawType, 41 | DepositType 42 | } -------------------------------------------------------------------------------- /src/models/tokenModel.ts: -------------------------------------------------------------------------------- 1 | import { default as mongoose, Schema } from 'mongoose'; 2 | 3 | const TokenSchema = new Schema({ 4 | mint: { 5 | type: String, 6 | required: true 7 | }, 8 | createdAt: { 9 | type: Date, 10 | default: Date.now, 11 | expires: '4h' 12 | }, 13 | name: { 14 | type: String, 15 | required: true 16 | }, 17 | symbol: { 18 | type: String, 19 | required: true 20 | }, 21 | handle: { 22 | type: String, 23 | required: true 24 | }, 25 | uri: { 26 | type: String, 27 | required: true 28 | }, 29 | vault: { 30 | type: String, 31 | required: true 32 | }, 33 | balance: { 34 | type: Number, 35 | required: true 36 | }, 37 | launched: { 38 | type: Boolean, 39 | required: true, 40 | default: false 41 | } 42 | }); 43 | 44 | export default mongoose.model("Token", TokenSchema); 45 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | // src/app.ts 2 | 3 | import mongoose from 'mongoose'; 4 | import cron from 'node-cron'; 5 | 6 | import { streamNewTokens } from './streaming/new-tokens'; 7 | import { sleep } from './utils/sleep'; 8 | import { checkPoolBalance } from './cron/watch-pools'; 9 | import { logLogger } from './utils/logger'; 10 | 11 | const listenNewTokens = async (attemp: number = 1) => { 12 | try { 13 | logLogger.log('attept to stream new tokens, attemp:', attemp++); 14 | 15 | await streamNewTokens(); 16 | } catch (e) { 17 | console.log('streamNewTokens err:', e); 18 | 19 | // sleep 1 min 20 | await sleep(1 * 60 * 1000); 21 | 22 | // call listenNewTokens() recursively 23 | await listenNewTokens(); 24 | } 25 | } 26 | 27 | const main = () => { 28 | // start mongoose 29 | mongoose.connect(process.env.MONGO_URI || '') 30 | .then(async () => { 31 | console.log("Connected to the database! ❤️"); 32 | 33 | listenNewTokens(1); 34 | }) 35 | .catch((err) => { 36 | console.log("Cannot connect to the database! 😭", err); 37 | process.exit(); 38 | }); 39 | } 40 | 41 | // check watch list every 1 min 42 | cron.schedule('* * * * *', checkPoolBalance); 43 | 44 | main(); 45 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | // src/server.ts 2 | 3 | import express, { type Request, type Response } from 'express'; 4 | import compression from 'compression'; 5 | import rateLimit from 'express-rate-limit'; 6 | 7 | import { HttpCode, ONE_HUNDRED, ONE_THOUSAND, SIXTY } from './core/constants'; 8 | 9 | interface ServerOptions { 10 | port: number; 11 | apiPrefix: string; 12 | } 13 | 14 | export class Server { 15 | private readonly app = express(); 16 | private readonly port: number; 17 | 18 | constructor(options: ServerOptions) { 19 | const { port } = options; 20 | this.port = port; 21 | } 22 | 23 | async start(): Promise { 24 | //* Middlewares 25 | this.app.use(express.json()); // parse json in request body (allow raw) 26 | this.app.use(express.urlencoded({ extended: true })); // allow x-www-form-urlencoded 27 | this.app.use(compression()); 28 | // limit repeated requests to public APIs 29 | this.app.use( 30 | rateLimit({ 31 | max: ONE_HUNDRED, 32 | windowMs: SIXTY * SIXTY * ONE_THOUSAND, 33 | message: 'Too many requests from this IP, please try again in one hour' 34 | }) 35 | ); 36 | 37 | // Test rest api 38 | this.app.get('/', (_req: Request, res: Response) => { 39 | res.status(HttpCode.OK).send({ 40 | message: `Welcome to Initial API! \n Endpoints available at http://localhost:${this.port}/` 41 | }); 42 | }); 43 | 44 | this.app.listen(this.port, () => { 45 | console.log(`Server running on port ${this.port}...`); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/metadata.ts: -------------------------------------------------------------------------------- 1 | class Metadata { 2 | discriminator: number; // u8 is typically represented as number in TS 3 | name: string; 4 | symbol: string; 5 | uri: string; 6 | 7 | constructor(discriminator: number, name: string, symbol: string, uri: string) { 8 | this.discriminator = discriminator; 9 | this.name = name; 10 | this.symbol = symbol; 11 | this.uri = uri; 12 | } 13 | } 14 | 15 | // Function to read a string prefixed by its length (4-byte integer) 16 | function readLengthPrefixedString(data: Uint8Array, offset: number): [string, number] { 17 | // Read the string length from the first 4 bytes 18 | const length = (data[offset]) | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); 19 | offset += 4; // Move past the length 20 | 21 | // Read the string of specified length 22 | const stringBytes = data.subarray(offset, offset + length); 23 | const stringValue = new TextDecoder("utf-8").decode(stringBytes); 24 | 25 | return [stringValue, offset + length]; // Move past the string 26 | } 27 | 28 | // Function to parse the Uint8Array to Metadata 29 | export function parseMetadata(data: Uint8Array): Metadata { 30 | const discriminator = data[0]; // Read the first byte for the discriminator 31 | let offset = 1; // Start reading after the discriminator 32 | 33 | const [name, afterName] = readLengthPrefixedString(data, offset); 34 | offset = afterName; 35 | 36 | const [symbol, afterSymbol] = readLengthPrefixedString(data, offset); 37 | offset = afterSymbol; 38 | 39 | const [uri, afterUri] = readLengthPrefixedString(data, offset); 40 | offset = afterUri; 41 | 42 | return new Metadata(discriminator, name, symbol, uri); 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/jupiter/buy-token-jup.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, VersionedTransaction } from "@solana/web3.js"; 2 | import { NATIVE_MINT } from "@solana/spl-token"; 3 | import { execTx } from "../../utils/web3"; 4 | 5 | export const buyTokenJup = async ( 6 | mint: PublicKey, 7 | buyAmount: number, 8 | keypair: Keypair, 9 | connection: Connection, 10 | ) => { 11 | try { 12 | const quoteResponse = await ( 13 | await fetch( 14 | `https://quote-api.jup.ag/v6/quote?inputMint=${NATIVE_MINT.toBase58()}&outputMint=${mint.toBase58()}&amount=${buyAmount}&slippageBps=${500}` 15 | ) 16 | ).json(); 17 | 18 | const { swapTransaction } = await ( 19 | await fetch("https://quote-api.jup.ag/v6/swap", { 20 | method: "POST", 21 | headers: { 22 | "Content-Type": "application/json", 23 | }, 24 | body: JSON.stringify({ 25 | quoteResponse, 26 | userPublicKey: keypair.publicKey.toBase58(), 27 | wrapAndUnwrapSol: true, 28 | dynamicComputeUnitLimit: true, 29 | prioritizationFeeLamports: "auto" 30 | }), 31 | }) 32 | ).json(); 33 | 34 | const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); 35 | const swapTx = VersionedTransaction.deserialize(swapTransactionBuf); 36 | 37 | swapTx.sign([keypair]); 38 | // const latestBlockhash = await connection.getLatestBlockhash(); 39 | 40 | // await executeJitoTx([swapTx], 'processed', latestBlockhash); 41 | await execTx(swapTx, connection, "processed"); 42 | 43 | return true; 44 | } catch (error) { 45 | throw (error); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/x.ts: -------------------------------------------------------------------------------- 1 | import { TwitterApi } from 'twitter-api-v2'; 2 | import { PublicKey } from '@solana/web3.js'; 3 | import { X_BEARER } from '../core/config/env'; 4 | import { logLogger } from './logger'; 5 | 6 | export const checkXAccount = async (username: string) => { 7 | const client = new TwitterApi(X_BEARER); // Replace with your API Bearer Token 8 | const readOnlyClient = client.readOnly; 9 | 10 | try { 11 | // Fetch user details by username 12 | const user = await readOnlyClient.v2.userByUsername(username, { 13 | 'user.fields': 'description,public_metrics' 14 | }); 15 | 16 | if (user) { 17 | // const isVerified = user.data.verified; // Check the verified status 18 | const description = user.data.description || ''; // Retrieve bio (description field) 19 | const followersCount = user.data.public_metrics?.followers_count; // Retrieve follower count 20 | const tweetCount = user.data.public_metrics?.tweet_count; // Retrieve follower count 21 | 22 | // return null if tweets and follwers are too small 23 | if ((tweetCount && tweetCount < 5) || (followersCount && followersCount < 50)) { 24 | logLogger.log("too small - tweetCount:", tweetCount, "followersCount:", followersCount); 25 | return null; 26 | } 27 | 28 | const publicKeyRegex = /[A-Za-z0-9]{32,44}/; 29 | const match = description.match(publicKeyRegex); 30 | 31 | if (match && match[0]) { 32 | try { 33 | const ca = match[0]; 34 | 35 | // logLogger.log('Verified:', isVerified); 36 | return new PublicKey(ca); 37 | } catch (e: any) { 38 | // logLogger.error('failed to parse public key:', e); 39 | } 40 | } 41 | } 42 | } catch (e: any) { 43 | logLogger.error("can not get ca:", e as string); 44 | } 45 | 46 | return null; 47 | }; 48 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { Schema, serialize, deserialize } from "borsh"; 3 | 4 | export type PumpCreateData = { 5 | name: string; 6 | symbol: string; 7 | uri: string; 8 | mint: PublicKey; 9 | bondingCurve: PublicKey; 10 | user: PublicKey; 11 | }; 12 | 13 | export type PumpMigrateData = { 14 | // 16 - discriminator 15 | // 8 - timestamp 16 | // 2 - index 17 | // 32 - creator 18 | baseMint: PublicKey; 19 | quoteMint: PublicKey; 20 | // 1 - baseMintDecimals 21 | // 1 - quoteMintDecimals 22 | // 8 - baseAmountIn 23 | // 8 - quoteAmountIn 24 | // 8 - poolBaseAmount 25 | // 8 - poolQuoteAmount 26 | // 8 - minimumLiquidity 27 | // 8 - initialLiquidity 28 | // 8 - lpTokenAmountOut 29 | // 1 - poolBump 30 | pool: PublicKey; 31 | // 32 - lpMint 32 | // 32 - userBaseTokenAccount 33 | // 32 - userQuoteTOkenAccount 34 | }; 35 | 36 | export type PumpAmmBuyData = { 37 | // 16 - discriminator 38 | // 8 - timestamp 39 | baseAmountOut: bigint; 40 | // 8 - maxQuoteAmountIn 41 | // 8 - userBaseTokenReserves 42 | // 8 - userQuoteTokenReserves 43 | poolBaseTokenReserves: bigint; 44 | poolQuoteTokenReserves: bigint; 45 | // 8 - quoteAmountIn 46 | // 8 - lpFeeBasisPoints 47 | // 8 - lpFee 48 | // 8 - protocolFeeBasisPoints 49 | // 8 - protocolFee 50 | quoteAmountInWithLpFee: bigint; 51 | // 8 - userQuoteAmountIn 52 | // 32 - pool 53 | // 32 - user 54 | // 32 - userBaseTokenAccount 55 | // 32 - userQuoteTokenAccount 56 | // 32 - protocolFeeRecipient 57 | // 32 - protocolFeeRecipientTokenAccount 58 | coinCreator: PublicKey; 59 | // 8 - coinCreatorFeeBasisPoints 60 | // 8 - coinCreatorFee 61 | }; 62 | 63 | export type Socials = { 64 | website?: string; 65 | twitter?: string; 66 | telegram?: string; 67 | // description?: string; 68 | tweetCreatorUsername?: string; 69 | }; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ray-sniper", 3 | "version": "1.0.0", 4 | "description": "solana sniper on raydium, pumpfun, pumpswap, believe(meteora dbc)", 5 | "main": "index.js", 6 | "author": "micro", 7 | "license": "MIT", 8 | "scripts": { 9 | "dev": "ts-node-dev --respawn --clear --transpile-only --ignore-watch node_modules ./src/app.ts", 10 | "build": "tsc", 11 | "start": "node dist/app.js" 12 | }, 13 | "keywords": [ 14 | "solana", 15 | "raydium", 16 | "sniper", 17 | "bot", 18 | "grpc" 19 | ], 20 | "engines": { 21 | "node": ">=20.18.0", 22 | "yarn": ">=1.22.19", 23 | "npm": "please-use-yarn" 24 | }, 25 | "devDependencies": { 26 | "@eslint/js": "^9.18.0", 27 | "@types/compression": "^1.7.5", 28 | "@types/express": "^5.0.0", 29 | "@types/node": "^22.10.7", 30 | "@types/node-cron": "^3.0.11", 31 | "eslint": "^9.18.0", 32 | "globals": "^15.14.0", 33 | "ts-node-dev": "^2.0.0", 34 | "typescript": "^5.7.3", 35 | "typescript-eslint": "^8.21.0" 36 | }, 37 | "dependencies": { 38 | "@coral-xyz/anchor": "=0.30.1", 39 | "@coral-xyz/borsh": "^0.31.1", 40 | "@jito-lab/provider": "^1.0.0", 41 | "@metaplex-foundation/mpl-token-metadata": "^3.3.0", 42 | "@metaplex-foundation/umi": "^1.0.0", 43 | "@project-serum/serum": "^0.13.65", 44 | "@pump-fun/pump-swap-sdk": "^0.0.1-beta.32", 45 | "@raydium-io/raydium-sdk": "^1.3.1-beta.58", 46 | "@solana/spl-token": "^0.4.13", 47 | "@solana/spl-token-registry": "^0.2.4574", 48 | "@solana/web3.js": "=1.68.2", 49 | "@triton-one/yellowstone-grpc": "^2.0.0", 50 | "bs58": "^6.0.0", 51 | "compression": "^1.7.5", 52 | "dotenv": "^16.4.7", 53 | "env-var": "^7.5.0", 54 | "express": "^4.21.2", 55 | "express-rate-limit": "^7.5.0", 56 | "fs": "^0.0.1-security", 57 | "helius-laserstream": "^0.0.6", 58 | "log4js": "^6.9.1", 59 | "mongoose": "^8.13.0", 60 | "node-cron": "^4.0.5", 61 | "twitter-api-v2": "^1.21.1" 62 | } 63 | } -------------------------------------------------------------------------------- /src/lib/pumpfun/sell-token.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { ComputeBudgetProgram, Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js"; 3 | import { PUMP_FUN } from "../../core/constants"; 4 | import { execTx, getAssociatedTokenAccount } from "../../utils/web3"; 5 | import { createCloseAccountInstruction } from "@solana/spl-token"; 6 | import { PumpFunSDK } from "./pumpfun"; 7 | import { solanaConnection, userKp } from "../../core/config/env"; 8 | import { BN } from "@coral-xyz/anchor"; 9 | 10 | export const sellTokens = async ( 11 | tokens: { mint: PublicKey, amount: BN }[], 12 | keypair: Keypair, 13 | connection: Connection, 14 | priorityFee: number = 30_000 15 | ) => { 16 | const provider = new anchor.AnchorProvider(solanaConnection, new anchor.Wallet(userKp), { 17 | commitment: "processed", 18 | }); 19 | const pumpfunSdk = new PumpFunSDK(provider); 20 | 21 | const transaction = new Transaction().add( 22 | ComputeBudgetProgram.setComputeUnitLimit({ units: 180_000 }) 23 | ).add( 24 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }) 25 | ); 26 | 27 | for (const token of tokens) { 28 | // get user ATA 29 | const associatedUser = getAssociatedTokenAccount(keypair.publicKey, token.mint); 30 | 31 | transaction.add( 32 | await pumpfunSdk.program.methods.sell( 33 | token.amount, new anchor.BN(0) 34 | ).accountsPartial({ 35 | user: keypair.publicKey, 36 | mint: token.mint, 37 | associatedUser: associatedUser, 38 | feeRecipient: PUMP_FUN.FEE_RECEIPT, 39 | }).instruction() 40 | ).add( 41 | createCloseAccountInstruction(associatedUser, keypair.publicKey, keypair.publicKey) 42 | ); 43 | } 44 | 45 | const latestBlockhash = await connection.getLatestBlockhash(); 46 | 47 | transaction.feePayer = keypair.publicKey; 48 | transaction.recentBlockhash = latestBlockhash.blockhash; 49 | transaction.sign(keypair); 50 | 51 | await execTx(transaction, connection, "processed"); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/jupiter/sell-token-jup.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, VersionedTransaction } from "@solana/web3.js"; 2 | import { NATIVE_MINT } from "@solana/spl-token"; 3 | import { execTx, getAssociatedTokenAccount } from "../../utils/web3"; 4 | 5 | export const sellTokenJup = async ( 6 | mint: PublicKey, 7 | keypair: Keypair, 8 | connection: Connection, 9 | sellPercent: number = 70, 10 | ) => { 11 | try { 12 | const tokenAccount = getAssociatedTokenAccount(keypair.publicKey, mint); 13 | const balance = await connection.getTokenAccountBalance(tokenAccount); 14 | const sellAmount = Number(balance.value.amount) / 100 * sellPercent; 15 | console.log('sell amount:', sellAmount); 16 | 17 | const quoteResponse = await ( 18 | await fetch( 19 | `https://quote-api.jup.ag/v6/quote?inputMint=${mint.toBase58()}&outputMint=${NATIVE_MINT.toBase58()}&amount=${sellAmount}&slippageBps=${500}` 20 | ) 21 | ).json(); 22 | 23 | const { swapTransaction } = await ( 24 | await fetch("https://quote-api.jup.ag/v6/swap", { 25 | method: "POST", 26 | headers: { 27 | "Content-Type": "application/json", 28 | }, 29 | body: JSON.stringify({ 30 | quoteResponse, 31 | userPublicKey: keypair.publicKey.toBase58(), 32 | wrapAndUnwrapSol: true, 33 | dynamicComputeUnitLimit: true, 34 | prioritizationFeeLamports: "auto" 35 | }), 36 | }) 37 | ).json(); 38 | 39 | const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); 40 | const swapTx = VersionedTransaction.deserialize(swapTransactionBuf); 41 | 42 | swapTx.sign([keypair]); 43 | // const latestBlockhash = await connection.getLatestBlockhash(); 44 | 45 | // await executeJitoTx([swapTx], 'processed', latestBlockhash); 46 | await execTx(swapTx, connection, "processed"); 47 | 48 | return true; 49 | } catch (error) { 50 | // logLogger.log(error as string); 51 | // return false; 52 | throw (error); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/pumpfun/buy-token.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { ComputeBudgetProgram, Connection, Keypair, PublicKey, Transaction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; 3 | import { PumpFunSDK } from "./pumpfun"; 4 | import { BUY_SOL_AMOUNT, solanaConnection, userKp } from "../../core/config/env"; 5 | import { buildJitoTipIx, executeJitoTx } from "../jito"; 6 | import { logLogger } from "../../utils/logger"; 7 | 8 | export const buyToken = async ( 9 | mint: PublicKey, 10 | keypair: Keypair, 11 | connection: Connection, 12 | priorityFee: number = 700_000 13 | ) => { 14 | try { 15 | const provider = new anchor.AnchorProvider(solanaConnection, new anchor.Wallet(userKp), { 16 | commitment: "processed", 17 | }); 18 | const pumpfunSdk = new PumpFunSDK(provider); 19 | 20 | const transaction = new Transaction().add( 21 | ComputeBudgetProgram.setComputeUnitLimit({ units: 76_000 }) 22 | ).add( 23 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }) 24 | ); 25 | 26 | const buyIxs = await pumpfunSdk.getBuyIxsBySolAmount( 27 | keypair.publicKey, 28 | mint, 29 | BigInt(BUY_SOL_AMOUNT), 30 | BigInt(500), 31 | "confirmed" 32 | ); 33 | buyIxs.map((ix) => transaction.add(ix)); 34 | 35 | transaction.add( 36 | buildJitoTipIx(userKp.publicKey) 37 | ); 38 | 39 | const latestBlockhash = await connection.getLatestBlockhash(); 40 | 41 | const messageV0 = new TransactionMessage({ 42 | payerKey: keypair.publicKey, 43 | recentBlockhash: latestBlockhash.blockhash, 44 | instructions: transaction.instructions, 45 | }).compileToV0Message(); 46 | 47 | const versionedTx = new VersionedTransaction(messageV0); 48 | versionedTx.sign([keypair]); 49 | 50 | await executeJitoTx([versionedTx], 'processed', latestBlockhash); 51 | // await execTx(versionedTx, connection, "processed"); 52 | 53 | return true; 54 | } catch (error) { 55 | logLogger.log(error as string); 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/core/config/env.ts: -------------------------------------------------------------------------------- 1 | // src/core/config/env.ts 2 | 3 | import { Cluster, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; 4 | import * as fs from 'fs' 5 | import { get } from 'env-var'; 6 | import 'dotenv/config'; 7 | import { DEVNET_PROGRAM_ID, MAINNET_PROGRAM_ID } from '@raydium-io/raydium-sdk'; 8 | import NodeWallet from '@jito-lab/provider/dist/esm/nodewallet'; 9 | 10 | export const envs = { 11 | PORT: get('PORT').required().asPortNumber(), 12 | API_PREFIX: get('DEFAULT_API_PREFIX').default('/api/v1').asString(), 13 | NODE_ENV: get('NODE_ENV').default('development').asString(), 14 | 15 | SOLANA_MAINNET_RPC: get('SOLANA_MAINNET_RPC').default('https://api.mainnet-beta.solana.com').asString(), 16 | SOLANA_DEVNET_RPC: get('SOLANA_DEVNET_RPC').default('https://api.devnet.solana.com').asString(), 17 | 18 | PRIVATE_KEY_PATH: get('PRIVATE_KEY_PATH').required().asString(), 19 | 20 | XAPI_KEY: get('XAPI_KEY').required().asString(), 21 | XAPI_URL: get('XAPI_URL').required().asString() 22 | }; 23 | 24 | export const JITO_FEE = 100000; 25 | export const X_BEARER = ""; 26 | 27 | export const MAX_CANDLE_CNT = 10; 28 | export const PUMPAMM_TP_RATE = 2; 29 | 30 | export const LAUNCH_MIN_BUY_SOL = 0.3 * LAMPORTS_PER_SOL; // 0.3 SOL 31 | export const LAUNCH_MAX_BUY_SOL = 15 * LAMPORTS_PER_SOL; // 15 SOL 32 | 33 | export const BUY_SOL_AMOUNT = 0.2 * LAMPORTS_PER_SOL; // 0.2 SOL 34 | export const PUMPAMM_BUY_AMOUNT = 0.5 * LAMPORTS_PER_SOL; // 0.5 SOL 35 | 36 | export const userKp = Keypair.fromSecretKey( 37 | Uint8Array.from(JSON.parse(fs.readFileSync(envs.PRIVATE_KEY_PATH, 'utf-8'))), 38 | { skipValidation: true }, 39 | ); 40 | export const userWallet = new NodeWallet(userKp); 41 | 42 | const cluster: Cluster = "mainnet-beta"; 43 | 44 | export const solanaConnection = cluster.toString() == "mainnet-beta" 45 | ? new Connection(envs.SOLANA_MAINNET_RPC) 46 | : new Connection(envs.SOLANA_DEVNET_RPC); 47 | 48 | export const raydiumProgramId = cluster.toString() == "mainnet-beta" 49 | ? MAINNET_PROGRAM_ID 50 | : DEVNET_PROGRAM_ID; 51 | 52 | export const feeDestination = cluster.toString() == "mainnet-beta" 53 | ? new PublicKey("7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5") // Mainnet 54 | : new PublicKey("3XMrhbv989VxAMi3DErLV9eJht1pHppW5LbKxe9fkEFR"); // Devnet -------------------------------------------------------------------------------- /src/core/constants/index.ts: -------------------------------------------------------------------------------- 1 | // src/core/constants/index.ts 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export const SIXTY = 60 as const; 5 | export const ONE_HUNDRED = 100 as const; 6 | export const ONE_THOUSAND = 1000 as const; 7 | 8 | export enum HttpCode { 9 | OK = 200, 10 | CREATED = 201, 11 | NO_CONTENT = 204, 12 | BAD_REQUEST = 400, 13 | UNAUTHORIZED = 401, 14 | FORBIDDEN = 403, 15 | NOT_FOUND = 404, 16 | INTERNAL_SERVER_ERROR = 500 17 | }; 18 | 19 | export const ONE_MIN = 1 * 60 * 1000; // 1 min 20 | export const ONE_SEC = 1 * 1000 // 1 sec 21 | 22 | export const METEORA_DBC_PROGRAM = new PublicKey("dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN"); 23 | 24 | export const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 25 | 26 | export const PUMP_FUN = { 27 | PROGRAM_ID: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", 28 | MINT_AUTH: new PublicKey("TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM"), 29 | FEE_RECEIPT: new PublicKey("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"), 30 | AMM_PROGRAM_ID: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", 31 | MIGRATION: "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg", 32 | 33 | CREATE_IX_DISCRIMINATOR: Buffer.from([0x18, 0x1e, 0xc8, 0x28, 0x05, 0x1c, 0x07, 0x77]), 34 | CREATE_CPI_DISCRIMINATOR: Buffer.from([0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x1b, 0x72, 0xa9, 0x4d, 0xde, 0xeb, 0x63, 0x76]), 35 | 36 | MIGRATE_IX_DISCRIMINATOR: Buffer.from([0x9b, 0xea, 0xe7, 0x92, 0xec, 0x9e, 0xa2, 0x1e]), 37 | MIGRATE_CPI_DISCRIMINATOR: Buffer.from([0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xb1, 0x31, 0x0c, 0xd2, 0xa0, 0x76, 0xa7, 0x74]), 38 | 39 | BUY_CPI_DISCRIMINATOR: Buffer.from([0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x67, 0xf4, 0x52, 0x1f, 0x2c, 0xf5, 0x77, 0x77]) 40 | }; 41 | 42 | export const RAYDIUM = { 43 | LAUNCHPAD_AUTHORITY: "WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh", 44 | CREATE_IX_DISCRIMINATOR: Buffer.from([0xaf, 0xaf, 0x6d, 0x1f, 0x0d, 0x98, 0x9b, 0xed]), 45 | CREATE_CPI_DISCRIMINATOR: Buffer.from([0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x97, 0xd7, 0xe2, 0x09, 0x76, 0xa1, 0x73, 0xae]) 46 | }; 47 | 48 | export const BELIEVE = { 49 | AUTHORITY: "5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE", 50 | CREATE_IX_DISCRIMINATOR: Buffer.from([0x8c, 0x55, 0xd7, 0xb0, 0x66, 0x36, 0x68, 0x4f]) 51 | } 52 | 53 | export const MINT_POST_BALANCE = 1461600; 54 | 55 | export const X_BLACKLIST = [ 56 | "elonmusk", 57 | "solana", 58 | "home", 59 | "binance" 60 | ]; 61 | 62 | export const TARGET_WALLETS = [ 63 | ]; 64 | -------------------------------------------------------------------------------- /src/lib/pumpfun/globalAccount.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { struct, bool, u64, publicKey, Layout } from "@coral-xyz/borsh"; 3 | 4 | export class GlobalAccount { 5 | public discriminator: bigint; 6 | public initialized: boolean = false; 7 | public authority: PublicKey; 8 | public feeRecipient: PublicKey; 9 | public initialVirtualTokenReserves: bigint; 10 | public initialVirtualSolReserves: bigint; 11 | public initialRealTokenReserves: bigint; 12 | public tokenTotalSupply: bigint; 13 | public feeBasisPoints: bigint; 14 | 15 | constructor( 16 | discriminator: bigint, 17 | initialized: boolean, 18 | authority: PublicKey, 19 | feeRecipient: PublicKey, 20 | initialVirtualTokenReserves: bigint, 21 | initialVirtualSolReserves: bigint, 22 | initialRealTokenReserves: bigint, 23 | tokenTotalSupply: bigint, 24 | feeBasisPoints: bigint 25 | ) { 26 | this.discriminator = discriminator; 27 | this.initialized = initialized; 28 | this.authority = authority; 29 | this.feeRecipient = feeRecipient; 30 | this.initialVirtualTokenReserves = initialVirtualTokenReserves; 31 | this.initialVirtualSolReserves = initialVirtualSolReserves; 32 | this.initialRealTokenReserves = initialRealTokenReserves; 33 | this.tokenTotalSupply = tokenTotalSupply; 34 | this.feeBasisPoints = feeBasisPoints; 35 | } 36 | 37 | getInitialBuyPrice(amount: bigint): bigint { 38 | if (amount <= 0n) { 39 | return 0n; 40 | } 41 | 42 | let n = this.initialVirtualSolReserves * this.initialVirtualTokenReserves; 43 | let i = this.initialVirtualSolReserves + amount; 44 | let r = n / i + 1n; 45 | let s = this.initialVirtualTokenReserves - r; 46 | return s < this.initialRealTokenReserves 47 | ? s 48 | : this.initialRealTokenReserves; 49 | } 50 | 51 | public static fromBuffer(buffer: Buffer): GlobalAccount { 52 | const structure: Layout = struct([ 53 | u64("discriminator"), 54 | bool("initialized"), 55 | publicKey("authority"), 56 | publicKey("feeRecipient"), 57 | u64("initialVirtualTokenReserves"), 58 | u64("initialVirtualSolReserves"), 59 | u64("initialRealTokenReserves"), 60 | u64("tokenTotalSupply"), 61 | u64("feeBasisPoints"), 62 | ]); 63 | 64 | let value = structure.decode(buffer); 65 | return new GlobalAccount( 66 | BigInt(value.discriminator), 67 | value.initialized, 68 | value.authority, 69 | value.feeRecipient, 70 | BigInt(value.initialVirtualTokenReserves), 71 | BigInt(value.initialVirtualSolReserves), 72 | BigInt(value.initialRealTokenReserves), 73 | BigInt(value.tokenTotalSupply), 74 | BigInt(value.feeBasisPoints) 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/handler/handle-migrate.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import BoughtModel from '../models/boughtModel' 3 | import AmmBoughtModel from '../models/ammBoughtModel' 4 | 5 | import { infoLogger, logLogger } from "../utils/logger"; 6 | import { PumpMigrateData } from "../utils/types"; 7 | import { NATIVE_MINT } from "@solana/spl-token"; 8 | import { sellTokenPumpswap } from "../lib/pumpswap/sell-pumpswap"; 9 | import { PUMPAMM_BUY_AMOUNT, solanaConnection, userKp } from "../core/config/env"; 10 | import { buyTokenPumpswap } from "../lib/pumpswap/buy-pumpswap"; 11 | import { BN } from "@coral-xyz/anchor"; 12 | 13 | export const handleMigrate = async (migrateData: PumpMigrateData, tx: string, solReserve: bigint, tokenReserve: bigint, coinCreator: PublicKey, hasTarget: boolean) => { 14 | const mint = migrateData.baseMint.equals(NATIVE_MINT) ? migrateData.quoteMint : migrateData.baseMint; 15 | 16 | logLogger.log("================================"); 17 | logLogger.log(`migrate, mint: ${mint.toBase58()}, tx: ${tx}`); 18 | 19 | //// buy this token 20 | if (solReserve != BigInt(0) && tokenReserve != BigInt(0)) { 21 | if (solReserve >= BigInt(370000000000) && hasTarget) { 22 | logLogger.log(`solReserve: ${solReserve}, tokenReserve: ${tokenReserve}`); 23 | 24 | // get buy token amount 25 | let n = solReserve * tokenReserve; 26 | let i = solReserve + (BigInt(PUMPAMM_BUY_AMOUNT) * BigInt(100) / BigInt(101)); 27 | let r = n / i + 1n; 28 | let baseAmountOut = tokenReserve - r; 29 | 30 | try { 31 | infoLogger.log("buy token:", mint.toBase58()); 32 | 33 | // buy token 34 | await buyTokenPumpswap( 35 | migrateData.pool, 36 | mint, 37 | new BN(baseAmountOut), 38 | new BN(PUMPAMM_BUY_AMOUNT * 1.1), 39 | coinCreator, 40 | userKp, 41 | solanaConnection); 42 | 43 | // save buy data to db 44 | await new AmmBoughtModel({ 45 | mint: mint.toBase58(), 46 | pool: migrateData.pool.toBase58(), 47 | creator: coinCreator.toBase58(), 48 | solReserve: solReserve.toString(), 49 | tokenReserve: tokenReserve.toString() 50 | }).save(); 51 | } catch (e) { 52 | logLogger.log(e as string); 53 | } 54 | return; 55 | } 56 | } 57 | 58 | // check bought this token 59 | const exist = await BoughtModel.exists({ mint: mint.toBase58() }); 60 | 61 | if (!exist) return; 62 | 63 | infoLogger.info("sell token, mint:", mint.toBase58()); 64 | 65 | //// sell token 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import * as log4js from "log4js"; 2 | // Function to generate log file names based on a prefix 3 | function generateLogFileName(prefix: string): string { 4 | const now = new Date(); 5 | const year = now.getFullYear(); 6 | const month = String(now.getMonth() + 1).padStart(2, '0'); 7 | const day = String(now.getDate()).padStart(2, '0'); 8 | // const hours = String(now.getHours()).padStart(2, '0'); 9 | // const minutes = String(now.getMinutes()).padStart(2, '0'); 10 | // const seconds = String(now.getSeconds()).padStart(2, '0'); 11 | // Return the formatted file name 12 | return `logs/${prefix}/${prefix}-${year}-${month}-${day}.txt`; 13 | } 14 | 15 | // Export loggers using CommonJS syntax 16 | export const defaultLogger = log4js.configure({ 17 | appenders: { 18 | console: { type: 'console' }, 19 | info: { type: 'file', filename: generateLogFileName('info') }, 20 | log: { type: 'file', filename: generateLogFileName('log') }, 21 | error: { type: 'file', filename: generateLogFileName('error') } 22 | }, 23 | categories: { 24 | default: { appenders: ['console'], level: 'debug' }, 25 | info: { appenders: ['info'], level: 'info' }, 26 | log: { appenders: ['log'], level: 'debug' }, 27 | error: { appenders: ['error'], level: 'error' } 28 | } 29 | }).getLogger(); 30 | 31 | export const infoLogger = log4js.configure({ 32 | appenders: { 33 | console: { type: 'console' }, 34 | info: { type: 'file', filename: generateLogFileName('info') }, 35 | log: { type: 'file', filename: generateLogFileName('log') }, 36 | error: { type: 'file', filename: generateLogFileName('error') } 37 | }, 38 | categories: { 39 | default: { appenders: ['console'], level: 'debug' }, 40 | info: { appenders: ['info'], level: 'info' }, 41 | log: { appenders: ['log'], level: 'debug' }, 42 | error: { appenders: ['error'], level: 'error' } 43 | } 44 | }).getLogger('info'); 45 | 46 | export const logLogger = log4js.configure({ 47 | appenders: { 48 | console: { type: 'console' }, 49 | info: { type: 'file', filename: generateLogFileName('info') }, 50 | log: { type: 'file', filename: generateLogFileName('log') }, 51 | error: { type: 'file', filename: generateLogFileName('error') } 52 | }, 53 | categories: { 54 | default: { appenders: ['console'], level: 'debug' }, 55 | info: { appenders: ['info'], level: 'info' }, 56 | log: { appenders: ['log'], level: 'debug' }, 57 | error: { appenders: ['error'], level: 'error' } 58 | } 59 | }).getLogger('log'); 60 | 61 | export const errorLogger = log4js.configure({ 62 | appenders: { 63 | console: { type: 'console' }, 64 | info: { type: 'file', filename: generateLogFileName('info') }, 65 | log: { type: 'file', filename: generateLogFileName('log') }, 66 | error: { type: 'file', filename: generateLogFileName('error') } 67 | }, 68 | categories: { 69 | default: { appenders: ['console'], level: 'debug' }, 70 | info: { appenders: ['info'], level: 'info' }, 71 | log: { appenders: ['log'], level: 'debug' }, 72 | error: { appenders: ['error'], level: 'error' } 73 | } 74 | }).getLogger('error'); 75 | -------------------------------------------------------------------------------- /src/handler/handle-create.ts: -------------------------------------------------------------------------------- 1 | import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; 2 | 3 | import { checkMetadata } from "../checker/check-metadata"; 4 | import { sleep } from "../utils/sleep"; 5 | import { ONE_SEC } from "../core/constants"; 6 | import { errorLogger, infoLogger, logLogger } from "../utils/logger"; 7 | import { countUniquePosts, getCa } from "../utils/xapi"; 8 | import { solanaConnection, userKp } from "../core/config/env"; 9 | import TokenModel from "../models/tokenModel"; 10 | 11 | export const handleCreate = async ( 12 | launchpad: string, 13 | name: string, 14 | symbol: string, 15 | uri: string, 16 | mint: PublicKey, 17 | vault: PublicKey, 18 | tokenVault: PublicKey, 19 | tx: string, 20 | lamports: number, 21 | supply: number 22 | ) => { 23 | // check uri is on ipfs 24 | if (!uri.startsWith("https://ipfs.io/ipfs/")) return; 25 | 26 | //// criteria_1 27 | 28 | // check metadata from ipfs 29 | const { code, link } = await checkMetadata(uri); 30 | if (code < 0) return; 31 | 32 | // wait for 15 sec and get vault balance 33 | await sleep(15 * ONE_SEC); 34 | const vaultBalance1 = await solanaConnection.getBalance(vault, "processed"); 35 | 36 | //// criteria_2 37 | 38 | // get holders cnt 39 | const holders1 = await solanaConnection.getTokenLargestAccounts(mint, "processed"); 40 | const { count: cntHolders1, sum: topAmount1 } = holders1.value.reduce((acc, value) => { 41 | if (value.uiAmount) { 42 | acc.count += 1; 43 | if (!value.address.equals(tokenVault)) 44 | acc.sum += value.uiAmount; 45 | } 46 | return acc; 47 | }, { count: 0, sum: 0 }); 48 | const topPercent1 = topAmount1 / supply * 100; 49 | 50 | //// criteria_3 51 | 52 | //// criteria_4 53 | //// get posts on x 54 | const posts = await countUniquePosts(mint.toBase58()); 55 | 56 | //// criteria_5 - posts 57 | 58 | logLogger.log("-----------------------------------------"); 59 | logLogger.log(`launch: ${launchpad}, tx: ${tx}`); 60 | logLogger.log(`buy: ${(lamports / LAMPORTS_PER_SOL).toFixed(1)}, name: ${name}, symbol: ${symbol}, mint: ${mint.toBase58()}, link: ${link}`); 61 | logLogger.log(`hold1: ${cntHolders1}, topP1: ${topPercent1.toFixed(1)}`); 62 | // logLogger.log(`vault1: ${(vaultBalance1 / LAMPORTS_PER_SOL).toFixed(1)}, vault2: ${(vaultBalance2 / LAMPORTS_PER_SOL).toFixed(1)}, vault3: ${(vaultBalance3 / LAMPORTS_PER_SOL).toFixed(1)}, vault4: ${(vaultBalance4 / LAMPORTS_PER_SOL).toFixed(1)}`); 63 | logLogger.log(`user: ${posts.users}, like: ${posts.likes}, view: ${posts.views}`) 64 | 65 | infoLogger.log(`mint: ${(vaultBalance1 / LAMPORTS_PER_SOL).toFixed(1)}:`); 66 | 67 | 68 | try { 69 | //// buy token 70 | // use jupiter or custom buy instructions from ../lib/ 71 | 72 | // save token to tokensModel 73 | await new TokenModel({ 74 | mint: mint.toBase58(), 75 | name, 76 | symbol, 77 | handle: link, 78 | uri, 79 | vault: vault.toBase58(), 80 | balance: vaultBalance1 81 | }).save() 82 | } catch (e) { 83 | errorLogger.error("can not save to token model, e:", e as string); 84 | } 85 | 86 | return; 87 | } 88 | -------------------------------------------------------------------------------- /src/lib/pumpswap/sell-pumpswap.ts: -------------------------------------------------------------------------------- 1 | import { ComputeBudgetProgram, Connection, Keypair, PublicKey, SystemProgram, Transaction } from "@solana/web3.js"; 2 | import { execTx, getAssociatedTokenAccount } from "../../utils/web3"; 3 | import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, createSyncNativeInstruction, getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 4 | import { PumpSwapSDK } from "./pumpswap"; 5 | import { solanaConnection, userKp } from "../../core/config/env"; 6 | import { BN } from "@coral-xyz/anchor"; 7 | 8 | export const sellTokenPumpswap = async ( 9 | baseMint: PublicKey, 10 | quoteMint: PublicKey, 11 | pool: PublicKey, 12 | coinCreator: PublicKey, 13 | 14 | sellPercent: number, 15 | 16 | keypair: Keypair, 17 | connection: Connection, 18 | priorityFee: number = 30_000 19 | ) => { 20 | if (sellPercent > 100) return; 21 | 22 | try { 23 | const pumpswapSdk = new PumpSwapSDK(solanaConnection); 24 | 25 | const userBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, keypair.publicKey); 26 | const quoteBaseTokenAccount = getAssociatedTokenAddressSync(quoteMint, keypair.publicKey); 27 | 28 | const tokenBalance = await connection.getTokenAccountBalance(userBaseTokenAccount, "processed"); 29 | 30 | const amount = sellPercent == 100 ? 31 | new BN(tokenBalance.value.amount) : 32 | new BN(tokenBalance.value.amount).mul(new BN(sellPercent)).div(new BN(100)); 33 | 34 | const transaction = new Transaction().add( 35 | ComputeBudgetProgram.setComputeUnitLimit({ units: 180_000 }) 36 | ).add( 37 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }) 38 | ).add( 39 | createAssociatedTokenAccountInstruction( 40 | userKp.publicKey, 41 | quoteBaseTokenAccount, 42 | userKp.publicKey, 43 | NATIVE_MINT 44 | ) 45 | ).add( 46 | await pumpswapSdk.getSellInstruction( 47 | amount, 48 | new BN(0), { 49 | baseMint, 50 | quoteMint, 51 | pool, 52 | baseTokenProgram: TOKEN_PROGRAM_ID, 53 | quoteTokenProgram: TOKEN_PROGRAM_ID, 54 | user: keypair.publicKey, 55 | coinCreator 56 | }) 57 | ).add( 58 | createCloseAccountInstruction( 59 | quoteBaseTokenAccount, 60 | userKp.publicKey, 61 | userKp.publicKey 62 | ) 63 | ); 64 | if (sellPercent == 100) { 65 | transaction.add( 66 | createCloseAccountInstruction(userBaseTokenAccount, keypair.publicKey, keypair.publicKey) 67 | ); 68 | } 69 | 70 | const latestBlockhash = await connection.getLatestBlockhash(); 71 | 72 | transaction.feePayer = keypair.publicKey; 73 | transaction.recentBlockhash = latestBlockhash.blockhash; 74 | transaction.sign(keypair); 75 | 76 | await execTx(transaction, connection, "processed"); 77 | } catch (e) { 78 | // errorLogger.error("can not sell token, e:", e as string); 79 | console.log("can not sell token, e:", e as string); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/checker/check-metadata.ts: -------------------------------------------------------------------------------- 1 | import { X_BLACKLIST } from "../core/constants"; 2 | import { errorLogger } from "../utils/logger"; 3 | import { Socials } from "../utils/types"; 4 | 5 | // check and return X link 6 | export const checkMetadata = async (uri: string): Promise<{ code: number; link: string; }> => { 7 | try { 8 | // check if any social inclues one of the keywords 9 | const jsonData = await (await fetch(uri)).json(); 10 | 11 | const socials = getSocialLinks(jsonData); 12 | 13 | // return X link 14 | const xHandle = extractTwitterHandle(socials.twitter) || socials.tweetCreatorUsername; 15 | 16 | if (!xHandle) { 17 | if (!socials.website) 18 | return { code: -1, link: "" }; 19 | else 20 | return { code: 2, link: socials.website }; 21 | } 22 | 23 | // check xHandle is in the blacklist 24 | if (X_BLACKLIST.includes(xHandle)) 25 | return { code: -2, link: "" }; 26 | 27 | return { code: 1, link: xHandle }; 28 | } catch (e) { 29 | errorLogger.log("Error during parse uri", e as string); 30 | errorLogger.log("uri:", uri); 31 | } 32 | return { code: -3, link: "" }; 33 | } 34 | 35 | // Function to get values if they exist 36 | const getSocialLinks = (data: any): Socials => { 37 | // Function to deeply search for the keys 38 | const findKeyDeep = (obj: any, key: string): string | undefined => { 39 | if (typeof obj !== "object" || obj === null) { 40 | return undefined; 41 | } 42 | 43 | if (key in obj && typeof obj[key] === "string") { 44 | return obj[key]; 45 | } 46 | 47 | for (const k in obj) { 48 | if (obj.hasOwnProperty(k)) { 49 | const result = findKeyDeep(obj[k], key); 50 | if (result) { 51 | return result; 52 | } 53 | } 54 | } 55 | 56 | return undefined; 57 | }; 58 | 59 | // Use the helper to search for values 60 | return { 61 | website: findKeyDeep(data, "website"), 62 | twitter: findKeyDeep(data, "twitter"), 63 | telegram: findKeyDeep(data, "telegram"), 64 | tweetCreatorUsername: findKeyDeep(data, "tweetCreatorUsername") 65 | // description: findKeyDeep(data, "description"), 66 | }; 67 | } 68 | 69 | const extractTwitterHandle = (url: string | undefined) => { 70 | if (!url) return null; // If no URL is provided, return null. 71 | 72 | // Regular expression to extract the handle from valid Twitter or X profile URLs. 73 | const match = url.match(/^https?:\/\/(www\.)?(twitter\.com|x\.com)\/@?(?[a-zA-Z0-9_]+)$/); 74 | 75 | // If a match is found, return the handle with an '@'. Otherwise, return null. 76 | return match?.groups?.handle ? `${match.groups.handle}` : null; 77 | } 78 | 79 | const allHaveThreeOrFewerDigits = (socials: Socials): boolean => { 80 | // Iterate over the values of the Socials object 81 | return Object.values(socials).every(value => { 82 | if (value === undefined) 83 | return true; // Skip undefined properties 84 | 85 | // Count the number of numerical digits in the string using a regular expression 86 | const digitCount = (value.match(/\d/g) || []).length; 87 | return digitCount <= 3; // Ensure the number of digits is 3 or fewer 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /src/lib/pumpswap/buy-pumpswap.ts: -------------------------------------------------------------------------------- 1 | import { ComputeBudgetProgram, Connection, Keypair, PublicKey, SystemProgram, Transaction } from "@solana/web3.js"; 2 | import { execTx, getAssociatedTokenAccount } from "../../utils/web3"; 3 | import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, createSyncNativeInstruction, getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 4 | import { PumpSwapSDK } from "./pumpswap"; 5 | import { solanaConnection, userKp } from "../../core/config/env"; 6 | import { BN } from "@coral-xyz/anchor"; 7 | 8 | export const buyTokenPumpswap = async ( 9 | pool: PublicKey, 10 | baseMint: PublicKey, 11 | 12 | baseAmountOut: BN, 13 | maxQuoteAmountIn: BN, 14 | 15 | coinCreator: PublicKey, 16 | 17 | keypair: Keypair, 18 | connection: Connection, 19 | priorityFee: number = 100_000 20 | ) => { 21 | 22 | try { 23 | const pumpswapSdk = new PumpSwapSDK(solanaConnection); 24 | 25 | const baseTokenAccount = getAssociatedTokenAddressSync(baseMint, keypair.publicKey); 26 | const quoteTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, keypair.publicKey); 27 | 28 | const transaction = new Transaction().add( 29 | ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }) 30 | ).add( 31 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }) 32 | ).add( // Create WSOL account 33 | createAssociatedTokenAccountInstruction( 34 | userKp.publicKey, 35 | quoteTokenAccount, 36 | userKp.publicKey, 37 | NATIVE_MINT 38 | ) 39 | ).add( // transfer SOL to WSOL account 40 | SystemProgram.transfer({ 41 | fromPubkey: userKp.publicKey, 42 | toPubkey: quoteTokenAccount, 43 | lamports: maxQuoteAmountIn 44 | }) 45 | ).add( // sync WSOL account 46 | createSyncNativeInstruction(quoteTokenAccount) 47 | ).add( // Create Token Account 48 | createAssociatedTokenAccountInstruction( 49 | userKp.publicKey, 50 | baseTokenAccount, 51 | userKp.publicKey, 52 | baseMint 53 | ) 54 | ).add( 55 | await pumpswapSdk.getBuyInstruction( 56 | baseAmountOut, 57 | maxQuoteAmountIn, 58 | { 59 | baseMint, 60 | baseTokenProgram: TOKEN_PROGRAM_ID, 61 | pool, 62 | quoteMint: NATIVE_MINT, 63 | quoteTokenProgram: TOKEN_PROGRAM_ID, 64 | user: keypair.publicKey, 65 | coinCreator 66 | } 67 | ) 68 | ).add( // Close WSOL account 69 | createCloseAccountInstruction( 70 | quoteTokenAccount, 71 | userKp.publicKey, 72 | userKp.publicKey 73 | ) 74 | ); 75 | 76 | const latestBlockhash = await connection.getLatestBlockhash(); 77 | 78 | transaction.feePayer = keypair.publicKey; 79 | transaction.recentBlockhash = latestBlockhash.blockhash; 80 | transaction.sign(keypair); 81 | 82 | await execTx(transaction, connection, "processed"); 83 | } catch (e) { 84 | throw ("can not buy token in pumpAmm, e:"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/utils/web3.ts: -------------------------------------------------------------------------------- 1 | import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 2 | import { Connection, PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js"; 3 | import { PUMP_FUN } from "../core/constants"; 4 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 5 | 6 | export const getAssociatedTokenAccount = (ownerPubkey: PublicKey, mintPk: PublicKey): PublicKey => { 7 | return PublicKey.findProgramAddressSync( 8 | [ 9 | ownerPubkey.toBytes(), 10 | TOKEN_PROGRAM_ID.toBytes(), 11 | mintPk.toBytes(), // mint address 12 | ], 13 | ASSOCIATED_TOKEN_PROGRAM_ID 14 | )[0]; 15 | } 16 | 17 | export const getBondingCurvePDA = (mint: PublicKey): PublicKey => { 18 | const BONDING_CURVE_SEED = "bonding-curve"; 19 | 20 | return PublicKey.findProgramAddressSync( 21 | [ 22 | Buffer.from(BONDING_CURVE_SEED), 23 | mint.toBuffer() 24 | ], 25 | new PublicKey(PUMP_FUN.PROGRAM_ID) 26 | )[0]; 27 | } 28 | 29 | export const execTx = async ( 30 | transaction: Transaction | VersionedTransaction, 31 | connection: Connection, 32 | commitment: "processed" | "confirmed" | "finalized" = 'confirmed' 33 | ) => { 34 | try { 35 | // Serialize, send and confirm the transaction 36 | const rawTransaction = transaction.serialize() 37 | 38 | const txid = await connection.sendRawTransaction(rawTransaction, { 39 | skipPreflight: true, 40 | maxRetries: 2, 41 | preflightCommitment: "processed" 42 | }); 43 | console.log(`https://solscan.io/tx/${txid}?cluster=custom&customUrl=${connection.rpcEndpoint}`); 44 | 45 | const confirmed = await connection.confirmTransaction(txid, commitment); 46 | 47 | console.log("err ", confirmed.value.err) 48 | } catch (e) { 49 | console.log(e); 50 | } 51 | } 52 | 53 | export const execAllTx = async ( 54 | transaction: (Transaction | VersionedTransaction)[], 55 | connection: Connection, 56 | commitment: "processed" | "confirmed" | "finalized" = 'confirmed' 57 | ) => { 58 | try { 59 | const rawTransactions = transaction.map((tx) => { 60 | // Serialize, send and confirm the transaction 61 | return tx.serialize(); 62 | }); 63 | 64 | const txHashes = await Promise.all( 65 | rawTransactions.map(async (rawTx) => { 66 | const txid = await connection.sendRawTransaction(rawTx, { 67 | skipPreflight: true, 68 | maxRetries: 2, 69 | preflightCommitment: "processed" 70 | }); 71 | console.log(`https://solscan.io/tx/${txid}?cluster=custom&customUrl=${connection.rpcEndpoint}`); 72 | 73 | return txid; 74 | })); 75 | 76 | // const confirmed = await connection.confirmTransaction(txid, commitment); 77 | 78 | // console.log("err ", confirmed.value.err) 79 | } catch (e) { 80 | console.log(e); 81 | } 82 | } 83 | 84 | export const findInstructionWithDiscriminator = (array: any[], discriminator: Buffer) => { 85 | for (const item of array) { 86 | const found = item.instructions.find( 87 | (instruction: any) => 88 | instruction.data.slice(0, discriminator.length).compare(discriminator) === 0 89 | ); 90 | if (found) { 91 | return found; 92 | } 93 | } 94 | return null; 95 | }; 96 | -------------------------------------------------------------------------------- /src/cron/watch-pools.ts: -------------------------------------------------------------------------------- 1 | import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; 2 | import TokenModel from '../models/tokenModel' 3 | import { errorLogger, logLogger } from '../utils/logger'; 4 | import { sleep } from '../utils/sleep'; 5 | import { PUMPAMM_TP_RATE, solanaConnection, userKp } from '../core/config/env'; 6 | import fs from 'fs'; 7 | import path from 'path'; 8 | import { sellTokenJup } from '../lib/jupiter/sell-token-jup'; 9 | 10 | export const checkPoolBalance = async () => { 11 | // console.log("=========================="); 12 | // console.log("cron - check pool balance"); 13 | 14 | try { 15 | const tokens = await TokenModel.find({}); 16 | 17 | for (const token of tokens) { 18 | // check pool 19 | const vault = new PublicKey(token.vault); 20 | const solReserve = await solanaConnection.getBalance(vault, 'processed'); 21 | 22 | // sell token if needed 23 | if (solReserve >= token.balance * PUMPAMM_TP_RATE) { 24 | try { 25 | //// use sell in from jup or custom ix in ../lib/ 26 | 27 | 28 | await token.deleteOne() 29 | } catch (e) { 30 | console.log(e); 31 | } 32 | } 33 | 34 | await modifyLastLogFile(token.mint, (solReserve / LAMPORTS_PER_SOL).toFixed(1)); 35 | 36 | if (solReserve / LAMPORTS_PER_SOL <= 5) { 37 | // remove token from db 38 | await token.deleteOne() 39 | } 40 | 41 | // sleep 500m sec to avoid rate limit 42 | await sleep(500); 43 | } 44 | } catch (e) { 45 | errorLogger.log("error fetching all tokens:", e); 46 | } 47 | } 48 | 49 | const modifyLastLogFile = async (mint: string, balance: string) => { 50 | const logDir = path.join(__dirname, '../../logs/info'); 51 | 52 | fs.readdir(logDir, async (err, files) => { 53 | if (err) { 54 | errorLogger.log('Error reading directory:', err); 55 | return; 56 | } 57 | 58 | // Filter for .log files and get the last one sorted by name (or you could sort by modified time if needed) 59 | const logFiles = files.filter(file => file.endsWith('.txt')); 60 | const lastFile = logFiles.sort().pop(); // Get the last file (based on alphanumeric sort) 61 | 62 | if (!lastFile) { 63 | errorLogger.log('No log files found.'); 64 | return; 65 | } 66 | 67 | const filePath = path.join(logDir, lastFile); 68 | 69 | // Read the last file 70 | fs.readFile(filePath, 'utf-8', (err, data) => { 71 | if (err) { 72 | errorLogger.log('Error reading file:', err); 73 | return; 74 | } 75 | 76 | // Split the content into lines 77 | const lines = data.split('\n'); 78 | 79 | // Modify the specific line 80 | const updatedLines = lines.map(line => { 81 | if (line.includes(mint)) { 82 | return line + ': ' + balance; // Append balance 83 | } 84 | return line; 85 | }); 86 | 87 | // Write the updated content back to the file 88 | fs.writeFile(filePath, updatedLines.join('\n'), 'utf-8', (err) => { 89 | if (err) { 90 | errorLogger.log('Error writing to file:', err); 91 | return; 92 | } 93 | errorLogger.log(`Successfully updated the line in ${lastFile}.`); 94 | }); 95 | }); 96 | }); 97 | }; 98 | -------------------------------------------------------------------------------- /src/utils/xapi.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { envs } from '../core/config/env'; 3 | import { errorLogger, logLogger } from './logger'; 4 | 5 | export const getCa = async (username: string) => { 6 | const options = { 7 | method: 'GET', 8 | headers: { 9 | 'X-API-Key': envs.XAPI_KEY, 10 | } 11 | }; 12 | 13 | try { 14 | const response = await fetch(`${envs.XAPI_URL}/user/info/?userName=${username}`, options); 15 | 16 | if (!response.ok) { 17 | throw new Error(`HTTP error! Status: ${response.status}`); 18 | } 19 | 20 | const data = (await response.json()).data; 21 | const postCount = data.statusesCount; 22 | const followerCount = data.followers; 23 | const followingCount = data.following; 24 | const description = data.description; 25 | const createdAt = new Date(data.createdAt).getTime(); 26 | 27 | // return 0 if tweets and follwers are too small 28 | if (postCount < 10 || followerCount < 50 || followingCount > followerCount / 2) { 29 | // logLogger.log("xxx - posts:", postCount, "followers:", followerCount, "following:", followingCount); 30 | return -1; 31 | } 32 | 33 | // return -1 if there're too many followers 34 | if (followerCount > 50000) { // 50k 35 | // logLogger.log("too many followers - followerCount:", followerCount); 36 | return -1; 37 | } 38 | 39 | // return -1 if the account is created more than a month ago 40 | const aMonth = 30 * 24 * 60 * 60 * 1000; 41 | if ((new Date().getTime()) - createdAt > aMonth) { 42 | // logLogger.log("account is created more than a month ago"); 43 | return -1; 44 | } 45 | 46 | if (!description || description.length < 32) { 47 | // logLogger.log("has problem with description, x:", username); 48 | return 0; 49 | } 50 | 51 | const publicKeyRegex = /[A-Za-z0-9]{32,44}/; 52 | const match = description.match(publicKeyRegex); 53 | 54 | if (match && match[0]) { 55 | try { 56 | const ca = match[0]; 57 | 58 | logLogger.log('ca:', ca); 59 | return new PublicKey(ca); 60 | } catch (e: any) { 61 | // logLogger.error('failed to parse public key:', e); 62 | } 63 | } 64 | } catch (err) { 65 | errorLogger.error('can not get ca:', err as string); 66 | } 67 | 68 | 69 | return 0; 70 | }; 71 | 72 | export const countUniquePosts = async (query: string): Promise<{ users: number, likes: number, views: number }> => { 73 | const options = { 74 | method: 'GET', 75 | headers: { 76 | 'X-API-Key': envs.XAPI_KEY, 77 | } 78 | }; 79 | 80 | return await fetch(`${envs.XAPI_URL}/tweet/advanced_search/?query=${query}`, options) 81 | .then(response => response.json()) 82 | .then(response => { 83 | const uniqueUserNames = new Set(); 84 | let likeCount = 0; 85 | let viewCount = 0; 86 | const now = new Date(); 87 | 88 | for (const tweet of response.tweets) { 89 | const author = tweet.author; 90 | const digitCount = (author.userName.match(/\d/g) || []).length; 91 | 92 | if (author.userName.length < 15 93 | && digitCount <= 3 94 | && author.followers > author.following * 2 95 | && author.followers > 50 96 | && (now.getTime() - new Date(author.createdAt).getTime()) > 30 * 24 * 60 * 60 * 1000 97 | && !uniqueUserNames.has(author.userName)) { 98 | 99 | uniqueUserNames.add(author.userName); 100 | likeCount += tweet.likeCount; 101 | viewCount += tweet.viewCount; 102 | } 103 | } 104 | 105 | return { users: uniqueUserNames.size, likes: likeCount, views: viewCount }; 106 | }) 107 | .catch(err => { 108 | errorLogger.log("failed to search x"); 109 | errorLogger.log(err as string); 110 | return { users: 0, likes: 0, views: 0 }; 111 | }); 112 | } 113 | -------------------------------------------------------------------------------- /src/lib/pumpfun/bondingCurveAccount.ts: -------------------------------------------------------------------------------- 1 | import { struct, bool, u64, Layout } from "@coral-xyz/borsh"; 2 | 3 | export class BondingCurveAccount { 4 | public discriminator: bigint; 5 | public virtualTokenReserves: bigint; 6 | public virtualSolReserves: bigint; 7 | public realTokenReserves: bigint; 8 | public realSolReserves: bigint; 9 | public tokenTotalSupply: bigint; 10 | public complete: boolean; 11 | 12 | constructor( 13 | discriminator: bigint, 14 | virtualTokenReserves: bigint, 15 | virtualSolReserves: bigint, 16 | realTokenReserves: bigint, 17 | realSolReserves: bigint, 18 | tokenTotalSupply: bigint, 19 | complete: boolean 20 | ) { 21 | this.discriminator = discriminator; 22 | this.virtualTokenReserves = virtualTokenReserves; 23 | this.virtualSolReserves = virtualSolReserves; 24 | this.realTokenReserves = realTokenReserves; 25 | this.realSolReserves = realSolReserves; 26 | this.tokenTotalSupply = tokenTotalSupply; 27 | this.complete = complete; 28 | } 29 | 30 | getBuyPrice(amount: bigint): bigint { 31 | if (this.complete) { 32 | throw new Error("Curve is complete"); 33 | } 34 | 35 | if (amount <= 0n) { 36 | return 0n; 37 | } 38 | 39 | // Calculate the product of virtual reserves 40 | let n = this.virtualSolReserves * this.virtualTokenReserves; 41 | 42 | // Calculate the new virtual sol reserves after the purchase 43 | let i = this.virtualSolReserves + amount; 44 | 45 | // Calculate the new virtual token reserves after the purchase 46 | let r = n / i + 1n; 47 | 48 | // Calculate the amount of tokens to be purchased 49 | let s = this.virtualTokenReserves - r; 50 | 51 | // Return the minimum of the calculated tokens and real token reserves 52 | return s < this.realTokenReserves ? s : this.realTokenReserves; 53 | } 54 | 55 | getSellPrice(amount: bigint, feeBasisPoints: bigint): bigint { 56 | if (this.complete) { 57 | throw new Error("Curve is complete"); 58 | } 59 | 60 | if (amount <= 0n) { 61 | return 0n; 62 | } 63 | 64 | // Calculate the proportional amount of virtual sol reserves to be received 65 | let n = 66 | (amount * this.virtualSolReserves) / (this.virtualTokenReserves + amount); 67 | 68 | // Calculate the fee amount in the same units 69 | let a = (n * feeBasisPoints) / 10000n; 70 | 71 | // Return the net amount after deducting the fee 72 | return n - a; 73 | } 74 | 75 | getMarketCapSOL(): bigint { 76 | if (this.virtualTokenReserves === 0n) { 77 | return 0n; 78 | } 79 | 80 | return ( 81 | (this.tokenTotalSupply * this.virtualSolReserves) / 82 | this.virtualTokenReserves 83 | ); 84 | } 85 | 86 | getFinalMarketCapSOL(feeBasisPoints: bigint): bigint { 87 | let totalSellValue = this.getBuyOutPrice( 88 | this.realTokenReserves, 89 | feeBasisPoints 90 | ); 91 | let totalVirtualValue = this.virtualSolReserves + totalSellValue; 92 | let totalVirtualTokens = this.virtualTokenReserves - this.realTokenReserves; 93 | 94 | if (totalVirtualTokens === 0n) { 95 | return 0n; 96 | } 97 | 98 | return (this.tokenTotalSupply * totalVirtualValue) / totalVirtualTokens; 99 | } 100 | 101 | getBuyOutPrice(amount: bigint, feeBasisPoints: bigint): bigint { 102 | let solTokens = 103 | amount < this.realSolReserves ? this.realSolReserves : amount; 104 | let totalSellValue = 105 | (solTokens * this.virtualSolReserves) / 106 | (this.virtualTokenReserves - solTokens) + 107 | 1n; 108 | let fee = (totalSellValue * feeBasisPoints) / 10000n; 109 | return totalSellValue + fee; 110 | } 111 | 112 | public static fromBuffer(buffer: Buffer): BondingCurveAccount { 113 | const structure: Layout = struct([ 114 | u64("discriminator"), 115 | u64("virtualTokenReserves"), 116 | u64("virtualSolReserves"), 117 | u64("realTokenReserves"), 118 | u64("realSolReserves"), 119 | u64("tokenTotalSupply"), 120 | bool("complete"), 121 | ]); 122 | 123 | let value = structure.decode(buffer); 124 | return new BondingCurveAccount( 125 | BigInt(value.discriminator), 126 | BigInt(value.virtualTokenReserves), 127 | BigInt(value.virtualSolReserves), 128 | BigInt(value.realTokenReserves), 129 | BigInt(value.realSolReserves), 130 | BigInt(value.tokenTotalSupply), 131 | value.complete 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/lib/jito.ts: -------------------------------------------------------------------------------- 1 | import { Blockhash, Commitment, Keypair, PublicKey, SystemProgram, Transaction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; 2 | import base58 from "bs58"; 3 | import axios from "axios"; 4 | import { logLogger } from "../utils/logger"; 5 | import { JITO_FEE } from "../core/config/env"; 6 | 7 | export const executeJitoTx = async (transactions: VersionedTransaction[], commitment: Commitment, latestBlockhash: any) => { 8 | 9 | try { 10 | const jitoTxsignature = base58.encode(transactions[0].signatures[0]); 11 | 12 | // Serialize the transactions once here 13 | const serializedTransactions: string[] = []; 14 | for (let i = 0; i < transactions.length; i++) { 15 | serializedTransactions.push(base58.encode(transactions[i].serialize())); 16 | } 17 | 18 | const endpoints = [ 19 | // 'https://mainnet.block-engine.jito.wtf/api/v1/bundles', 20 | 'https://amsterdam.mainnet.block-engine.jito.wtf/api/v1/bundles', 21 | 'https://frankfurt.mainnet.block-engine.jito.wtf/api/v1/bundles', 22 | 'https://ny.mainnet.block-engine.jito.wtf/api/v1/bundles', 23 | 'https://tokyo.mainnet.block-engine.jito.wtf/api/v1/bundles', 24 | ]; 25 | 26 | const requests = endpoints.map((url) => 27 | axios.post(url, { 28 | jsonrpc: '2.0', 29 | id: 1, 30 | method: 'sendBundle', 31 | params: [serializedTransactions], 32 | }) 33 | ); 34 | 35 | // console.log('Sending transactions to endpoints...'); 36 | 37 | const results = await Promise.all(requests.map((p) => p.catch((e) => e))); 38 | 39 | const successfulResults = results.filter((result) => !(result instanceof Error)); 40 | 41 | if (successfulResults.length > 0) { 42 | logLogger.log("tx:", jitoTxsignature); 43 | // console.log(`Successful response`); 44 | // console.log(`Confirming jito transaction...`); 45 | 46 | // console.log("successfulResults.length====>", successfulResults.length); 47 | 48 | // const confirmation = await solanaConnection.confirmTransaction( 49 | // { 50 | // signature: jitoTxsignature, 51 | // lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, 52 | // blockhash: latestBlockhash.blockhash, 53 | // }, 54 | // commitment, 55 | // ); 56 | // console.log("🚀 ~ executeJitoTx ~ confirmation:", confirmation) 57 | 58 | // if (confirmation.value.err) { 59 | // console.log("Confirmtaion error") 60 | // return null 61 | // } else { 62 | // return jitoTxsignature; 63 | // } 64 | } else { 65 | logLogger.log("successfulResults.length====>", successfulResults.length); 66 | logLogger.log(`No successful responses received for jito`); 67 | } 68 | return null 69 | } catch (error) { 70 | logLogger.log('Error during transaction execution', error as string); 71 | return null 72 | } 73 | } 74 | 75 | export const buildJitoTipIx = (fromPubkey: PublicKey) => { 76 | // add jito tip instruction 77 | const tipAccounts = [ 78 | 'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY', 79 | 'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL', 80 | '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5', 81 | '3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT', 82 | 'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe', 83 | 'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49', 84 | 'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt', 85 | 'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh', 86 | ]; 87 | const jitoFeeWallet = new PublicKey(tipAccounts[Math.floor(tipAccounts.length * Math.random())]); 88 | 89 | return SystemProgram.transfer({ 90 | fromPubkey, 91 | toPubkey: jitoFeeWallet, 92 | lamports: Math.floor(JITO_FEE), 93 | }); 94 | } 95 | 96 | export const buildJitoTipTx = (userKp: Keypair, blockhash: Blockhash) => { 97 | const messageV0 = new TransactionMessage({ 98 | payerKey: userKp.publicKey, 99 | recentBlockhash: blockhash, 100 | instructions: [ 101 | buildJitoTipIx(userKp.publicKey) 102 | ], 103 | }).compileToV0Message(); 104 | 105 | const tx = new VersionedTransaction(messageV0); 106 | tx.sign([userKp]); 107 | 108 | return tx; 109 | } 110 | -------------------------------------------------------------------------------- /src/lib/create-pool.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Liquidity } from "@raydium-io/raydium-sdk"; 3 | import { PublicKey, Keypair, TransactionInstruction, SystemProgram, TransactionMessage, VersionedTransaction, Blockhash, ComputeBudgetProgram } from "@solana/web3.js"; 4 | import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, createSyncNativeInstruction, NATIVE_MINT } from "@solana/spl-token"; 5 | import { BN } from "@coral-xyz/anchor"; 6 | import { feeDestination, raydiumProgramId } from "../core/config/env"; 7 | import { getAssociatedTokenAccount } from "../utils/web3"; 8 | 9 | export const buildCreatePoolTx = ( 10 | market: PublicKey, 11 | userKp: Keypair, 12 | 13 | baseMint: PublicKey, 14 | baseDecimals: number, 15 | baseAmount: number, 16 | 17 | quoteMint: PublicKey, 18 | quoteDecimals: number, 19 | quoteAmount: number, 20 | 21 | blockhash: Blockhash, 22 | skipWsolCreate: boolean = false 23 | ) => { 24 | const poolInfo = Liquidity.getAssociatedPoolKeys({ 25 | version: 4, 26 | marketVersion: 3, 27 | marketId: market, 28 | baseMint: baseMint, 29 | quoteMint: quoteMint, 30 | baseDecimals: baseDecimals, 31 | quoteDecimals: quoteDecimals, 32 | programId: raydiumProgramId.AmmV4, 33 | marketProgramId: raydiumProgramId.OPENBOOK_MARKET, 34 | }); 35 | 36 | const userCoinVault = getAssociatedTokenAccount(userKp.publicKey, poolInfo.baseMint); 37 | const userPcVault = getAssociatedTokenAccount(userKp.publicKey, poolInfo.quoteMint); 38 | 39 | // create and send WSOL 40 | const {solAmount, solVault} = baseMint.equals(NATIVE_MINT) ? { 41 | solAmount: baseAmount, 42 | solVault: userCoinVault 43 | } : quoteMint == NATIVE_MINT ? { 44 | solAmount: quoteAmount, 45 | solVault: userPcVault 46 | } : { 47 | solAmount: 0, 48 | solVault: PublicKey.default 49 | }; 50 | 51 | const ixs: TransactionInstruction[] = [] 52 | 53 | ixs.push( 54 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 }), 55 | ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 }) 56 | ); 57 | 58 | if (solAmount != 0) { 59 | // create WSOL account if needed 60 | if (skipWsolCreate) { 61 | ixs.push( 62 | createAssociatedTokenAccountInstruction( 63 | userKp.publicKey, 64 | solVault, 65 | userKp.publicKey, 66 | NATIVE_MINT 67 | ) 68 | ) 69 | } 70 | 71 | ixs.push( 72 | // transfer SOL to WSOL account 73 | SystemProgram.transfer({ 74 | fromPubkey: userKp.publicKey, 75 | toPubkey: solVault, 76 | lamports: solAmount 77 | }), 78 | // sync WSOL account 79 | createSyncNativeInstruction(solVault) 80 | ) 81 | } 82 | 83 | const createPoolIxs = Liquidity.makeCreatePoolV4InstructionV2({ 84 | programId: raydiumProgramId.AmmV4, 85 | ammId: poolInfo.id, 86 | ammAuthority: poolInfo.authority, 87 | ammOpenOrders: poolInfo.openOrders, 88 | lpMint: poolInfo.lpMint, 89 | coinMint: poolInfo.baseMint, 90 | pcMint: poolInfo.quoteMint, 91 | coinVault: poolInfo.baseVault, 92 | pcVault: poolInfo.quoteVault, 93 | ammTargetOrders: poolInfo.targetOrders, 94 | marketProgramId: poolInfo.marketProgramId, 95 | marketId: poolInfo.marketId, 96 | userWallet: userKp.publicKey, 97 | userCoinVault, 98 | userPcVault, 99 | userLpVault: getAssociatedTokenAccount(userKp.publicKey, poolInfo.lpMint), 100 | ammConfigId: poolInfo.configId, 101 | feeDestinationId: feeDestination, 102 | 103 | nonce: poolInfo.nonce, 104 | openTime: new BN(0), // just open it 105 | coinAmount: new BN(baseAmount), 106 | pcAmount: new BN(quoteAmount), 107 | }).innerTransaction; 108 | 109 | ixs.push(...createPoolIxs.instructions,); 110 | if (skipWsolCreate) { 111 | // close WSOL account if needed 112 | ixs.push( 113 | createCloseAccountInstruction( 114 | solVault, 115 | userKp.publicKey, 116 | userKp.publicKey 117 | ) 118 | ); 119 | } 120 | 121 | if (createPoolIxs.lookupTableAddress && createPoolIxs.lookupTableAddress.length != 0) { 122 | console.log("lookup table is not empty!"); 123 | console.log(createPoolIxs.lookupTableAddress); 124 | } 125 | 126 | const messageV0 = new TransactionMessage({ 127 | payerKey: userKp.publicKey, 128 | recentBlockhash: blockhash, 129 | instructions: ixs, 130 | }).compileToV0Message(); 131 | 132 | const transaction = new VersionedTransaction(messageV0); 133 | transaction.sign([userKp, ...createPoolIxs.signers]); 134 | 135 | return transaction; 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana sniper for pumpfun, pumpswap or pumpAmm, raydium launchpad(letsbonk.fun), believe or launchcoin(meteora DBC) with social check 2 | 3 | ## How it works 4 | 5 | * Subscribe transactions for token creation(migration) on pumpfun, pumpswap, raydium launchpad, believe(meteora). 6 | * Parse the transaction, get mint, creator, metadata uri, initial buy amount. 7 | * Check metadata and get posts on tweeter(X). 8 | * Filter the tokens, buy, save to DB. 9 | * Get list of bought tokens from DB, check pool balance. Sell if needed. 10 | * Log launched tokens and bought tokens 11 | 12 | ## How to install 13 | 14 | ### Prerequites 15 | 16 | #### Install node environment.
17 | 18 | Here're some useful links for installing node environment.
19 | https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
20 | https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04 21 | 22 | ### Download the project 23 | 24 | #### Setup git bash if you don't have it.
25 | https://git-scm.com/book/en/v2/Getting-Started-Installing-Git 26 | 27 | #### Clone this project to local machine. 28 | > git clone https://github.com/m8s-lab/solana-sniper 29 | 30 | #### Install node modules. 31 | > cd solana-sniper
32 | yarn 33 | 34 | ## Configuration 35 | 36 | ### Set environment variables in `.env` file 37 | 38 | #### Copy .env.template as .env and edit it. 39 | 40 | > MONGO_URI: uri to mongo db that stores token data
41 | SOLANA_MAINNET_RPC: solana mainnet rpc url
42 | PRIVATE_KEY_PATH: path to a solana keypair file.
43 | XAPI_KEY: xapi key to get social info of tokens
44 | XAPI_URL: link to x provider 45 | 46 | ### Set target launchpads 47 | 48 | For now, this bot listens pumpfun, pumpswap, raydium launchpad, believe(meteora DBC).
49 | Edit `./src/streaming/new-tokens.ts:L225` to add or remove launchpads. 50 | ```javascript 51 | transactions: { 52 | // subscribe token create on pumpfun 53 | pumpfun: { 54 | ... 55 | }, 56 | // subscribe token migration from pumpfun to pumpswap 57 | pumpswap: { 58 | ... 59 | }, 60 | // subscribe token launch on raydium launchpad 61 | raydiumlaunch: { 62 | ... 63 | }, 64 | // subscribe token creation by believe(launchcoin) 65 | believe: { 66 | ... 67 | } 68 | } 69 | ``` 70 | 71 | ### Implement your sniping strategy 72 | 73 | #### Add criterias for your strategy. 74 | Edit `./src/handler/handle-create.ts`, `./src/handler/handle-migrate.ts`, `./src/cron/watch-pools.ts` to ipmlement your sniping strategy.
75 | Like token filters, TP/SL percent, buy amount etc. 76 | 77 | ```javascript 78 | //// criteria_* 79 | ... 80 | //// buy token 81 | ... 82 | //// sell token 83 | ``` 84 | 85 | #### Edit your filter for social accounts. 86 | Edit `./src/checker/check-metadata.ts` and `./src/utils/xapi.ts` to check metadata and twitter posts in detail. 87 | ```javascript 88 | // return X link 89 | const socials = getSocialLinks(jsonData); 90 | ... 91 | // return -1 if tweets and follwers are too small 92 | if (postCount < 10 || followerCount < 50 || followingCount > followerCount / 2) { 93 | return -1; 94 | } 95 | ``` 96 | ## Run and make profit 97 | Finally, you're ready to run sniper and make profit. 😃 98 | 99 | ### Build the project 100 | > yarn build 101 | 102 | ### Run bot 103 | 104 | > yarn start 105 | 106 | ### Check log 107 | Log files are stored in `./logs/`.
108 | You can check the log files and fix your strategy.
109 | 110 | #### Files in `./logs/log` store info for all launched tokens.
111 | ```YAML 112 | ----------------------------------------- 113 | launch: raydiumlaunch, tx: 4ywjKXNZyr8YpfUfdDfRZW1WWAW4ZhTdYxqinAGsjtRL81sho94k9AHCpWCdUbGSP69NxxLSVZ6KHSxD7BpD5AeU 114 | buy: 3.0, name: Yuria, symbol: Yuria, mint: Ja9QM1RK7EafnQ9PNz4GZb1bPBYvLGYtifYooPybonk, link: https://www.instagram.com/stories/kabosumama/3640846729807247053?igsh=== 115 | hold1: 6, topP1: 17.9, hold2: 20, topP2: 39.6, bal1: 6.2, bal2: 20.8, bal2: 10.9 116 | tweet user: 3, like: 0, view: 110 117 | ----------------------------------------- 118 | launch: pumpfun, tx: 3YPoprDLSAF5utzJGEmrVxgFk7YJv63GTma5VH6bd6az83WVdmPnHFV1yGEt9cvFfz2hNbSK3ZbMhjJe8Jb9cBH6 119 | buy: 2.0, name: Pipapeep, symbol: PIP, mint: GH7DjqN3ewHUf4vsDmcmz2jipFo2NMab3qehwqFopump, link: https://www.tiktok.com/@pipapeep?_t=ZS-8wfgLJ2Fz29&_r=1 120 | hold1: 7, topP1: 17.8, hold2: 20, topP2: 28.4, bal1: 6.0, bal2: 10.8, bal2: 10.6 121 | tweet user: 1, like: 1, view: 22 122 | ``` 123 | #### Files in `./logs/info` store vault balance for target tokens.
124 | ```YAML 125 | mint users, likes, views, balance1, holders, top20 %, .... 126 | DaQq..., 7, 10, 1115, 18.8, 16, 23.1, 37.1, 2.16, 85.0, 93.5, 97.8, 115.4, ... 127 | E1Xn..., 4, 4, 93, 43.9, 20, 33.0, 35.8, 2.69, 85.0, 81.6, 90.7, 82.2, ... 128 | 8h7i..., 4, 15, 1046, 10.4, 17, 21.5, 39.0, 0.73, 85.0, 94.9, 103.6, 71.2, ... 129 | ``` 130 | 131 | ## Contact 132 | 133 | * TG: https://t.me/microgift88 134 | * Discord: https://discord.com/users/1074514238325927956 135 | * Email: microgift28@gmail.com 136 | -------------------------------------------------------------------------------- /src/lib/remove-liquidity.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ApiPoolInfoV4, findProgramAddress, jsonInfo2PoolKeys, Liquidity, LIQUIDITY_STATE_LAYOUT_V4, Market, MARKET_STATE_LAYOUT_V3, SPL_MINT_LAYOUT } from "@raydium-io/raydium-sdk"; 3 | import { PublicKey, Keypair, TransactionMessage, VersionedTransaction, Blockhash, Connection } from "@solana/web3.js"; 4 | import { raydiumProgramId, solanaConnection } from "../core/config/env"; 5 | import { getAssociatedTokenAccount } from "../utils/web3"; 6 | import { BN } from "@coral-xyz/anchor"; 7 | import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, NATIVE_MINT } from "@solana/spl-token"; 8 | 9 | export const buildRemoveLiquidityTx = async ( 10 | market: PublicKey, 11 | userKp: Keypair, 12 | 13 | removePercent: number, 14 | blockhash: Blockhash, 15 | ) => { 16 | const poolId = findProgramAddress( 17 | [raydiumProgramId.AmmV4.toBuffer(), market.toBuffer(), Buffer.from('amm_associated_seed', 'utf-8')], 18 | raydiumProgramId.AmmV4, 19 | ); 20 | console.log("--------------------"); 21 | console.log("poolId:", poolId.publicKey.toBase58()); 22 | 23 | const poolKeys = jsonInfo2PoolKeys(await formatAmmKeysById(solanaConnection, poolId.publicKey.toBase58())); 24 | 25 | console.log("poolKeys:", poolKeys); 26 | 27 | const baseAta = getAssociatedTokenAccount(userKp.publicKey, poolKeys.baseMint); 28 | const quoteAta = getAssociatedTokenAccount(userKp.publicKey, poolKeys.quoteMint); 29 | const lpAta = getAssociatedTokenAccount(userKp.publicKey, poolKeys.lpMint); 30 | 31 | const solAta = poolKeys.baseMint.equals(NATIVE_MINT) ? baseAta : quoteAta; 32 | 33 | const lpAmount = Number((await solanaConnection.getTokenAccountBalance(lpAta)).value.amount); 34 | const amountIn = lpAmount / 100 * removePercent; 35 | 36 | // create/close WSOL account 37 | const closePoolIxs = Liquidity.makeRemoveLiquidityInstruction({ 38 | poolKeys, 39 | userKeys: { 40 | baseTokenAccount: baseAta, 41 | quoteTokenAccount: quoteAta, 42 | lpTokenAccount: lpAta, 43 | owner: userKp.publicKey, 44 | }, 45 | amountIn: new BN(amountIn) 46 | }).innerTransaction; 47 | 48 | if (closePoolIxs.lookupTableAddress && closePoolIxs.lookupTableAddress.length != 0) { 49 | console.log("lookup table is not empty!"); 50 | console.log(closePoolIxs.lookupTableAddress); 51 | } 52 | 53 | const messageV0 = new TransactionMessage({ 54 | payerKey: userKp.publicKey, 55 | recentBlockhash: blockhash, 56 | instructions: [ 57 | createAssociatedTokenAccountInstruction( 58 | userKp.publicKey, 59 | solAta, 60 | userKp.publicKey, 61 | NATIVE_MINT 62 | ), 63 | ...closePoolIxs.instructions, 64 | createCloseAccountInstruction( 65 | solAta, 66 | userKp.publicKey, 67 | userKp.publicKey 68 | ) 69 | ], 70 | }).compileToV0Message(); 71 | 72 | const transaction = new VersionedTransaction(messageV0); 73 | transaction.sign([userKp, ...closePoolIxs.signers]); 74 | 75 | return transaction; 76 | } 77 | 78 | export const formatAmmKeysById = async (connection: Connection, id: string): Promise => { 79 | const account = await connection.getAccountInfo(new PublicKey(id)) 80 | if (account === null) throw Error(' get id info error ') 81 | const info = LIQUIDITY_STATE_LAYOUT_V4.decode(account.data) 82 | 83 | const marketId = info.marketId 84 | const marketAccount = await connection.getAccountInfo(marketId) 85 | if (marketAccount === null) throw Error(' get market info error') 86 | const marketInfo = MARKET_STATE_LAYOUT_V3.decode(marketAccount.data) 87 | 88 | const lpMint = info.lpMint 89 | const lpMintAccount = await connection.getAccountInfo(lpMint) 90 | if (lpMintAccount === null) throw Error(' get lp mint info error') 91 | const lpMintInfo = SPL_MINT_LAYOUT.decode(lpMintAccount.data) 92 | 93 | return { 94 | id, 95 | baseMint: info.baseMint.toString(), 96 | quoteMint: info.quoteMint.toString(), 97 | lpMint: info.lpMint.toString(), 98 | baseDecimals: info.baseDecimal.toNumber(), 99 | quoteDecimals: info.quoteDecimal.toNumber(), 100 | lpDecimals: lpMintInfo.decimals, 101 | version: 4, 102 | programId: account.owner.toString(), 103 | authority: Liquidity.getAssociatedAuthority({ programId: account.owner }).publicKey.toString(), 104 | openOrders: info.openOrders.toString(), 105 | targetOrders: info.targetOrders.toString(), 106 | baseVault: info.baseVault.toString(), 107 | quoteVault: info.quoteVault.toString(), 108 | withdrawQueue: info.withdrawQueue.toString(), 109 | lpVault: info.lpVault.toString(), 110 | marketVersion: 3, 111 | marketProgramId: info.marketProgramId.toString(), 112 | marketId: info.marketId.toString(), 113 | marketAuthority: Market.getAssociatedAuthority({ programId: info.marketProgramId, marketId: info.marketId }).publicKey.toString(), 114 | marketBaseVault: marketInfo.baseVault.toString(), 115 | marketQuoteVault: marketInfo.quoteVault.toString(), 116 | marketBids: marketInfo.bids.toString(), 117 | marketAsks: marketInfo.asks.toString(), 118 | marketEventQueue: marketInfo.eventQueue.toString(), 119 | lookupTableAccount: PublicKey.default.toString() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/lib/pumpfun/pumpfun.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Commitment, 3 | Connection, 4 | Finality, 5 | Keypair, 6 | PublicKey, 7 | Transaction, 8 | } from "@solana/web3.js"; 9 | import { BN, Program, Provider } from "@coral-xyz/anchor"; 10 | import { 11 | createAssociatedTokenAccountInstruction, 12 | getAccount, 13 | } from "@solana/spl-token"; 14 | import { TransactionInstruction } from "@solana/web3.js"; 15 | import { PumpFun, PumpFunIDL } from "../../idl/index" 16 | import { PumpAmmBuyData, PumpCreateData, PumpMigrateData } from "../../utils/types"; 17 | import { getAssociatedTokenAccount } from "../../utils/web3"; 18 | import { BondingCurveAccount } from "./bondingCurveAccount"; 19 | import { GlobalAccount } from "./globalAccount"; 20 | import { PUMP_FUN } from "../../core/constants"; 21 | 22 | const PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; 23 | const MPL_TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"; 24 | 25 | export const GLOBAL_ACCOUNT_SEED = "global"; 26 | export const MINT_AUTHORITY_SEED = "mint-authority"; 27 | export const BONDING_CURVE_SEED = "bonding-curve"; 28 | export const METADATA_SEED = "metadata"; 29 | 30 | export const DEFAULT_DECIMALS = 6; 31 | 32 | export class PumpFunSDK { 33 | public program: Program; 34 | public connection: Connection; 35 | constructor(provider?: Provider) { 36 | this.program = new Program(PumpFunIDL as PumpFun, provider); 37 | this.connection = this.program.provider.connection; 38 | } 39 | 40 | getBondingCurvePDA = (mint: PublicKey): PublicKey => { 41 | return PublicKey.findProgramAddressSync( 42 | [Buffer.from(BONDING_CURVE_SEED), mint.toBuffer()], 43 | this.program.programId 44 | )[0]; 45 | }; 46 | getBondingCurveAccount = async (mint: PublicKey, commitment?: Commitment) => { 47 | const tokenAccount = await this.connection.getAccountInfo( 48 | this.getBondingCurvePDA(mint), 49 | commitment 50 | ); 51 | if (!tokenAccount) { 52 | return null; 53 | } 54 | return BondingCurveAccount.fromBuffer(tokenAccount!.data); 55 | } 56 | 57 | calculateWithSlippageBuy = ( 58 | amount: bigint, 59 | basisPoints: bigint 60 | ) => { 61 | return amount + (amount * basisPoints) / BigInt(1000); 62 | }; 63 | 64 | getGlobalAccount = async (commitment?: Commitment) => { 65 | const [globalAccountPDA] = PublicKey.findProgramAddressSync( 66 | [Buffer.from(GLOBAL_ACCOUNT_SEED)], 67 | new PublicKey(PROGRAM_ID) 68 | ); 69 | 70 | const tokenAccount = await this.connection.getAccountInfo( 71 | globalAccountPDA, 72 | commitment 73 | ); 74 | 75 | return GlobalAccount.fromBuffer(tokenAccount!.data); 76 | } 77 | 78 | getBuyIxsBySolAmount = async ( 79 | buyer: PublicKey, 80 | mint: PublicKey, 81 | buyAmountSol: bigint, 82 | slippageBasisPoints: bigint = BigInt(500), 83 | commitment?: Commitment 84 | ) => { 85 | const bondingCurveAccount = await this.getBondingCurveAccount( 86 | mint, 87 | commitment 88 | ); 89 | if (!bondingCurveAccount) { 90 | throw new Error(`Bonding curve account not found: ${mint.toBase58()}`); 91 | } 92 | 93 | const buyAmount = bondingCurveAccount.getBuyPrice(buyAmountSol); 94 | const buyAmountWithSlippage = this.calculateWithSlippageBuy( 95 | buyAmountSol, 96 | slippageBasisPoints 97 | ); 98 | // const globalAccount = await this.getGlobalAccount(commitment); 99 | 100 | return await this.getBuyIxs( 101 | buyer, 102 | mint, 103 | // globalAccount.feeRecipient, 104 | PUMP_FUN.FEE_RECEIPT, 105 | buyAmount, 106 | buyAmountWithSlippage, 107 | ); 108 | } 109 | 110 | //buy 111 | getBuyIxs = async ( 112 | buyer: PublicKey, 113 | mint: PublicKey, 114 | feeRecipient: PublicKey, 115 | amount: bigint, 116 | solAmount: bigint, 117 | ) => { 118 | const associatedUser = getAssociatedTokenAccount(buyer, mint); 119 | 120 | const ixs: TransactionInstruction[] = []; 121 | 122 | // try { 123 | // await getAccount(this.connection, associatedUser, commitment); 124 | // } catch (e) { 125 | ixs.push( 126 | createAssociatedTokenAccountInstruction( 127 | buyer, 128 | associatedUser, 129 | buyer, 130 | mint 131 | ) 132 | ); 133 | // } 134 | 135 | ixs.push( 136 | await this.program.methods 137 | .buy(new BN(amount.toString()), new BN(solAmount.toString())) 138 | .accountsPartial({ 139 | user: buyer, 140 | mint: mint, 141 | associatedUser: associatedUser, 142 | feeRecipient: feeRecipient 143 | }) 144 | .instruction() 145 | ); 146 | 147 | return ixs; 148 | } 149 | 150 | parseCreateCpiLog = (buffer: Buffer): PumpCreateData => { 151 | let offset = 16; // skip first 16 bytes 152 | 153 | // Helper function to read a string (assuming it's prefixed with a 4-byte length) 154 | const readString = (): string => { 155 | const length = buffer.readUInt32LE(offset); // 4 bytes for length 156 | offset += 4; 157 | const value = buffer.toString('utf-8', offset, offset + length); 158 | offset += length; 159 | return value; 160 | } 161 | 162 | // Helper function to read a PublicKey (assuming it's 32 bytes) 163 | const readPublicKey = (): PublicKey => { 164 | const publicKey = new PublicKey(buffer.subarray(offset, offset + 32)); 165 | offset += 32; 166 | return publicKey; 167 | } 168 | 169 | // Parse the buffer and populate the CpiLog 170 | const name = readString(); 171 | const symbol = readString(); 172 | const uri = readString(); 173 | const mint = readPublicKey(); 174 | const bondingCurve = readPublicKey(); 175 | const user = readPublicKey(); 176 | 177 | return { name, symbol, uri, mint, bondingCurve, user }; 178 | } 179 | 180 | parseMigrateCpiLog = (buffer: Buffer): PumpMigrateData => { 181 | let offset = 16 + 10; // discriminator + timestamp + index 182 | 183 | // Helper function to read a PublicKey (assuming it's 32 bytes) 184 | const readPublicKey = (): PublicKey => { 185 | const publicKey = new PublicKey(buffer.subarray(offset, offset + 32)); 186 | offset += 32; 187 | return publicKey; 188 | } 189 | 190 | readPublicKey(); // creator 191 | const baseMint = readPublicKey(); 192 | const quoteMint = readPublicKey(); 193 | offset += 59; 194 | const pool = readPublicKey(); 195 | 196 | return { baseMint, quoteMint, pool }; 197 | } 198 | 199 | parseBuyCpiLog = (buffer: Buffer): PumpAmmBuyData => { 200 | let offset = 16 + 8; // discriminator + timestamp 201 | 202 | const baseAmountOut = buffer.readBigUInt64LE(offset); 203 | offset += 8 + 24; 204 | 205 | const poolBaseTokenReserves = buffer.readBigUInt64LE(offset); 206 | const poolQuoteTokenReserves = buffer.readBigUInt64LE(offset + 8); 207 | offset += 16 + 40; 208 | 209 | const quoteAmountInWithLpFee = buffer.readBigUInt64LE(offset); 210 | 211 | // Helper function to read a PublicKey (assuming it's 32 bytes) 212 | const readPublicKey = (): PublicKey => { 213 | const publicKey = new PublicKey(buffer.subarray(offset, offset + 32)); 214 | offset += 32; 215 | return publicKey; 216 | } 217 | 218 | offset += 16 + 32 * 6; 219 | const coinCreator = readPublicKey(); 220 | 221 | return { baseAmountOut, poolBaseTokenReserves, poolQuoteTokenReserves, quoteAmountInWithLpFee, coinCreator }; 222 | } 223 | } 224 | 225 | 226 | -------------------------------------------------------------------------------- /src/lib/create-market.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AddressLookupTableAccount, 3 | Blockhash, 4 | ComputeBudgetProgram, 5 | Connection, 6 | Keypair, 7 | PublicKey, 8 | SystemProgram, 9 | Transaction, 10 | TransactionInstruction, 11 | TransactionMessage, 12 | VersionedTransaction, 13 | } from "@solana/web3.js" 14 | import { 15 | TOKEN_PROGRAM_ID, 16 | ACCOUNT_SIZE, 17 | createInitializeAccountInstruction, 18 | } from "@solana/spl-token" 19 | import { DexInstructions, Market } from "@project-serum/serum" 20 | import { BN } from "@coral-xyz/anchor"; 21 | import { raydiumProgramId } from "../core/config/env"; 22 | import { buildJitoTipIx } from "./jito"; 23 | 24 | const EVENT_QUEUE_LENGTH = 2978; 25 | const EVENT_SIZE = 88; 26 | const EVENT_QUEUE_HEADER_SIZE = 32; 27 | 28 | const REQUEST_QUEUE_LENGTH = 63; 29 | const REQUEST_SIZE = 80; 30 | const REQUEST_QUEUE_HEADER_SIZE = 32; 31 | 32 | const ORDERBOOK_LENGTH = 909; 33 | const ORDERBOOK_NODE_SIZE = 72; 34 | const ORDERBOOK_HEADER_SIZE = 40; 35 | const LOT_SIZE = -3; 36 | const TICK_SIZE = 8; 37 | 38 | export function calculateTotalAccountSize( 39 | individualAccountSize: number, 40 | accountHeaderSize: number, 41 | length: number 42 | ) { 43 | const accountPadding = 12; 44 | const minRequiredSize = 45 | accountPadding + accountHeaderSize + length * individualAccountSize; 46 | 47 | const modulo = minRequiredSize % 8; 48 | 49 | return modulo <= 4 50 | ? minRequiredSize + (4 - modulo) 51 | : minRequiredSize + (8 - modulo + 4); 52 | }; 53 | 54 | const TOTAL_EVENT_QUEUE_SIZE = calculateTotalAccountSize( 55 | 128, 56 | EVENT_QUEUE_HEADER_SIZE, 57 | EVENT_SIZE 58 | ); 59 | 60 | const TOTAL_REQUEST_QUEUE_SIZE = calculateTotalAccountSize( 61 | 10, 62 | REQUEST_QUEUE_HEADER_SIZE, 63 | REQUEST_SIZE 64 | ); 65 | 66 | const TOTAL_ORDER_BOOK_SIZE = calculateTotalAccountSize( 67 | 201, 68 | ORDERBOOK_HEADER_SIZE, 69 | ORDERBOOK_NODE_SIZE 70 | ); 71 | 72 | const getVaultNonce = (market: PublicKey, programId: PublicKey) => { 73 | let seedNum = 0; 74 | 75 | while (true) { 76 | const result = getVaultOwnerAndNonce( 77 | market, 78 | programId, 79 | seedNum 80 | ); 81 | 82 | if (result) return result; 83 | else seedNum++; 84 | } 85 | } 86 | 87 | 88 | export const getVaultOwnerAndNonce = ( 89 | marketAddress: PublicKey, 90 | dexAddress: PublicKey, 91 | seedNum: number 92 | ): [vaultOwner: PublicKey, nonce: BN] | undefined => { 93 | let nonce = new BN(seedNum); 94 | // console.log("nonce:", nonce); 95 | try { 96 | // console.log("market address: ", marketAddress.toBase58()); 97 | // console.log("dex address: ", dexAddress.toBase58()); 98 | 99 | const vaultOwner = PublicKey.createProgramAddressSync( 100 | [marketAddress.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)], 101 | dexAddress 102 | ); 103 | // console.log("vault owner ", vaultOwner.toBase58()); 104 | return [vaultOwner, nonce]; 105 | } catch (e) { 106 | // console.log('error in getVaultOwnerAndNonce'); 107 | } 108 | } 109 | 110 | 111 | export const createMarket = ( 112 | userKp: Keypair, 113 | 114 | baseMint: PublicKey, 115 | baseMintDecimals: number, 116 | 117 | quoteMint: PublicKey, 118 | quoteMintDecimals: number, 119 | 120 | blockhash: Blockhash, 121 | ) => { 122 | const vaultInstructions: TransactionInstruction[] = [] 123 | const marketInstructions: TransactionInstruction[] = [] 124 | 125 | // const timeOut = setTimeout(async () => { 126 | // console.log("Trying market creation again"); 127 | // const marketId = await createMarket(userKp, baseMint, baseMintDecimals, quoteMint, quoteMintDecimals, connection); 128 | // return marketId; 129 | // }, 20000); 130 | 131 | const marketAccounts = { 132 | market: Keypair.generate(), 133 | requestQueue: Keypair.generate(), 134 | eventQueue: Keypair.generate(), 135 | bids: Keypair.generate(), 136 | asks: Keypair.generate(), 137 | baseVault: Keypair.generate(), 138 | quoteVault: Keypair.generate(), 139 | } 140 | const [vaultOwner, vaultOwnerNonce] = getVaultNonce( 141 | marketAccounts.market.publicKey, 142 | raydiumProgramId.OPENBOOK_MARKET 143 | ); 144 | 145 | // create vaults 146 | vaultInstructions.push( 147 | SystemProgram.createAccount({ 148 | fromPubkey: userKp.publicKey, 149 | newAccountPubkey: marketAccounts.baseVault.publicKey, 150 | lamports: 2039280, // rent exempt 151 | space: 165, // ACOCUNT_SIZE of token account 152 | programId: TOKEN_PROGRAM_ID, 153 | }), 154 | SystemProgram.createAccount({ 155 | fromPubkey: userKp.publicKey, 156 | newAccountPubkey: marketAccounts.quoteVault.publicKey, 157 | lamports: 2039280, // rent exempt 158 | space: 165, // ACOCUNT_SIZE of token account 159 | programId: TOKEN_PROGRAM_ID, 160 | }), 161 | createInitializeAccountInstruction( 162 | marketAccounts.baseVault.publicKey, 163 | baseMint, 164 | vaultOwner 165 | ), 166 | createInitializeAccountInstruction( 167 | marketAccounts.quoteVault.publicKey, 168 | quoteMint, 169 | vaultOwner 170 | ) 171 | ); 172 | 173 | // clearTimeout(timeOut); 174 | // tickSize and lotSize here are the 1e^(-x) values, so no check for ><= 0 175 | const baseLotSize = Math.round( 176 | 10 ** baseMintDecimals * Math.pow(10, -1 * LOT_SIZE) 177 | ); 178 | const quoteLotSize = Math.round( 179 | 10 ** quoteMintDecimals * 180 | Math.pow(10, -1 * LOT_SIZE) * 181 | Math.pow(10, -1 * TICK_SIZE) 182 | ); 183 | 184 | // create market account 185 | marketInstructions.push( 186 | SystemProgram.createAccount({ 187 | newAccountPubkey: marketAccounts.market.publicKey, 188 | fromPubkey: userKp.publicKey, 189 | space: 388, // Market.getLayout(raydiumProgramId.OPENBOOK_MARKET).span 190 | lamports: 3591360, // rent exempt 191 | programId: raydiumProgramId.OPENBOOK_MARKET, 192 | }) 193 | ); 194 | 195 | // create request queue 196 | marketInstructions.push( 197 | SystemProgram.createAccount({ 198 | newAccountPubkey: marketAccounts.requestQueue.publicKey, 199 | fromPubkey: userKp.publicKey, 200 | space: 844, // TOTAL_REQUEST_QUEUE_SIZE 201 | lamports: 6765120, // rent exempt 202 | programId: raydiumProgramId.OPENBOOK_MARKET, 203 | }) 204 | ); 205 | 206 | // create event queue 207 | marketInstructions.push( 208 | SystemProgram.createAccount({ 209 | newAccountPubkey: marketAccounts.eventQueue.publicKey, 210 | fromPubkey: userKp.publicKey, 211 | space: 11308, // TOTAL_EVENT_QUEUE_SIZE 212 | lamports: 79594560, 213 | programId: raydiumProgramId.OPENBOOK_MARKET, 214 | }) 215 | ); 216 | 217 | // create bids 218 | marketInstructions.push( 219 | SystemProgram.createAccount({ 220 | newAccountPubkey: marketAccounts.bids.publicKey, 221 | fromPubkey: userKp.publicKey, 222 | space: 14524, // TOTAL_ORDER_BOOK_SIZE 223 | lamports: 101977920, 224 | programId: raydiumProgramId.OPENBOOK_MARKET, 225 | }) 226 | ); 227 | 228 | // create asks 229 | marketInstructions.push( 230 | SystemProgram.createAccount({ 231 | newAccountPubkey: marketAccounts.asks.publicKey, 232 | fromPubkey: userKp.publicKey, 233 | space: 14524, // TOTAL_ORDER_BOOK_SIZE 234 | lamports: 101977920, 235 | programId: raydiumProgramId.OPENBOOK_MARKET, 236 | }) 237 | ); 238 | 239 | marketInstructions.push( 240 | DexInstructions.initializeMarket({ 241 | market: marketAccounts.market.publicKey, 242 | requestQueue: marketAccounts.requestQueue.publicKey, 243 | eventQueue: marketAccounts.eventQueue.publicKey, 244 | bids: marketAccounts.bids.publicKey, 245 | asks: marketAccounts.asks.publicKey, 246 | baseVault: marketAccounts.baseVault.publicKey, 247 | quoteVault: marketAccounts.quoteVault.publicKey, 248 | baseMint, 249 | quoteMint, 250 | baseLotSize: new BN(baseLotSize), 251 | quoteLotSize: new BN(quoteLotSize), 252 | feeRateBps: 150, // Unused in v3 253 | quoteDustThreshold: new BN(500), // Unused in v3 254 | vaultSignerNonce: vaultOwnerNonce, 255 | programId: raydiumProgramId.OPENBOOK_MARKET, 256 | }) 257 | ); 258 | 259 | const createVaultTx = new Transaction().add( 260 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1_000_000 }), 261 | ComputeBudgetProgram.setComputeUnitLimit({ units: 20_000 }), 262 | ...vaultInstructions 263 | ); 264 | createVaultTx.recentBlockhash = blockhash; 265 | createVaultTx.feePayer = userKp.publicKey; 266 | createVaultTx.sign( 267 | userKp, 268 | marketAccounts.baseVault, 269 | marketAccounts.quoteVault 270 | ); 271 | 272 | const messageV0 = new TransactionMessage({ 273 | payerKey: userKp.publicKey, 274 | recentBlockhash: blockhash, 275 | instructions: [ 276 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 600_000 }), 277 | ComputeBudgetProgram.setComputeUnitLimit({ units: 20_000 }), 278 | ...marketInstructions 279 | ], 280 | }).compileToV0Message(); 281 | 282 | const createMarketTx = new VersionedTransaction(messageV0); 283 | 284 | createMarketTx.sign([ 285 | userKp, 286 | marketAccounts.market, 287 | marketAccounts.requestQueue, 288 | marketAccounts.eventQueue, 289 | marketAccounts.bids, 290 | marketAccounts.asks 291 | ]); 292 | 293 | // await execTx(createVaultTx, connection, userKp, "finalized"); 294 | // await execTx(createMarketTx, connection, userKp, "processed"); 295 | 296 | return { market: marketAccounts.market.publicKey, createVaultTx, createMarketTx }; 297 | } 298 | -------------------------------------------------------------------------------- /src/streaming/new-tokens.ts: -------------------------------------------------------------------------------- 1 | import Client, { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc"; 2 | import { LAMPORTS_PER_SOL, PublicKey, SystemProgram } from "@solana/web3.js"; 3 | import base58 from "bs58"; 4 | 5 | import { BUY_SOL_AMOUNT, LAUNCH_MAX_BUY_SOL, LAUNCH_MIN_BUY_SOL, userKp } from "../core/config/env"; 6 | import { MINT_POST_BALANCE, PUMP_FUN, RAYDIUM, BELIEVE, METEORA_DBC_PROGRAM, TARGET_WALLETS } from "../core/constants"; 7 | import { PumpFunSDK } from "../lib/pumpfun/pumpfun"; 8 | import { setHasEnoughBalance } from "../core/global"; 9 | import { errorLogger, logLogger } from "../utils/logger"; 10 | import { handleCreate } from "../handler/handle-create"; 11 | import { findInstructionWithDiscriminator as findCpiIx } from "../utils/web3"; 12 | import { handleMigrate } from "../handler/handle-migrate"; 13 | import { MPL_TOKEN_METADATA_PROGRAM_ID } from "@metaplex-foundation/mpl-token-metadata"; 14 | import { parseMetadata } from "../utils/metadata"; 15 | import { NATIVE_MINT } from "@solana/spl-token"; 16 | 17 | //grpc endpoint from Solana Vibe Station obviously 18 | const client = new Client("https://grpc.solanavibestation.com", undefined, undefined); 19 | 20 | (async () => { 21 | const version = await client.getVersion(); // gets the version information 22 | console.log("grpc version: ", version); 23 | })(); 24 | 25 | export const streamNewTokens = async () => { 26 | const stream = await client.subscribe(); 27 | 28 | // parse token lauch on pupmpfun, raydium launchpad, believe(launchcoin) 29 | const parseLaunchTransaction = async ( 30 | data: any, 31 | CREATE_IX_DISCRIMINATOR: Buffer, 32 | ) => { 33 | const launchpad = data.filters[0]; 34 | const transaction = data.transaction.transaction; 35 | const instructions = transaction.transaction.message.instructions; 36 | const meta = transaction.meta; 37 | const accountKeys = transaction.transaction.message.accountKeys; 38 | 39 | // check if it has mint ix 40 | const idxCreateIx = instructions.findIndex((ix: { programIdIndex: number, accounts: Uint8Array, data: Uint8Array }) => 41 | CREATE_IX_DISCRIMINATOR.length <= ix.data.length && 42 | CREATE_IX_DISCRIMINATOR.every((value, i) => value === ix.data[i]) 43 | ); 44 | if (idxCreateIx == -1) 45 | throw (`${launchpad}: can not find create ix`); 46 | 47 | // find inner instruction that has CPILog 48 | const innerInxs = meta.innerInstructions; 49 | 50 | const createMetadataIx = innerInxs[0].instructions.find((ix: any) => ix.data[0] == 33); 51 | if (!createMetadataIx) { 52 | logLogger.log(`${launchpad}: can not find metadata create ix`); 53 | return; 54 | } 55 | const metadata = parseMetadata(createMetadataIx.data); 56 | 57 | // find mint 58 | const mintIdx = meta.postBalances.findIndex((n: number) => n == MINT_POST_BALANCE); 59 | if (mintIdx == undefined) { 60 | logLogger.log(`${launchpad}: can not find mint with lamports`); 61 | return; 62 | } 63 | const mint = base58.encode(accountKeys[mintIdx]); 64 | if ((launchpad == "pumpfun" && !mint.endsWith("pump")) 65 | || (launchpad == "raydiumlaunch" && !mint.endsWith("bonk"))) 66 | return; 67 | 68 | const preBalances = meta.preBalances; 69 | const postBalances = meta.postBalances; 70 | // get buy amount 71 | let buyLamports = 0; 72 | if (launchpad == "raydiumlaunch" || launchpad == "pumpfun") { 73 | buyLamports = (preBalances[0] - postBalances[0]) - 0.02 * LAMPORTS_PER_SOL; 74 | 75 | if (buyLamports < LAUNCH_MIN_BUY_SOL || buyLamports > LAUNCH_MAX_BUY_SOL) { 76 | // logLogger.log("invalid buy amount, buyLamports:", (buyLamports / LAMPORTS_PER_SOL).toFixed(1)); 77 | return; 78 | } 79 | } 80 | 81 | // get token supply 82 | const supply = meta.postTokenBalances!.reduce((accumulator: number, currentValue: any) => accumulator + (currentValue.uiTokenAmount.uiAmount || 0), 0); 83 | 84 | // get vault addr 85 | let vault = null; 86 | if (launchpad == "believe") { 87 | const index = postBalances.findIndex((balance: number) => balance == 3841920); 88 | vault = PublicKey.findProgramAddressSync([ 89 | Buffer.from("token_vault"), NATIVE_MINT.toBuffer(), accountKeys[index]], 90 | METEORA_DBC_PROGRAM)[0]; 91 | } else { 92 | const { max, index } = preBalances.reduce((acc: { max: number, index: number }, pre: number, i: number) => { 93 | const diff = postBalances[i] - pre; 94 | if (diff > acc.max) { 95 | acc.max = diff; 96 | acc.index = i; 97 | } 98 | return acc; 99 | }, { max: 0, index: -1 }); 100 | 101 | vault = base58.encode(accountKeys[index]); 102 | } 103 | 104 | // get token vault addr 105 | const maxIndex = meta.postTokenBalances!.reduce((maxIndex: any, currentItem: any, currentIndex: any) => { 106 | return (currentItem.uiTokenAmount.uiAmount || 0) > (meta.postTokenBalances![maxIndex].uiTokenAmount.uiAmount || 0) ? currentIndex : maxIndex; 107 | }, 0); 108 | if (maxIndex == undefined) { 109 | errorLogger.error(`can not find max token value, tx: ${base58.encode(transaction.signature)}`); 110 | return 111 | }; 112 | const tokenVault = base58.encode(accountKeys[meta.postTokenBalances![maxIndex].accountIndex]); 113 | 114 | // handle token create 115 | await handleCreate(launchpad, metadata.name, metadata.symbol, metadata.uri, 116 | new PublicKey(mint), new PublicKey(vault), new PublicKey(tokenVault), 117 | base58.encode(transaction.signature), buyLamports, supply); 118 | } 119 | 120 | const parsePumpMigrate = async (data: any) => { 121 | const transaction = data.transaction.transaction; 122 | const instructions = transaction.transaction.message.instructions; 123 | const meta = transaction.meta; 124 | 125 | // check if it has migrate ix 126 | const idxCreateIx = instructions.findIndex((ix: { programIdIndex: number, accounts: Uint8Array, data: Uint8Array }) => 127 | PUMP_FUN.MIGRATE_IX_DISCRIMINATOR.length <= ix.data.length && 128 | PUMP_FUN.MIGRATE_IX_DISCRIMINATOR.every((value, i) => value === ix.data[i]) 129 | ); 130 | if (idxCreateIx == -1) { 131 | logLogger.log("pumpMigrate: can not find create ix"); 132 | return; 133 | } 134 | 135 | // find inner instruction that has CPILog 136 | const innerInxs = meta.innerInstructions; 137 | const cpiIx = findCpiIx(innerInxs, PUMP_FUN.MIGRATE_CPI_DISCRIMINATOR); 138 | if (cpiIx == null) { 139 | logLogger.log("pumpMigrate: can not find migrate CPI"); 140 | return; 141 | } 142 | const migrateData = new PumpFunSDK().parseMigrateCpiLog(cpiIx.data); 143 | 144 | // check if it has target wallet 145 | let hasTarget = false; 146 | const accountKeys: Buffer[] = transaction.transaction.message.accountKeys; 147 | for (let i = 0; i < accountKeys.length; i++) { 148 | for (let j = 0; j < TARGET_WALLETS.length; j++) { 149 | if (base58.encode(accountKeys[i]) == TARGET_WALLETS[j]) { 150 | hasTarget = true; 151 | break; 152 | } 153 | } 154 | 155 | if (hasTarget == true) 156 | break; 157 | } 158 | 159 | // get pool balance 160 | const buyCpiIx = findCpiIx(innerInxs, PUMP_FUN.BUY_CPI_DISCRIMINATOR); 161 | let solReserve = BigInt(0); 162 | let tokenReserve = BigInt(0); 163 | let coinCreator = PublicKey.default; 164 | if (!buyCpiIx) { 165 | logLogger.log("can not find buy ix"); 166 | } else { 167 | const buyData = new PumpFunSDK().parseBuyCpiLog(buyCpiIx.data); 168 | solReserve = buyData.poolQuoteTokenReserves + buyData.quoteAmountInWithLpFee; 169 | tokenReserve = buyData.poolBaseTokenReserves - buyData.baseAmountOut; 170 | coinCreator = buyData.coinCreator; 171 | } 172 | 173 | // handle token migrate 174 | await handleMigrate(migrateData, base58.encode(transaction.signature), solReserve, tokenReserve, coinCreator, hasTarget); 175 | } 176 | 177 | // Collect all incoming events 178 | stream.on("data", async (data: any) => { 179 | if (data.filters.includes('wallet')) { 180 | // check wallet balance 181 | const lamports = data.account.account.lamports; 182 | logLogger.log("balance: ", lamports / LAMPORTS_PER_SOL); 183 | 184 | const hasEnoughBalance = lamports >= (BUY_SOL_AMOUNT * LAMPORTS_PER_SOL); 185 | setHasEnoughBalance(hasEnoughBalance); 186 | 187 | return; 188 | } 189 | 190 | try { 191 | if (data.filters[0] == 'pumpfun') { 192 | // stream new pump tokens 193 | await parseLaunchTransaction(data, PUMP_FUN.CREATE_IX_DISCRIMINATOR); 194 | return; 195 | } else if (data.filters[0] == 'pumpswap') { 196 | // stream pump swap migrate 197 | await parsePumpMigrate(data); 198 | return; 199 | } else if (data.filters[0] == 'raydiumlaunch') { 200 | // stream new tokens on raydium launchpad 201 | await parseLaunchTransaction(data, RAYDIUM.CREATE_IX_DISCRIMINATOR); 202 | return; 203 | } else if (data.filters[0] == 'believe') { 204 | // stream new tokens on raydium launchpad 205 | await parseLaunchTransaction(data, BELIEVE.CREATE_IX_DISCRIMINATOR); 206 | return; 207 | } 208 | } catch (e) { 209 | errorLogger.log(base58.encode(data.transaction.transaction.signature)); 210 | errorLogger.log(e as string); 211 | } 212 | }); 213 | 214 | // Create a subscription request 215 | const request: SubscribeRequest = { 216 | slots: {}, 217 | accounts: { 218 | // subscribe wallet balance change 219 | wallet: { 220 | account: [userKp.publicKey.toBase58()], 221 | owner: [SystemProgram.programId.toBase58()], 222 | filters: [] 223 | } 224 | }, 225 | transactions: { 226 | // subscribe token create on pumpfun 227 | pumpfun: { 228 | vote: false, 229 | failed: false, 230 | accountInclude: [], 231 | accountExclude: [], 232 | accountRequired: [ 233 | PUMP_FUN.PROGRAM_ID, 234 | PUMP_FUN.MINT_AUTH.toBase58(), 235 | MPL_TOKEN_METADATA_PROGRAM_ID 236 | ] 237 | }, 238 | // subscribe token migration from pumpfun to pumpswap 239 | pumpswap: { 240 | vote: false, 241 | failed: false, 242 | accountInclude: [], 243 | accountExclude: [], 244 | accountRequired: [ 245 | PUMP_FUN.AMM_PROGRAM_ID, 246 | PUMP_FUN.PROGRAM_ID, 247 | PUMP_FUN.MIGRATION 248 | ] 249 | }, 250 | // subscribe token launch on raydium launchpad 251 | raydiumlaunch: { 252 | vote: false, 253 | failed: false, 254 | accountInclude: [], 255 | accountExclude: [], 256 | accountRequired: [ 257 | RAYDIUM.LAUNCHPAD_AUTHORITY, 258 | MPL_TOKEN_METADATA_PROGRAM_ID 259 | ] 260 | }, 261 | // subscribe token creation by believe(launchcoin) 262 | believe: { 263 | vote: false, 264 | failed: false, 265 | accountInclude: [], 266 | accountExclude: [], 267 | accountRequired: [ 268 | BELIEVE.AUTHORITY, 269 | MPL_TOKEN_METADATA_PROGRAM_ID 270 | ] 271 | } 272 | }, 273 | blocks: {}, 274 | blocksMeta: {}, 275 | accountsDataSlice: [], 276 | commitment: CommitmentLevel.PROCESSED, // Subscribe to processed blocks for the fastest updates 277 | entry: {}, 278 | transactionsStatus: {} 279 | }; 280 | 281 | // Sending a subscription request 282 | try { 283 | await new Promise((resolve, reject) => { 284 | stream.write(request, (err: null | undefined) => { 285 | if (err === null || err === undefined) { 286 | resolve(); 287 | } else { 288 | reject(err); 289 | } 290 | }); 291 | }); 292 | } catch (reason) { 293 | logLogger.log(reason as string); 294 | throw reason; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/lib/pumpswap/pumpswap.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, BN, Program } from "@coral-xyz/anchor"; 2 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 3 | import { clusterApiUrl, Connection, Keypair, PublicKey, TransactionInstruction } from "@solana/web3.js"; 4 | import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; 5 | import { PumpSwap, PumpSwapIDL } from "../../idl"; 6 | import { GLOBAL_CONFIG_SEED, LP_MINT_SEED, POOL_SEED, PROTOCOL_FEE_RECIPIENT } from "./constants"; 7 | import { CreatePoolType, DepositType, TradeType, WithdrawType } from "./types"; 8 | 9 | export class PumpSwapSDK { 10 | private program: Program; 11 | 12 | /** 13 | * 14 | * @param connection Connection 15 | */ 16 | constructor( 17 | connection: Connection 18 | ) { 19 | const wallet = new NodeWallet(Keypair.generate()); 20 | const provider = new AnchorProvider(connection, wallet, { commitment: connection.commitment }); 21 | this.program = new Program(PumpSwapIDL as PumpSwap, provider); 22 | } 23 | 24 | /** 25 | * 26 | * @param lpFeeBasisPoints BN 27 | * @param protocolFeeBasisPoints BN 28 | * @param protocolFeeRecipients Array 29 | * @returns Promise 30 | */ 31 | getCreateConfigInstruction = async ( 32 | lpFeeBasisPoints: BN, 33 | protocolFeeBasisPoints: BN, 34 | protocolFeeRecipients: Array, 35 | coinCreatorFeeBasisPoints: BN 36 | ): Promise => { 37 | const ix = await this.program.methods 38 | .createConfig( 39 | lpFeeBasisPoints, 40 | protocolFeeBasisPoints, 41 | protocolFeeRecipients, 42 | coinCreatorFeeBasisPoints 43 | ) 44 | .accounts({ 45 | program: this.program.programId 46 | }) 47 | .instruction() 48 | return ix 49 | } 50 | 51 | /** 52 | * 53 | * @param baseAmountOut BN 54 | * @param maxQuoteAmountIn BN 55 | * @param tradeParam TradeType 56 | * @returns Promise 57 | */ 58 | getBuyInstruction = async ( 59 | baseAmountOut: BN, 60 | maxQuoteAmountIn: BN, 61 | tradeParam: TradeType 62 | ): Promise => { 63 | const { 64 | baseMint, 65 | baseTokenProgram, 66 | pool, 67 | quoteMint, 68 | quoteTokenProgram, 69 | user, 70 | coinCreator 71 | } = tradeParam 72 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId); 73 | const userBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, user); 74 | const userQuoteTokenAccount = getAssociatedTokenAddressSync(quoteMint, user); 75 | 76 | const poolBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, pool, true); 77 | const poolQuoteTokenAccount = getAssociatedTokenAddressSync(quoteMint, pool, true); 78 | 79 | const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync( 80 | [Buffer.from("creator_vault"), coinCreator.toBuffer()], 81 | this.program.programId, 82 | ); 83 | const coinCreatorVaultAta = getAssociatedTokenAddressSync(quoteMint, coinCreatorVaultAuthority, true); 84 | 85 | const ix = await this.program.methods 86 | .buy(baseAmountOut, maxQuoteAmountIn) 87 | .accountsPartial({ 88 | pool, 89 | globalConfig, 90 | user: user, 91 | baseMint, 92 | quoteMint, 93 | userBaseTokenAccount, 94 | userQuoteTokenAccount, 95 | poolBaseTokenAccount, 96 | poolQuoteTokenAccount, 97 | protocolFeeRecipient: PROTOCOL_FEE_RECIPIENT, 98 | baseTokenProgram, 99 | quoteTokenProgram, 100 | program: this.program.programId, 101 | coinCreatorVaultAta, 102 | coinCreatorVaultAuthority 103 | }) 104 | .instruction() 105 | 106 | return ix 107 | } 108 | 109 | /** 110 | * 111 | * @param baseAmountIn BN 112 | * @param minQuoteAmountOut BN 113 | * @param tradeParam TradeType 114 | * @returns Promise 115 | */ 116 | getSellInstruction = async ( 117 | baseAmountIn: BN, 118 | minQuoteAmountOut: BN, 119 | tradeParam: TradeType 120 | ): Promise => { 121 | const { 122 | baseMint, 123 | baseTokenProgram, 124 | pool, 125 | quoteMint, 126 | quoteTokenProgram, 127 | user 128 | } = tradeParam 129 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 130 | const userBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, user) 131 | const userQuoteTokenAccount = getAssociatedTokenAddressSync(quoteMint, user) 132 | const ix = await this.program.methods 133 | .sell(baseAmountIn, minQuoteAmountOut) 134 | .accounts({ 135 | pool, 136 | globalConfig: globalConfig, 137 | program: this.program.programId, 138 | protocolFeeRecipient: PROTOCOL_FEE_RECIPIENT, 139 | baseTokenProgram, 140 | quoteTokenProgram, 141 | userBaseTokenAccount, 142 | userQuoteTokenAccount, 143 | user: user 144 | }) 145 | .instruction() 146 | return ix 147 | } 148 | 149 | /** 150 | * 151 | * @param index number 152 | * @param baseAmountIn BN 153 | * @param quoteAmountIn BN 154 | * @param createPoolParam CreatePoolType 155 | * @param user PublicKey 156 | * @returns Promise 157 | */ 158 | getCreatePoolInstruction = async ( 159 | index: number, 160 | baseAmountIn: BN, 161 | quoteAmountIn: BN, 162 | createPoolParam: CreatePoolType, 163 | user: PublicKey, 164 | coinCreatorFeeBasisPoints: BN 165 | ): Promise => { 166 | const { 167 | creator, 168 | baseMint, 169 | quoteMint, 170 | baseTokenProgram, 171 | quoteTokenProgram 172 | } = createPoolParam 173 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 174 | const userBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, user) 175 | const userQuoteTokenAccount = getAssociatedTokenAddressSync(quoteMint, user) 176 | const ix = await this.program.methods.createPool(index, baseAmountIn, quoteAmountIn, coinCreatorFeeBasisPoints).accounts({ 177 | globalConfig: globalConfig, 178 | baseMint, 179 | quoteMint, 180 | userBaseTokenAccount, 181 | userQuoteTokenAccount, 182 | baseTokenProgram, 183 | quoteTokenProgram, 184 | creator, 185 | program: this.program.programId, 186 | }) 187 | .instruction() 188 | 189 | return ix 190 | } 191 | 192 | /** 193 | * 194 | * @param lpTokenAmountOut BN 195 | * @param maxBaseAmountIn BN 196 | * @param maxQuoteAmountIn BN 197 | * @param depositType DepositType 198 | * @returns Promise 199 | */ 200 | getDepositInstruction = async ( 201 | lpTokenAmountOut: BN, 202 | maxBaseAmountIn: BN, 203 | maxQuoteAmountIn: BN, 204 | depositType: DepositType 205 | ): Promise => { 206 | const { baseMint, pool, quoteMint, user } = depositType 207 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 208 | const [lpMint] = PublicKey.findProgramAddressSync( 209 | [Buffer.from(LP_MINT_SEED), pool.toBuffer()], 210 | this.program.programId 211 | ); 212 | const userBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, user) 213 | const userQuoteTokenAccount = getAssociatedTokenAddressSync(quoteMint, user) 214 | const userPoolTokenAccount = getAssociatedTokenAddressSync(lpMint, user) 215 | const ix = await this.program.methods.deposit(lpTokenAmountOut, maxBaseAmountIn, maxQuoteAmountIn).accounts({ 216 | globalConfig: globalConfig, 217 | pool, 218 | program: this.program.programId, 219 | userPoolTokenAccount, 220 | userBaseTokenAccount, 221 | userQuoteTokenAccount, 222 | user: user 223 | }) 224 | .instruction() 225 | 226 | return ix 227 | } 228 | 229 | /** 230 | * 231 | * @param disableCreatePool boolean 232 | * @param disableDeposit boolean 233 | * @param disableWithdraw boolean 234 | * @param disableBuy boolean 235 | * @param disableSell boolean 236 | * @returns Promise 237 | */ 238 | getDisableInstruction = async ( 239 | disableCreatePool: boolean, 240 | disableDeposit: boolean, 241 | disableWithdraw: boolean, 242 | disableBuy: boolean, 243 | disableSell: boolean 244 | ): Promise => { 245 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 246 | 247 | const ix = await this.program.methods 248 | .disable(disableCreatePool, disableDeposit, disableWithdraw, disableBuy, disableSell) 249 | .accounts({ 250 | globalConfig: globalConfig, 251 | program: this.program.programId 252 | }) 253 | .instruction() 254 | 255 | return ix 256 | } 257 | 258 | /** 259 | * 260 | * @param account PublicKey 261 | * @returns Promise 262 | */ 263 | getExtendAccountInstruction = async (account: PublicKey): Promise => { 264 | const ix = await this.program.methods 265 | .extendAccount() 266 | .accounts({ 267 | account: account, 268 | program: this.program.programId 269 | }) 270 | .instruction() 271 | 272 | return ix 273 | } 274 | 275 | /** 276 | * 277 | * @param newAdmin PublicKey 278 | * @returns Promise 279 | */ 280 | getUpdateAdminInstruction = async (newAdmin: PublicKey): Promise => { 281 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 282 | const ix = await this.program.methods 283 | .updateAdmin() 284 | .accounts({ 285 | globalConfig: globalConfig, 286 | program: this.program.programId, 287 | newAdmin: newAdmin 288 | }) 289 | .instruction() 290 | 291 | return ix 292 | } 293 | 294 | /** 295 | * 296 | * @param lpFeeBasisPoints BN 297 | * @param protocolFeeBasisPoints BN 298 | * @param protocolFeeRecipients Array 299 | * @returns Promise 300 | */ 301 | getUpdateFeeConfigInstruction = async ( 302 | lpFeeBasisPoints: BN, 303 | protocolFeeBasisPoints: BN, 304 | protocolFeeRecipients: Array, 305 | coinCreatorFeeBasisPoints: BN 306 | ): Promise => { 307 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 308 | const ix = await this.program.methods 309 | .updateFeeConfig(lpFeeBasisPoints, protocolFeeBasisPoints, protocolFeeRecipients, coinCreatorFeeBasisPoints) 310 | .accounts({ 311 | globalConfig: globalConfig, 312 | program: this.program.programId 313 | }) 314 | .instruction() 315 | 316 | return ix 317 | } 318 | 319 | /** 320 | * 321 | * @param lpTokenAmountIn BN 322 | * @param minBaseAmountOut BN 323 | * @param minQuoteAmountOut BN 324 | * @param withdrawParam WithdrawType 325 | * @returns Promise 326 | */ 327 | getWithdrawInstruction = async ( 328 | lpTokenAmountIn: BN, 329 | minBaseAmountOut: BN, 330 | minQuoteAmountOut: BN, 331 | withdrawParam: WithdrawType 332 | ): Promise => { 333 | const { baseMint, creator, index, quoteMint, user } = withdrawParam 334 | 335 | const [pool] = PublicKey.findProgramAddressSync([ 336 | Buffer.from(POOL_SEED), 337 | new BN(index).toArrayLike(Buffer, "le", 8), 338 | creator.toBuffer(), 339 | baseMint.toBuffer(), 340 | quoteMint.toBuffer(), 341 | ], this.program.programId); 342 | const [lpMint] = PublicKey.findProgramAddressSync( 343 | [Buffer.from(LP_MINT_SEED), pool.toBuffer()], 344 | this.program.programId 345 | ); 346 | const [userPoolTokenAccount] = PublicKey.findProgramAddressSync( 347 | [creator.toBuffer(), TOKEN_2022_PROGRAM_ID.toBuffer(), lpMint.toBuffer()], 348 | this.program.programId 349 | ); 350 | const [globalConfig] = PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], this.program.programId) 351 | const userBaseTokenAccount = getAssociatedTokenAddressSync(baseMint, user) 352 | const userQuoteTokenAccount = getAssociatedTokenAddressSync(quoteMint, user) 353 | const ix = await this.program.methods 354 | .withdraw(lpTokenAmountIn, minBaseAmountOut, minQuoteAmountOut) 355 | .accounts({ 356 | pool, 357 | globalConfig: globalConfig, 358 | userBaseTokenAccount, 359 | userQuoteTokenAccount, 360 | userPoolTokenAccount, 361 | program: this.program.programId, 362 | user: user 363 | }) 364 | .instruction() 365 | 366 | return ix 367 | } 368 | } 369 | 370 | export default PumpSwapSDK -------------------------------------------------------------------------------- /src/idl/pump-fun.ts: -------------------------------------------------------------------------------- 1 | export type PumpFun = { 2 | address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; 3 | metadata: { 4 | name: "pump"; 5 | version: "0.1.0"; 6 | spec: "0.1.0"; 7 | description: "Created with Anchor" 8 | }, 9 | instructions: [ 10 | { 11 | name: "buy"; 12 | docs: [ 13 | "Buys tokens from a bonding curve." 14 | ]; 15 | discriminator: [ 16 | 102, 17 | 6, 18 | 61, 19 | 18, 20 | 1, 21 | 218, 22 | 235, 23 | 234 24 | ]; 25 | accounts: [ 26 | { 27 | name: "global"; 28 | pda: { 29 | seeds: [ 30 | { 31 | kind: "const"; 32 | value: [ 33 | 103, 34 | 108, 35 | 111, 36 | 98, 37 | 97, 38 | 108 39 | ] 40 | } 41 | ] 42 | } 43 | }, 44 | { 45 | name: "feeRecipient"; 46 | writable: true 47 | }, 48 | { 49 | name: "mint" 50 | }, 51 | { 52 | name: "bondingCurve"; 53 | writable: true, 54 | pda: { 55 | seeds: [ 56 | { 57 | kind: "const"; 58 | value: [ 59 | 98, 60 | 111, 61 | 110, 62 | 100, 63 | 105, 64 | 110, 65 | 103, 66 | 45, 67 | 99, 68 | 117, 69 | 114, 70 | 118, 71 | 101 72 | ] 73 | }, 74 | { 75 | kind: "account"; 76 | path: "mint" 77 | } 78 | ] 79 | } 80 | }, 81 | { 82 | name: "associatedBondingCurve"; 83 | writable: true, 84 | pda: { 85 | seeds: [ 86 | { 87 | kind: "account"; 88 | path: "bondingCurve" 89 | }, 90 | { 91 | kind: "const"; 92 | value: [ 93 | 6, 94 | 221, 95 | 246, 96 | 225, 97 | 215, 98 | 101, 99 | 161, 100 | 147, 101 | 217, 102 | 203, 103 | 225, 104 | 70, 105 | 206, 106 | 235, 107 | 121, 108 | 172, 109 | 28, 110 | 180, 111 | 133, 112 | 237, 113 | 95, 114 | 91, 115 | 55, 116 | 145, 117 | 58, 118 | 140, 119 | 245, 120 | 133, 121 | 126, 122 | 255, 123 | 0, 124 | 169 125 | ] 126 | }, 127 | { 128 | kind: "account"; 129 | path: "mint" 130 | } 131 | ]; 132 | program: { 133 | kind: "const"; 134 | value: [ 135 | 140, 136 | 151, 137 | 37, 138 | 143, 139 | 78, 140 | 36, 141 | 137, 142 | 241, 143 | 187, 144 | 61, 145 | 16, 146 | 41, 147 | 20, 148 | 142, 149 | 13, 150 | 131, 151 | 11, 152 | 90, 153 | 19, 154 | 153, 155 | 218, 156 | 255, 157 | 16, 158 | 132, 159 | 4, 160 | 142, 161 | 123, 162 | 216, 163 | 219, 164 | 233, 165 | 248, 166 | 89 167 | ] 168 | } 169 | } 170 | }, 171 | { 172 | name: "associatedUser"; 173 | writable: true 174 | }, 175 | { 176 | name: "user"; 177 | writable: true, 178 | signer: true 179 | }, 180 | { 181 | name: "systemProgram"; 182 | address: "11111111111111111111111111111111" 183 | }, 184 | { 185 | name: "tokenProgram"; 186 | address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" 187 | }, 188 | { 189 | name: "rent"; 190 | docs: [ 191 | "Unused" 192 | ]; 193 | address: "SysvarRent111111111111111111111111111111111" 194 | }, 195 | { 196 | name: "eventAuthority"; 197 | pda: { 198 | seeds: [ 199 | { 200 | kind: "const"; 201 | value: [ 202 | 95, 203 | 95, 204 | 101, 205 | 118, 206 | 101, 207 | 110, 208 | 116, 209 | 95, 210 | 97, 211 | 117, 212 | 116, 213 | 104, 214 | 111, 215 | 114, 216 | 105, 217 | 116, 218 | 121 219 | ] 220 | } 221 | ] 222 | } 223 | }, 224 | { 225 | name: "program" 226 | } 227 | ]; 228 | args: [ 229 | { 230 | name: "amount"; 231 | type: "u64" 232 | }, 233 | { 234 | name: "maxSolCost"; 235 | type: "u64" 236 | } 237 | ] 238 | }, 239 | { 240 | name: "create"; 241 | docs: [ 242 | "Creates a new coin and bonding curve." 243 | ]; 244 | discriminator: [ 245 | 24, 246 | 30, 247 | 200, 248 | 40, 249 | 5, 250 | 28, 251 | 7, 252 | 119 253 | ]; 254 | accounts: [ 255 | { 256 | name: "mint"; 257 | writable: true, 258 | signer: true 259 | }, 260 | { 261 | name: "mintAuthority"; 262 | pda: { 263 | seeds: [ 264 | { 265 | kind: "const"; 266 | value: [ 267 | 109, 268 | 105, 269 | 110, 270 | 116, 271 | 45, 272 | 97, 273 | 117, 274 | 116, 275 | 104, 276 | 111, 277 | 114, 278 | 105, 279 | 116, 280 | 121 281 | ] 282 | } 283 | ] 284 | } 285 | }, 286 | { 287 | name: "bondingCurve"; 288 | writable: true, 289 | pda: { 290 | seeds: [ 291 | { 292 | kind: "const"; 293 | value: [ 294 | 98, 295 | 111, 296 | 110, 297 | 100, 298 | 105, 299 | 110, 300 | 103, 301 | 45, 302 | 99, 303 | 117, 304 | 114, 305 | 118, 306 | 101 307 | ] 308 | }, 309 | { 310 | kind: "account"; 311 | path: "mint" 312 | } 313 | ] 314 | } 315 | }, 316 | { 317 | name: "associatedBondingCurve"; 318 | writable: true, 319 | pda: { 320 | seeds: [ 321 | { 322 | kind: "account"; 323 | path: "bondingCurve" 324 | }, 325 | { 326 | kind: "const"; 327 | value: [ 328 | 6, 329 | 221, 330 | 246, 331 | 225, 332 | 215, 333 | 101, 334 | 161, 335 | 147, 336 | 217, 337 | 203, 338 | 225, 339 | 70, 340 | 206, 341 | 235, 342 | 121, 343 | 172, 344 | 28, 345 | 180, 346 | 133, 347 | 237, 348 | 95, 349 | 91, 350 | 55, 351 | 145, 352 | 58, 353 | 140, 354 | 245, 355 | 133, 356 | 126, 357 | 255, 358 | 0, 359 | 169 360 | ] 361 | }, 362 | { 363 | kind: "account"; 364 | path: "mint" 365 | } 366 | ]; 367 | program: { 368 | kind: "const"; 369 | value: [ 370 | 140, 371 | 151, 372 | 37, 373 | 143, 374 | 78, 375 | 36, 376 | 137, 377 | 241, 378 | 187, 379 | 61, 380 | 16, 381 | 41, 382 | 20, 383 | 142, 384 | 13, 385 | 131, 386 | 11, 387 | 90, 388 | 19, 389 | 153, 390 | 218, 391 | 255, 392 | 16, 393 | 132, 394 | 4, 395 | 142, 396 | 123, 397 | 216, 398 | 219, 399 | 233, 400 | 248, 401 | 89 402 | ] 403 | } 404 | } 405 | }, 406 | { 407 | name: "global"; 408 | pda: { 409 | seeds: [ 410 | { 411 | kind: "const"; 412 | value: [ 413 | 103, 414 | 108, 415 | 111, 416 | 98, 417 | 97, 418 | 108 419 | ] 420 | } 421 | ] 422 | } 423 | }, 424 | { 425 | name: "mplTokenMetadata"; 426 | address: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" 427 | }, 428 | { 429 | name: "metadata"; 430 | writable: true, 431 | pda: { 432 | seeds: [ 433 | { 434 | kind: "const"; 435 | value: [ 436 | 109, 437 | 101, 438 | 116, 439 | 97, 440 | 100, 441 | 97, 442 | 116, 443 | 97 444 | ] 445 | }, 446 | { 447 | kind: "const"; 448 | value: [ 449 | 11, 450 | 112, 451 | 101, 452 | 177, 453 | 227, 454 | 209, 455 | 124, 456 | 69, 457 | 56, 458 | 157, 459 | 82, 460 | 127, 461 | 107, 462 | 4, 463 | 195, 464 | 205, 465 | 88, 466 | 184, 467 | 108, 468 | 115, 469 | 26, 470 | 160, 471 | 253, 472 | 181, 473 | 73, 474 | 182, 475 | 209, 476 | 188, 477 | 3, 478 | 248, 479 | 41, 480 | 70 481 | ] 482 | }, 483 | { 484 | kind: "account"; 485 | path: "mint" 486 | } 487 | ]; 488 | program: { 489 | kind: "const"; 490 | value: [ 491 | 11, 492 | 112, 493 | 101, 494 | 177, 495 | 227, 496 | 209, 497 | 124, 498 | 69, 499 | 56, 500 | 157, 501 | 82, 502 | 127, 503 | 107, 504 | 4, 505 | 195, 506 | 205, 507 | 88, 508 | 184, 509 | 108, 510 | 115, 511 | 26, 512 | 160, 513 | 253, 514 | 181, 515 | 73, 516 | 182, 517 | 209, 518 | 188, 519 | 3, 520 | 248, 521 | 41, 522 | 70 523 | ] 524 | } 525 | } 526 | }, 527 | { 528 | name: "user"; 529 | writable: true, 530 | signer: true 531 | }, 532 | { 533 | name: "systemProgram"; 534 | address: "11111111111111111111111111111111" 535 | }, 536 | { 537 | name: "tokenProgram"; 538 | address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" 539 | }, 540 | { 541 | name: "associatedTokenProgram"; 542 | address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" 543 | }, 544 | { 545 | name: "rent"; 546 | address: "SysvarRent111111111111111111111111111111111" 547 | }, 548 | { 549 | name: "eventAuthority"; 550 | pda: { 551 | seeds: [ 552 | { 553 | kind: "const"; 554 | value: [ 555 | 95, 556 | 95, 557 | 101, 558 | 118, 559 | 101, 560 | 110, 561 | 116, 562 | 95, 563 | 97, 564 | 117, 565 | 116, 566 | 104, 567 | 111, 568 | 114, 569 | 105, 570 | 116, 571 | 121 572 | ] 573 | } 574 | ] 575 | } 576 | }, 577 | { 578 | name: "program" 579 | } 580 | ]; 581 | args: [ 582 | { 583 | name: "name"; 584 | type: "string" 585 | }, 586 | { 587 | name: "symbol"; 588 | type: "string" 589 | }, 590 | { 591 | name: "uri"; 592 | type: "string" 593 | }, 594 | { 595 | name: "creator"; 596 | type: "pubkey" 597 | } 598 | ] 599 | }, 600 | { 601 | name: "extendAccount"; 602 | docs: [ 603 | "Extends the size of program-owned accounts" 604 | ]; 605 | discriminator: [ 606 | 234, 607 | 102, 608 | 194, 609 | 203, 610 | 150, 611 | 72, 612 | 62, 613 | 229 614 | ]; 615 | accounts: [ 616 | { 617 | name: "account"; 618 | writable: true 619 | }, 620 | { 621 | name: "user"; 622 | signer: true 623 | }, 624 | { 625 | name: "systemProgram"; 626 | address: "11111111111111111111111111111111" 627 | }, 628 | { 629 | name: "eventAuthority"; 630 | pda: { 631 | seeds: [ 632 | { 633 | kind: "const"; 634 | value: [ 635 | 95, 636 | 95, 637 | 101, 638 | 118, 639 | 101, 640 | 110, 641 | 116, 642 | 95, 643 | 97, 644 | 117, 645 | 116, 646 | 104, 647 | 111, 648 | 114, 649 | 105, 650 | 116, 651 | 121 652 | ] 653 | } 654 | ] 655 | } 656 | }, 657 | { 658 | name: "program" 659 | } 660 | ]; 661 | args: [] 662 | }, 663 | { 664 | name: "initialize"; 665 | docs: [ 666 | "Creates the global state." 667 | ]; 668 | discriminator: [ 669 | 175, 670 | 175, 671 | 109, 672 | 31, 673 | 13, 674 | 152, 675 | 155, 676 | 237 677 | ]; 678 | accounts: [ 679 | { 680 | name: "global"; 681 | writable: true, 682 | pda: { 683 | seeds: [ 684 | { 685 | kind: "const"; 686 | value: [ 687 | 103, 688 | 108, 689 | 111, 690 | 98, 691 | 97, 692 | 108 693 | ] 694 | } 695 | ] 696 | } 697 | }, 698 | { 699 | name: "user"; 700 | writable: true, 701 | signer: true 702 | }, 703 | { 704 | name: "systemProgram"; 705 | address: "11111111111111111111111111111111" 706 | } 707 | ]; 708 | args: [] 709 | }, 710 | { 711 | name: "migrate"; 712 | docs: [ 713 | "Migrates liquidity to pumpAmm if bonding curve is complete" 714 | ]; 715 | discriminator: [ 716 | 155, 717 | 234, 718 | 231, 719 | 146, 720 | 236, 721 | 158, 722 | 162, 723 | 30 724 | ]; 725 | accounts: [ 726 | { 727 | name: "global"; 728 | pda: { 729 | seeds: [ 730 | { 731 | kind: "const"; 732 | value: [ 733 | 103, 734 | 108, 735 | 111, 736 | 98, 737 | 97, 738 | 108 739 | ] 740 | } 741 | ] 742 | } 743 | }, 744 | { 745 | name: "withdrawAuthority"; 746 | writable: true, 747 | relations: [ 748 | "global" 749 | ] 750 | }, 751 | { 752 | name: "mint" 753 | }, 754 | { 755 | name: "bondingCurve"; 756 | writable: true, 757 | pda: { 758 | seeds: [ 759 | { 760 | kind: "const"; 761 | value: [ 762 | 98, 763 | 111, 764 | 110, 765 | 100, 766 | 105, 767 | 110, 768 | 103, 769 | 45, 770 | 99, 771 | 117, 772 | 114, 773 | 118, 774 | 101 775 | ] 776 | }, 777 | { 778 | kind: "account"; 779 | path: "mint" 780 | } 781 | ] 782 | } 783 | }, 784 | { 785 | name: "associatedBondingCurve"; 786 | writable: true, 787 | pda: { 788 | seeds: [ 789 | { 790 | kind: "account"; 791 | path: "bondingCurve" 792 | }, 793 | { 794 | kind: "const"; 795 | value: [ 796 | 6, 797 | 221, 798 | 246, 799 | 225, 800 | 215, 801 | 101, 802 | 161, 803 | 147, 804 | 217, 805 | 203, 806 | 225, 807 | 70, 808 | 206, 809 | 235, 810 | 121, 811 | 172, 812 | 28, 813 | 180, 814 | 133, 815 | 237, 816 | 95, 817 | 91, 818 | 55, 819 | 145, 820 | 58, 821 | 140, 822 | 245, 823 | 133, 824 | 126, 825 | 255, 826 | 0, 827 | 169 828 | ] 829 | }, 830 | { 831 | kind: "account"; 832 | path: "mint" 833 | } 834 | ]; 835 | program: { 836 | kind: "const"; 837 | value: [ 838 | 140, 839 | 151, 840 | 37, 841 | 143, 842 | 78, 843 | 36, 844 | 137, 845 | 241, 846 | 187, 847 | 61, 848 | 16, 849 | 41, 850 | 20, 851 | 142, 852 | 13, 853 | 131, 854 | 11, 855 | 90, 856 | 19, 857 | 153, 858 | 218, 859 | 255, 860 | 16, 861 | 132, 862 | 4, 863 | 142, 864 | 123, 865 | 216, 866 | 219, 867 | 233, 868 | 248, 869 | 89 870 | ] 871 | } 872 | } 873 | }, 874 | { 875 | name: "user"; 876 | signer: true 877 | }, 878 | { 879 | name: "systemProgram"; 880 | address: "11111111111111111111111111111111" 881 | }, 882 | { 883 | name: "tokenProgram"; 884 | address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" 885 | }, 886 | { 887 | name: "metadataAccount"; 888 | pda: { 889 | seeds: [ 890 | { 891 | kind: "const"; 892 | value: [ 893 | 109, 894 | 101, 895 | 116, 896 | 97, 897 | 100, 898 | 97, 899 | 116, 900 | 97 901 | ] 902 | }, 903 | { 904 | kind: "const"; 905 | value: [ 906 | 11, 907 | 112, 908 | 101, 909 | 177, 910 | 227, 911 | 209, 912 | 124, 913 | 69, 914 | 56, 915 | 157, 916 | 82, 917 | 127, 918 | 107, 919 | 4, 920 | 195, 921 | 205, 922 | 88, 923 | 184, 924 | 108, 925 | 115, 926 | 26, 927 | 160, 928 | 253, 929 | 181, 930 | 73, 931 | 182, 932 | 209, 933 | 188, 934 | 3, 935 | 248, 936 | 41, 937 | 70 938 | ] 939 | }, 940 | { 941 | kind: "account"; 942 | path: "mint" 943 | } 944 | ]; 945 | program: { 946 | kind: "const"; 947 | value: [ 948 | 11, 949 | 112, 950 | 101, 951 | 177, 952 | 227, 953 | 209, 954 | 124, 955 | 69, 956 | 56, 957 | 157, 958 | 82, 959 | 127, 960 | 107, 961 | 4, 962 | 195, 963 | 205, 964 | 88, 965 | 184, 966 | 108, 967 | 115, 968 | 26, 969 | 160, 970 | 253, 971 | 181, 972 | 73, 973 | 182, 974 | 209, 975 | 188, 976 | 3, 977 | 248, 978 | 41, 979 | 70 980 | ] 981 | } 982 | } 983 | }, 984 | { 985 | name: "creator"; 986 | docs: [ 987 | "metadata account creators only if creators is not None" 988 | ]; 989 | writable: true 990 | }, 991 | { 992 | name: "pumpAmm"; 993 | address: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA" 994 | }, 995 | { 996 | name: "pool"; 997 | writable: true 998 | }, 999 | { 1000 | name: "poolAuthority"; 1001 | writable: true, 1002 | pda: { 1003 | seeds: [ 1004 | { 1005 | kind: "const"; 1006 | value: [ 1007 | 112, 1008 | 111, 1009 | 111, 1010 | 108, 1011 | 45, 1012 | 97, 1013 | 117, 1014 | 116, 1015 | 104, 1016 | 111, 1017 | 114, 1018 | 105, 1019 | 116, 1020 | 121 1021 | ] 1022 | }, 1023 | { 1024 | kind: "account"; 1025 | path: "mint" 1026 | } 1027 | ] 1028 | } 1029 | }, 1030 | { 1031 | name: "poolAuthorityMintAccount"; 1032 | writable: true 1033 | }, 1034 | { 1035 | name: "poolAuthorityWsolAccount"; 1036 | writable: true 1037 | }, 1038 | { 1039 | name: "ammGlobalConfig" 1040 | }, 1041 | { 1042 | name: "wsolMint"; 1043 | address: "So11111111111111111111111111111111111111112" 1044 | }, 1045 | { 1046 | name: "lpMint"; 1047 | writable: true 1048 | }, 1049 | { 1050 | name: "userPoolTokenAccount"; 1051 | writable: true 1052 | }, 1053 | { 1054 | name: "poolBaseTokenAccount"; 1055 | writable: true 1056 | }, 1057 | { 1058 | name: "poolQuoteTokenAccount"; 1059 | writable: true 1060 | }, 1061 | { 1062 | name: "token2022Program"; 1063 | address: "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" 1064 | }, 1065 | { 1066 | name: "associatedTokenProgram"; 1067 | address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" 1068 | }, 1069 | { 1070 | name: "pumpAmmEventAuthority" 1071 | }, 1072 | { 1073 | name: "eventAuthority"; 1074 | pda: { 1075 | seeds: [ 1076 | { 1077 | kind: "const"; 1078 | value: [ 1079 | 95, 1080 | 95, 1081 | 101, 1082 | 118, 1083 | 101, 1084 | 110, 1085 | 116, 1086 | 95, 1087 | 97, 1088 | 117, 1089 | 116, 1090 | 104, 1091 | 111, 1092 | 114, 1093 | 105, 1094 | 116, 1095 | 121 1096 | ] 1097 | } 1098 | ] 1099 | } 1100 | }, 1101 | { 1102 | name: "program" 1103 | } 1104 | ]; 1105 | args: [] 1106 | }, 1107 | { 1108 | name: "sell"; 1109 | docs: [ 1110 | "Sells tokens into a bonding curve." 1111 | ]; 1112 | discriminator: [ 1113 | 51, 1114 | 230, 1115 | 133, 1116 | 164, 1117 | 1, 1118 | 127, 1119 | 131, 1120 | 173 1121 | ]; 1122 | accounts: [ 1123 | { 1124 | name: "global"; 1125 | pda: { 1126 | seeds: [ 1127 | { 1128 | kind: "const"; 1129 | value: [ 1130 | 103, 1131 | 108, 1132 | 111, 1133 | 98, 1134 | 97, 1135 | 108 1136 | ] 1137 | } 1138 | ] 1139 | } 1140 | }, 1141 | { 1142 | name: "feeRecipient"; 1143 | writable: true 1144 | }, 1145 | { 1146 | name: "mint" 1147 | }, 1148 | { 1149 | name: "bondingCurve"; 1150 | writable: true, 1151 | pda: { 1152 | seeds: [ 1153 | { 1154 | kind: "const"; 1155 | value: [ 1156 | 98, 1157 | 111, 1158 | 110, 1159 | 100, 1160 | 105, 1161 | 110, 1162 | 103, 1163 | 45, 1164 | 99, 1165 | 117, 1166 | 114, 1167 | 118, 1168 | 101 1169 | ] 1170 | }, 1171 | { 1172 | kind: "account"; 1173 | path: "mint" 1174 | } 1175 | ] 1176 | } 1177 | }, 1178 | { 1179 | name: "associatedBondingCurve"; 1180 | writable: true, 1181 | pda: { 1182 | seeds: [ 1183 | { 1184 | kind: "account"; 1185 | path: "bondingCurve" 1186 | }, 1187 | { 1188 | kind: "const"; 1189 | value: [ 1190 | 6, 1191 | 221, 1192 | 246, 1193 | 225, 1194 | 215, 1195 | 101, 1196 | 161, 1197 | 147, 1198 | 217, 1199 | 203, 1200 | 225, 1201 | 70, 1202 | 206, 1203 | 235, 1204 | 121, 1205 | 172, 1206 | 28, 1207 | 180, 1208 | 133, 1209 | 237, 1210 | 95, 1211 | 91, 1212 | 55, 1213 | 145, 1214 | 58, 1215 | 140, 1216 | 245, 1217 | 133, 1218 | 126, 1219 | 255, 1220 | 0, 1221 | 169 1222 | ] 1223 | }, 1224 | { 1225 | kind: "account"; 1226 | path: "mint" 1227 | } 1228 | ]; 1229 | program: { 1230 | kind: "const"; 1231 | value: [ 1232 | 140, 1233 | 151, 1234 | 37, 1235 | 143, 1236 | 78, 1237 | 36, 1238 | 137, 1239 | 241, 1240 | 187, 1241 | 61, 1242 | 16, 1243 | 41, 1244 | 20, 1245 | 142, 1246 | 13, 1247 | 131, 1248 | 11, 1249 | 90, 1250 | 19, 1251 | 153, 1252 | 218, 1253 | 255, 1254 | 16, 1255 | 132, 1256 | 4, 1257 | 142, 1258 | 123, 1259 | 216, 1260 | 219, 1261 | 233, 1262 | 248, 1263 | 89 1264 | ] 1265 | } 1266 | } 1267 | }, 1268 | { 1269 | name: "associatedUser"; 1270 | writable: true 1271 | }, 1272 | { 1273 | name: "user"; 1274 | writable: true, 1275 | signer: true 1276 | }, 1277 | { 1278 | name: "systemProgram"; 1279 | address: "11111111111111111111111111111111" 1280 | }, 1281 | { 1282 | name: "associatedTokenProgram"; 1283 | docs: [ 1284 | "Unused" 1285 | ]; 1286 | address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" 1287 | }, 1288 | { 1289 | name: "tokenProgram"; 1290 | address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" 1291 | }, 1292 | { 1293 | name: "eventAuthority"; 1294 | pda: { 1295 | seeds: [ 1296 | { 1297 | kind: "const"; 1298 | value: [ 1299 | 95, 1300 | 95, 1301 | 101, 1302 | 118, 1303 | 101, 1304 | 110, 1305 | 116, 1306 | 95, 1307 | 97, 1308 | 117, 1309 | 116, 1310 | 104, 1311 | 111, 1312 | 114, 1313 | 105, 1314 | 116, 1315 | 121 1316 | ] 1317 | } 1318 | ] 1319 | } 1320 | }, 1321 | { 1322 | name: "program" 1323 | } 1324 | ]; 1325 | args: [ 1326 | { 1327 | name: "amount"; 1328 | type: "u64" 1329 | }, 1330 | { 1331 | name: "minSolOutput"; 1332 | type: "u64" 1333 | } 1334 | ] 1335 | }, 1336 | { 1337 | name: "setParams"; 1338 | docs: [ 1339 | "Sets the global state parameters." 1340 | ]; 1341 | discriminator: [ 1342 | 27, 1343 | 234, 1344 | 178, 1345 | 52, 1346 | 147, 1347 | 2, 1348 | 187, 1349 | 141 1350 | ]; 1351 | accounts: [ 1352 | { 1353 | name: "global"; 1354 | writable: true, 1355 | pda: { 1356 | seeds: [ 1357 | { 1358 | kind: "const"; 1359 | value: [ 1360 | 103, 1361 | 108, 1362 | 111, 1363 | 98, 1364 | 97, 1365 | 108 1366 | ] 1367 | } 1368 | ] 1369 | } 1370 | }, 1371 | { 1372 | name: "authority"; 1373 | writable: true, 1374 | signer: true, 1375 | relations: [ 1376 | "global" 1377 | ] 1378 | }, 1379 | { 1380 | name: "eventAuthority"; 1381 | pda: { 1382 | seeds: [ 1383 | { 1384 | kind: "const"; 1385 | value: [ 1386 | 95, 1387 | 95, 1388 | 101, 1389 | 118, 1390 | 101, 1391 | 110, 1392 | 116, 1393 | 95, 1394 | 97, 1395 | 117, 1396 | 116, 1397 | 104, 1398 | 111, 1399 | 114, 1400 | 105, 1401 | 116, 1402 | 121 1403 | ] 1404 | } 1405 | ] 1406 | } 1407 | }, 1408 | { 1409 | name: "program" 1410 | } 1411 | ]; 1412 | args: [ 1413 | { 1414 | name: "initialVirtualTokenReserves"; 1415 | type: "u64" 1416 | }, 1417 | { 1418 | name: "initialVirtualSolReserves"; 1419 | type: "u64" 1420 | }, 1421 | { 1422 | name: "initialRealTokenReserves"; 1423 | type: "u64" 1424 | }, 1425 | { 1426 | name: "tokenTotalSupply"; 1427 | type: "u64" 1428 | }, 1429 | { 1430 | name: "feeBasisPoints"; 1431 | type: "u64" 1432 | }, 1433 | { 1434 | name: "withdrawAuthority"; 1435 | type: "pubkey" 1436 | }, 1437 | { 1438 | name: "enableMigrate"; 1439 | type: "bool" 1440 | }, 1441 | { 1442 | name: "poolMigrationFee"; 1443 | type: "u64" 1444 | }, 1445 | { 1446 | name: "creatorFee"; 1447 | type: "u64" 1448 | } 1449 | ] 1450 | }, 1451 | { 1452 | name: "updateGlobalAuthority"; 1453 | discriminator: [ 1454 | 227, 1455 | 181, 1456 | 74, 1457 | 196, 1458 | 208, 1459 | 21, 1460 | 97, 1461 | 213 1462 | ]; 1463 | accounts: [ 1464 | { 1465 | name: "global"; 1466 | writable: true, 1467 | pda: { 1468 | seeds: [ 1469 | { 1470 | kind: "const"; 1471 | value: [ 1472 | 103, 1473 | 108, 1474 | 111, 1475 | 98, 1476 | 97, 1477 | 108 1478 | ] 1479 | } 1480 | ] 1481 | } 1482 | }, 1483 | { 1484 | name: "authority"; 1485 | signer: true, 1486 | relations: [ 1487 | "global" 1488 | ] 1489 | }, 1490 | { 1491 | name: "newAuthority"; 1492 | signer: true 1493 | }, 1494 | { 1495 | name: "eventAuthority"; 1496 | pda: { 1497 | seeds: [ 1498 | { 1499 | kind: "const"; 1500 | value: [ 1501 | 95, 1502 | 95, 1503 | 101, 1504 | 118, 1505 | 101, 1506 | 110, 1507 | 116, 1508 | 95, 1509 | 97, 1510 | 117, 1511 | 116, 1512 | 104, 1513 | 111, 1514 | 114, 1515 | 105, 1516 | 116, 1517 | 121 1518 | ] 1519 | } 1520 | ] 1521 | } 1522 | }, 1523 | { 1524 | name: "program" 1525 | } 1526 | ]; 1527 | args: [] 1528 | }, 1529 | { 1530 | name: "withdraw"; 1531 | docs: [ 1532 | "Allows the admin to withdraw liquidity for a migration once the bonding curve completes" 1533 | ]; 1534 | discriminator: [ 1535 | 183, 1536 | 18, 1537 | 70, 1538 | 156, 1539 | 148, 1540 | 109, 1541 | 161, 1542 | 34 1543 | ]; 1544 | accounts: [ 1545 | { 1546 | name: "global"; 1547 | pda: { 1548 | seeds: [ 1549 | { 1550 | kind: "const"; 1551 | value: [ 1552 | 103, 1553 | 108, 1554 | 111, 1555 | 98, 1556 | 97, 1557 | 108 1558 | ] 1559 | } 1560 | ] 1561 | } 1562 | }, 1563 | { 1564 | name: "lastWithdraw"; 1565 | writable: true, 1566 | pda: { 1567 | seeds: [ 1568 | { 1569 | kind: "const"; 1570 | value: [ 1571 | 108, 1572 | 97, 1573 | 115, 1574 | 116, 1575 | 45, 1576 | 119, 1577 | 105, 1578 | 116, 1579 | 104, 1580 | 100, 1581 | 114, 1582 | 97, 1583 | 119 1584 | ] 1585 | } 1586 | ] 1587 | } 1588 | }, 1589 | { 1590 | name: "mint" 1591 | }, 1592 | { 1593 | name: "bondingCurve"; 1594 | writable: true, 1595 | pda: { 1596 | seeds: [ 1597 | { 1598 | kind: "const"; 1599 | value: [ 1600 | 98, 1601 | 111, 1602 | 110, 1603 | 100, 1604 | 105, 1605 | 110, 1606 | 103, 1607 | 45, 1608 | 99, 1609 | 117, 1610 | 114, 1611 | 118, 1612 | 101 1613 | ] 1614 | }, 1615 | { 1616 | kind: "account"; 1617 | path: "mint" 1618 | } 1619 | ] 1620 | } 1621 | }, 1622 | { 1623 | name: "associatedBondingCurve"; 1624 | writable: true, 1625 | pda: { 1626 | seeds: [ 1627 | { 1628 | kind: "account"; 1629 | path: "bondingCurve" 1630 | }, 1631 | { 1632 | kind: "const"; 1633 | value: [ 1634 | 6, 1635 | 221, 1636 | 246, 1637 | 225, 1638 | 215, 1639 | 101, 1640 | 161, 1641 | 147, 1642 | 217, 1643 | 203, 1644 | 225, 1645 | 70, 1646 | 206, 1647 | 235, 1648 | 121, 1649 | 172, 1650 | 28, 1651 | 180, 1652 | 133, 1653 | 237, 1654 | 95, 1655 | 91, 1656 | 55, 1657 | 145, 1658 | 58, 1659 | 140, 1660 | 245, 1661 | 133, 1662 | 126, 1663 | 255, 1664 | 0, 1665 | 169 1666 | ] 1667 | }, 1668 | { 1669 | kind: "account"; 1670 | path: "mint" 1671 | } 1672 | ]; 1673 | program: { 1674 | kind: "const"; 1675 | value: [ 1676 | 140, 1677 | 151, 1678 | 37, 1679 | 143, 1680 | 78, 1681 | 36, 1682 | 137, 1683 | 241, 1684 | 187, 1685 | 61, 1686 | 16, 1687 | 41, 1688 | 20, 1689 | 142, 1690 | 13, 1691 | 131, 1692 | 11, 1693 | 90, 1694 | 19, 1695 | 153, 1696 | 218, 1697 | 255, 1698 | 16, 1699 | 132, 1700 | 4, 1701 | 142, 1702 | 123, 1703 | 216, 1704 | 219, 1705 | 233, 1706 | 248, 1707 | 89 1708 | ] 1709 | } 1710 | } 1711 | }, 1712 | { 1713 | name: "associatedUser"; 1714 | writable: true 1715 | }, 1716 | { 1717 | name: "user"; 1718 | writable: true, 1719 | signer: true 1720 | }, 1721 | { 1722 | name: "systemProgram"; 1723 | address: "11111111111111111111111111111111" 1724 | }, 1725 | { 1726 | name: "tokenProgram"; 1727 | address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" 1728 | }, 1729 | { 1730 | name: "rent"; 1731 | docs: [ 1732 | "Unused" 1733 | ]; 1734 | address: "SysvarRent111111111111111111111111111111111" 1735 | }, 1736 | { 1737 | name: "eventAuthority"; 1738 | pda: { 1739 | seeds: [ 1740 | { 1741 | kind: "const"; 1742 | value: [ 1743 | 95, 1744 | 95, 1745 | 101, 1746 | 118, 1747 | 101, 1748 | 110, 1749 | 116, 1750 | 95, 1751 | 97, 1752 | 117, 1753 | 116, 1754 | 104, 1755 | 111, 1756 | 114, 1757 | 105, 1758 | 116, 1759 | 121 1760 | ] 1761 | } 1762 | ] 1763 | } 1764 | }, 1765 | { 1766 | name: "program" 1767 | } 1768 | ]; 1769 | args: [] 1770 | } 1771 | ]; 1772 | accounts: [ 1773 | { 1774 | name: "BondingCurve"; 1775 | discriminator: [ 1776 | 23, 1777 | 183, 1778 | 248, 1779 | 55, 1780 | 96, 1781 | 216, 1782 | 172, 1783 | 96 1784 | ] 1785 | }, 1786 | { 1787 | name: "Global"; 1788 | discriminator: [ 1789 | 167, 1790 | 232, 1791 | 232, 1792 | 177, 1793 | 200, 1794 | 108, 1795 | 114, 1796 | 127 1797 | ] 1798 | }, 1799 | { 1800 | name: "LastWithdraw"; 1801 | discriminator: [ 1802 | 203, 1803 | 18, 1804 | 220, 1805 | 103, 1806 | 120, 1807 | 145, 1808 | 187, 1809 | 2 1810 | ] 1811 | } 1812 | ]; 1813 | events: [ 1814 | { 1815 | name: "CompleteEvent"; 1816 | discriminator: [ 1817 | 95, 1818 | 114, 1819 | 97, 1820 | 156, 1821 | 212, 1822 | 46, 1823 | 152, 1824 | 8 1825 | ] 1826 | }, 1827 | { 1828 | name: "CompletePumpAmmMigrationEvent"; 1829 | discriminator: [ 1830 | 189, 1831 | 233, 1832 | 93, 1833 | 185, 1834 | 92, 1835 | 148, 1836 | 234, 1837 | 148 1838 | ] 1839 | }, 1840 | { 1841 | name: "CreateEvent"; 1842 | discriminator: [ 1843 | 27, 1844 | 114, 1845 | 169, 1846 | 77, 1847 | 222, 1848 | 235, 1849 | 99, 1850 | 118 1851 | ] 1852 | }, 1853 | { 1854 | name: "ExtendAccountEvent"; 1855 | discriminator: [ 1856 | 97, 1857 | 97, 1858 | 215, 1859 | 144, 1860 | 93, 1861 | 146, 1862 | 22, 1863 | 124 1864 | ] 1865 | }, 1866 | { 1867 | name: "SetParamsEvent"; 1868 | discriminator: [ 1869 | 223, 1870 | 195, 1871 | 159, 1872 | 246, 1873 | 62, 1874 | 48, 1875 | 143, 1876 | 131 1877 | ] 1878 | }, 1879 | { 1880 | name: "TradeEvent"; 1881 | discriminator: [ 1882 | 189, 1883 | 219, 1884 | 127, 1885 | 211, 1886 | 78, 1887 | 230, 1888 | 97, 1889 | 238 1890 | ] 1891 | }, 1892 | { 1893 | name: "UpdateGlobalAuthorityEvent"; 1894 | discriminator: [ 1895 | 182, 1896 | 195, 1897 | 137, 1898 | 42, 1899 | 35, 1900 | 206, 1901 | 207, 1902 | 247 1903 | ] 1904 | } 1905 | ]; 1906 | errors: [ 1907 | { 1908 | code: 6000, 1909 | name: "NotAuthorized"; 1910 | msg: "The given account is not authorized to execute this instruction." 1911 | }, 1912 | { 1913 | code: 6001, 1914 | name: "AlreadyInitialized"; 1915 | msg: "The program is already initialized." 1916 | }, 1917 | { 1918 | code: 6002, 1919 | name: "TooMuchSolRequired"; 1920 | msg: "slippage: Too much SOL required to buy the given amount of tokens." 1921 | }, 1922 | { 1923 | code: 6003, 1924 | name: "TooLittleSolReceived"; 1925 | msg: "slippage: Too little SOL received to sell the given amount of tokens." 1926 | }, 1927 | { 1928 | code: 6004, 1929 | name: "MintDoesNotMatchBondingCurve"; 1930 | msg: "The mint does not match the bonding curve." 1931 | }, 1932 | { 1933 | code: 6005, 1934 | name: "BondingCurveComplete"; 1935 | msg: "The bonding curve has completed and liquidity migrated to raydium." 1936 | }, 1937 | { 1938 | code: 6006, 1939 | name: "BondingCurveNotComplete"; 1940 | msg: "The bonding curve has not completed." 1941 | }, 1942 | { 1943 | code: 6007, 1944 | name: "NotInitialized"; 1945 | msg: "The program is not initialized." 1946 | }, 1947 | { 1948 | code: 6008, 1949 | name: "WithdrawTooFrequent"; 1950 | msg: "Withdraw too frequent" 1951 | }, 1952 | { 1953 | code: 6009, 1954 | name: "NewSizeShouldBeGreaterThanCurrentSize"; 1955 | msg: "newSize should be > currentSize" 1956 | }, 1957 | { 1958 | code: 6010, 1959 | name: "AccountTypeNotSupported"; 1960 | msg: "Account type not supported" 1961 | }, 1962 | { 1963 | code: 6011, 1964 | name: "InitialRealTokenReservesShouldBeLessThanTokenTotalSupply"; 1965 | msg: "initialRealTokenReserves should be less than tokenTotalSupply" 1966 | }, 1967 | { 1968 | code: 6012, 1969 | name: "InitialVirtualTokenReservesShouldBeGreaterThanInitialRealTokenReserves"; 1970 | msg: "initialVirtualTokenReserves should be greater than initialRealTokenReserves" 1971 | }, 1972 | { 1973 | code: 6013, 1974 | name: "FeeBasisPointsGreaterThanMaximum"; 1975 | msg: "feeBasisPoints greater than maximum" 1976 | }, 1977 | { 1978 | code: 6014, 1979 | name: "AllZerosWithdrawAuthority"; 1980 | msg: "Withdraw authority cannot be set to System Program ID" 1981 | }, 1982 | { 1983 | code: 6015, 1984 | name: "PoolMigrationFeeShouldBeLessThanFinalRealSolReserves"; 1985 | msg: "poolMigrationFee should be less than finalRealSolReserves" 1986 | }, 1987 | { 1988 | code: 6016, 1989 | name: "PoolMigrationFeeShouldBeGreaterThanCreatorFeePlusMaxMigrateFees"; 1990 | msg: "poolMigrationFee should be greater than creatorFee + MAXMIGRATEFEES" 1991 | }, 1992 | { 1993 | code: 6017, 1994 | name: "DisabledWithdraw"; 1995 | msg: "Migrate instruction is disabled" 1996 | }, 1997 | { 1998 | code: 6018, 1999 | name: "DisabledMigrate"; 2000 | msg: "Migrate instruction is disabled" 2001 | }, 2002 | { 2003 | code: 6019, 2004 | name: "InvalidCreator"; 2005 | msg: "Invalid creator pubkey" 2006 | }, 2007 | { 2008 | code: 6020, 2009 | name: "BuyZeroAmount"; 2010 | msg: "Buy zero amount" 2011 | }, 2012 | { 2013 | code: 6021, 2014 | name: "NotEnoughTokensToBuy"; 2015 | msg: "Not enough tokens to buy" 2016 | }, 2017 | { 2018 | code: 6022, 2019 | name: "SellZeroAmount"; 2020 | msg: "Sell zero amount" 2021 | }, 2022 | { 2023 | code: 6023, 2024 | name: "NotEnoughTokensToSell"; 2025 | msg: "Not enough tokens to sell" 2026 | }, 2027 | { 2028 | code: 6024, 2029 | name: "Overflow"; 2030 | msg: "Overflow" 2031 | }, 2032 | { 2033 | code: 6025, 2034 | name: "Truncation"; 2035 | msg: "Truncation" 2036 | }, 2037 | { 2038 | code: 6026, 2039 | name: "DivisionByZero"; 2040 | msg: "Division by zero" 2041 | }, 2042 | { 2043 | code: 6027, 2044 | name: "NotEnoughRemainingAccounts"; 2045 | msg: "Not enough remaining accounts" 2046 | }, 2047 | { 2048 | code: 6028, 2049 | name: "AllFeeRecipientsShouldBeNonZero"; 2050 | msg: "All fee recipients should be non-zero" 2051 | }, 2052 | { 2053 | code: 6029, 2054 | name: "UnsortedNotUniqueFeeRecipients"; 2055 | msg: "Unsorted or not unique fee recipients" 2056 | }, 2057 | { 2058 | code: 6030, 2059 | name: "CreatorShouldNotBeZero"; 2060 | msg: "Creator should not be zero" 2061 | } 2062 | ]; 2063 | types: [ 2064 | { 2065 | name: "BondingCurve"; 2066 | type: { 2067 | kind: "struct"; 2068 | fields: [ 2069 | { 2070 | name: "virtualTokenReserves"; 2071 | type: "u64" 2072 | }, 2073 | { 2074 | name: "virtualSolReserves"; 2075 | type: "u64" 2076 | }, 2077 | { 2078 | name: "realTokenReserves"; 2079 | type: "u64" 2080 | }, 2081 | { 2082 | name: "realSolReserves"; 2083 | type: "u64" 2084 | }, 2085 | { 2086 | name: "tokenTotalSupply"; 2087 | type: "u64" 2088 | }, 2089 | { 2090 | name: "complete"; 2091 | type: "bool" 2092 | } 2093 | ] 2094 | } 2095 | }, 2096 | { 2097 | name: "CompleteEvent"; 2098 | type: { 2099 | kind: "struct"; 2100 | fields: [ 2101 | { 2102 | name: "user"; 2103 | type: "pubkey" 2104 | }, 2105 | { 2106 | name: "mint"; 2107 | type: "pubkey" 2108 | }, 2109 | { 2110 | name: "bondingCurve"; 2111 | type: "pubkey" 2112 | }, 2113 | { 2114 | name: "timestamp"; 2115 | type: "i64" 2116 | } 2117 | ] 2118 | } 2119 | }, 2120 | { 2121 | name: "CompletePumpAmmMigrationEvent"; 2122 | type: { 2123 | kind: "struct"; 2124 | fields: [ 2125 | { 2126 | name: "user"; 2127 | type: "pubkey" 2128 | }, 2129 | { 2130 | name: "mint"; 2131 | type: "pubkey" 2132 | }, 2133 | { 2134 | name: "creator"; 2135 | type: { 2136 | option: "pubkey" 2137 | } 2138 | }, 2139 | { 2140 | name: "mintAmount"; 2141 | type: "u64" 2142 | }, 2143 | { 2144 | name: "solAmount"; 2145 | type: "u64" 2146 | }, 2147 | { 2148 | name: "poolMigrationFee"; 2149 | type: "u64" 2150 | }, 2151 | { 2152 | name: "creatorFee"; 2153 | type: "u64" 2154 | }, 2155 | { 2156 | name: "bondingCurve"; 2157 | type: "pubkey" 2158 | }, 2159 | { 2160 | name: "timestamp"; 2161 | type: "i64" 2162 | }, 2163 | { 2164 | name: "pool"; 2165 | type: "pubkey" 2166 | } 2167 | ] 2168 | } 2169 | }, 2170 | { 2171 | name: "CreateEvent"; 2172 | type: { 2173 | kind: "struct"; 2174 | fields: [ 2175 | { 2176 | name: "name"; 2177 | type: "string" 2178 | }, 2179 | { 2180 | name: "symbol"; 2181 | type: "string" 2182 | }, 2183 | { 2184 | name: "uri"; 2185 | type: "string" 2186 | }, 2187 | { 2188 | name: "mint"; 2189 | type: "pubkey" 2190 | }, 2191 | { 2192 | name: "bondingCurve"; 2193 | type: "pubkey" 2194 | }, 2195 | { 2196 | name: "user"; 2197 | type: "pubkey" 2198 | }, 2199 | { 2200 | name: "creator"; 2201 | type: "pubkey" 2202 | }, 2203 | { 2204 | name: "timestamp"; 2205 | type: "i64" 2206 | } 2207 | ] 2208 | } 2209 | }, 2210 | { 2211 | name: "ExtendAccountEvent"; 2212 | type: { 2213 | kind: "struct"; 2214 | fields: [ 2215 | { 2216 | name: "account"; 2217 | type: "pubkey" 2218 | }, 2219 | { 2220 | name: "user"; 2221 | type: "pubkey" 2222 | }, 2223 | { 2224 | name: "currentSize"; 2225 | type: "u64" 2226 | }, 2227 | { 2228 | name: "newSize"; 2229 | type: "u64" 2230 | }, 2231 | { 2232 | name: "timestamp"; 2233 | type: "i64" 2234 | } 2235 | ] 2236 | } 2237 | }, 2238 | { 2239 | name: "Global"; 2240 | type: { 2241 | kind: "struct"; 2242 | fields: [ 2243 | { 2244 | name: "initialized"; 2245 | type: "bool" 2246 | }, 2247 | { 2248 | name: "authority"; 2249 | type: "pubkey" 2250 | }, 2251 | { 2252 | name: "feeRecipient"; 2253 | type: "pubkey" 2254 | }, 2255 | { 2256 | name: "initialVirtualTokenReserves"; 2257 | type: "u64" 2258 | }, 2259 | { 2260 | name: "initialVirtualSolReserves"; 2261 | type: "u64" 2262 | }, 2263 | { 2264 | name: "initialRealTokenReserves"; 2265 | type: "u64" 2266 | }, 2267 | { 2268 | name: "tokenTotalSupply"; 2269 | type: "u64" 2270 | }, 2271 | { 2272 | name: "feeBasisPoints"; 2273 | type: "u64" 2274 | }, 2275 | { 2276 | name: "withdrawAuthority"; 2277 | type: "pubkey" 2278 | }, 2279 | { 2280 | name: "enableMigrate"; 2281 | type: "bool" 2282 | }, 2283 | { 2284 | name: "poolMigrationFee"; 2285 | type: "u64" 2286 | }, 2287 | { 2288 | name: "creatorFee"; 2289 | type: "u64" 2290 | }, 2291 | { 2292 | name: "feeRecipients"; 2293 | type: { 2294 | array: [ 2295 | "pubkey", 2296 | 7 2297 | ] 2298 | } 2299 | } 2300 | ] 2301 | } 2302 | }, 2303 | { 2304 | name: "LastWithdraw"; 2305 | type: { 2306 | kind: "struct"; 2307 | fields: [ 2308 | { 2309 | name: "lastWithdrawTimestamp"; 2310 | type: "i64" 2311 | } 2312 | ] 2313 | } 2314 | }, 2315 | { 2316 | name: "SetParamsEvent"; 2317 | type: { 2318 | kind: "struct"; 2319 | fields: [ 2320 | { 2321 | name: "initialVirtualTokenReserves"; 2322 | type: "u64" 2323 | }, 2324 | { 2325 | name: "initialVirtualSolReserves"; 2326 | type: "u64" 2327 | }, 2328 | { 2329 | name: "initialRealTokenReserves"; 2330 | type: "u64" 2331 | }, 2332 | { 2333 | name: "finalRealSolReserves"; 2334 | type: "u64" 2335 | }, 2336 | { 2337 | name: "tokenTotalSupply"; 2338 | type: "u64" 2339 | }, 2340 | { 2341 | name: "feeBasisPoints"; 2342 | type: "u64" 2343 | }, 2344 | { 2345 | name: "withdrawAuthority"; 2346 | type: "pubkey" 2347 | }, 2348 | { 2349 | name: "enableMigrate"; 2350 | type: "bool" 2351 | }, 2352 | { 2353 | name: "poolMigrationFee"; 2354 | type: "u64" 2355 | }, 2356 | { 2357 | name: "creatorFee"; 2358 | type: "u64" 2359 | }, 2360 | { 2361 | name: "feeRecipients"; 2362 | type: { 2363 | array: [ 2364 | "pubkey", 2365 | 8 2366 | ] 2367 | } 2368 | }, 2369 | { 2370 | name: "timestamp"; 2371 | type: "i64" 2372 | } 2373 | ] 2374 | } 2375 | }, 2376 | { 2377 | name: "TradeEvent"; 2378 | type: { 2379 | kind: "struct"; 2380 | fields: [ 2381 | { 2382 | name: "mint"; 2383 | type: "pubkey" 2384 | }, 2385 | { 2386 | name: "solAmount"; 2387 | type: "u64" 2388 | }, 2389 | { 2390 | name: "tokenAmount"; 2391 | type: "u64" 2392 | }, 2393 | { 2394 | name: "isBuy"; 2395 | type: "bool" 2396 | }, 2397 | { 2398 | name: "user"; 2399 | type: "pubkey" 2400 | }, 2401 | { 2402 | name: "timestamp"; 2403 | type: "i64" 2404 | }, 2405 | { 2406 | name: "virtualSolReserves"; 2407 | type: "u64" 2408 | }, 2409 | { 2410 | name: "virtualTokenReserves"; 2411 | type: "u64" 2412 | }, 2413 | { 2414 | name: "realSolReserves"; 2415 | type: "u64" 2416 | }, 2417 | { 2418 | name: "realTokenReserves"; 2419 | type: "u64" 2420 | } 2421 | ] 2422 | } 2423 | }, 2424 | { 2425 | name: "UpdateGlobalAuthorityEvent"; 2426 | type: { 2427 | kind: "struct"; 2428 | fields: [ 2429 | { 2430 | name: "global"; 2431 | type: "pubkey" 2432 | }, 2433 | { 2434 | name: "authority"; 2435 | type: "pubkey" 2436 | }, 2437 | { 2438 | name: "newAuthority"; 2439 | type: "pubkey" 2440 | }, 2441 | { 2442 | name: "timestamp"; 2443 | type: "i64" 2444 | } 2445 | ] 2446 | } 2447 | } 2448 | ] 2449 | } 2450 | --------------------------------------------------------------------------------