├── .env.example ├── .gitignore ├── package.json ├── jupiterService.ts ├── jitoService.ts ├── index.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY="" 2 | RPC_URL="" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | dist 4 | package-lock.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-swap", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node index.ts", 8 | "build": "tsc", 9 | "clean": "tsc --build --clean", 10 | "dev": "tsc && ts-node ./dist/index.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@project-serum/anchor": "^0.26.0", 17 | "@solana/spl-token": "^0.4.12", 18 | "@solana/web3.js": "^1.98.0", 19 | "axios": "^1.7.9", 20 | "bs58": "^6.0.0", 21 | "dotenv": "^16.4.7", 22 | "ts-node": "^10.9.2", 23 | "typescript": "^5.7.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jupiterService.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from "@project-serum/anchor"; 2 | import axios from "axios"; 3 | 4 | // const JUP_API = "https://quote-api.jup.ag/v6"; // Old 5 | const JUP_API = "https://api.jup.ag/swap/v1"; // New 6 | 7 | export const getResponse = async (tokenA: string, tokenB: string, amount: number, slippageBps: number) => { 8 | const response = await axios.get(`${JUP_API}/quote?inputMint=${tokenA}&outputMint=${tokenB}&amount=${amount}&slippageBps=${slippageBps}`); 9 | const quoteResponse = response.data; 10 | return quoteResponse; 11 | // Get the serialized transactions to perform the swap 12 | }; 13 | 14 | 15 | export const getSwapInfo = async (tokenA: string, tokenB: string, amount: number, slippageBps: number) => { 16 | const res = await axios.get(`${JUP_API}/quote?inputMint=${tokenA}&outputMint=${tokenB}&amount=${amount}&slippageBps=${slippageBps}`); 17 | const swapinfo = res.data; 18 | return swapinfo; 19 | // Get the serialized transactions to perform the swap 20 | }; 21 | 22 | export const getSwapTransaction = async (quoteResponse: any, anchorWallet: Wallet) => { 23 | const swapResponse = await axios.post(`${JUP_API}/swap`, { 24 | // quoteResponse from /quote api 25 | quoteResponse, 26 | // user public key to be used for the swap 27 | userPublicKey: anchorWallet.publicKey.toString(), 28 | // auto wrap and unwrap SOL. default is true 29 | wrapAndUnwrapSol: true, 30 | // dynamicComputeUnitLimit: true, // allow dynamic compute limit instead of max 1,400,000 31 | prioritizationFeeLamports: 200000, // or custom lamports: 1000 32 | // dynamicSlippage: { maxBps: 300 }, 33 | // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API. 34 | // feeAccount: "fee_account_public_key" 35 | }); 36 | return swapResponse.data.swapTransaction; 37 | // console.log("quoteResponse", quoteResponse); 38 | // Get the serialized transactions to perform the swap 39 | }; 40 | -------------------------------------------------------------------------------- /jitoService.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Connection, Keypair, VersionedTransaction, SystemProgram, TransactionMessage } from "@solana/web3.js"; 2 | import axios from "axios"; 3 | import bs58 from "bs58"; 4 | 5 | const TIP_ACCOUNTS = [ 6 | "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5", 7 | "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe", 8 | "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY", 9 | "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49", 10 | "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh", 11 | "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt", 12 | "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL", 13 | "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT", 14 | ].map((pubkey) => new PublicKey(pubkey)); 15 | 16 | const BLOCK_ENGINE_URL = "https://frankfurt.mainnet.block-engine.jito.wtf"; 17 | 18 | export async function sendBundle( 19 | connection: Connection, 20 | jitoFeeWallet: Keypair, 21 | signedTransaction: VersionedTransaction 22 | ) { 23 | try { 24 | const { blockhash } = await connection.getLatestBlockhash("finalized"); 25 | const tipAccount = 26 | TIP_ACCOUNTS[Math.floor(Math.random() * TIP_ACCOUNTS.length)]; 27 | 28 | const instruction1 = SystemProgram.transfer({ 29 | fromPubkey: jitoFeeWallet.publicKey, 30 | toPubkey: tipAccount, 31 | lamports: 100000, 32 | }); 33 | 34 | const messageV0 = new TransactionMessage({ 35 | payerKey: jitoFeeWallet.publicKey, 36 | instructions: [instruction1], 37 | recentBlockhash: blockhash, 38 | }).compileToV0Message(); 39 | 40 | const vTxn = new VersionedTransaction(messageV0); 41 | const signatures = [signedTransaction, vTxn].map((signedTx) => { 42 | return bs58.encode(signedTx.signatures[0]); 43 | }); 44 | // console.log("bundle signatures", signatures);🚩 45 | vTxn.sign([jitoFeeWallet]); 46 | 47 | const encodedTx = [signedTransaction, vTxn].map((tx) => 48 | bs58.encode(tx.serialize()) 49 | ); 50 | 51 | // const encodedTx = txn.map((tx) => bs58.default.encode(txn1.serialize())); 52 | const jitoURL = `${BLOCK_ENGINE_URL}/api/v1/bundles`; // ?uuid=${JITO_UUID} 53 | const payload = { 54 | jsonrpc: "2.0", 55 | id: 1, 56 | method: "sendBundle", 57 | // params: [[bs58.default.encode(vTxn.serialize())]], 58 | params: [encodedTx], 59 | }; 60 | // console.log('payload', payload)🚩 61 | try { 62 | const response = await axios.post(jitoURL, payload, { 63 | headers: { "Content-Type": "application/json" }, 64 | // httpsAgent: httpsAgent 65 | }); 66 | // return response.data.result; 67 | return signatures[0]; 68 | } catch (error) { 69 | console.error("cannot send!:", error); 70 | return null; 71 | } 72 | } catch (error: any) { 73 | const err = error; 74 | console.error("Error sending bundle:", err.message); 75 | 76 | if (err?.message?.includes("Bundle Dropped, no connected leader up soon")) { 77 | console.error( 78 | "Error sending bundle: Bundle Dropped, no connected leader up soon." 79 | ); 80 | } else { 81 | console.error("An unexpected error occurred:", err.message); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | PublicKey, 5 | LAMPORTS_PER_SOL, 6 | VersionedTransaction, 7 | } from "@solana/web3.js"; 8 | import { NATIVE_MINT } from "@solana/spl-token"; 9 | import bs58 from "bs58"; 10 | import dotenv from "dotenv"; 11 | import { Wallet } from "@project-serum/anchor"; 12 | import { getSwapInfo, getSwapTransaction } from "./jupiterService"; 13 | import { sendBundle } from "./jitoService"; 14 | 15 | dotenv.config(); 16 | 17 | const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY as string; 18 | const RPC_URL = process.env.RPC_URL as string; 19 | 20 | const wallet = Keypair.fromSecretKey(bs58.decode(WALLET_PRIVATE_KEY)); 21 | const anchorWallet = new Wallet(wallet); 22 | 23 | async function SplTokenSwap( 24 | tokenAddress: string, 25 | amount: number, 26 | isBuySell: string 27 | ) { 28 | const connection = new Connection(RPC_URL, "confirmed"); 29 | const tokenDecimals = await getTokenDecimals(connection, tokenAddress); 30 | const slippageBps = parseInt(process.env.SLIPPAGEBPS || "") || 50; 31 | let swapInfo: any; 32 | if (isBuySell == "buy") { 33 | swapInfo = await getSwapInfo( 34 | NATIVE_MINT.toBase58(), 35 | tokenAddress, 36 | amount * LAMPORTS_PER_SOL, 37 | slippageBps 38 | ); 39 | } else { 40 | // sell 41 | swapInfo = await getSwapInfo( 42 | tokenAddress, 43 | NATIVE_MINT.toBase58(), 44 | amount * Math.pow(10, tokenDecimals), 45 | slippageBps 46 | ); 47 | } 48 | const swapTransaction = await getSwapTransaction(swapInfo, anchorWallet); 49 | const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); 50 | const latestBlockHash = await connection.getLatestBlockhash(); 51 | const versionedTransaction = 52 | VersionedTransaction.deserialize(swapTransactionBuf); 53 | versionedTransaction.message.recentBlockhash = latestBlockHash.blockhash; 54 | versionedTransaction.sign([anchorWallet.payer]); 55 | 56 | const bundleResult = await sendBundle( 57 | connection, 58 | wallet, 59 | versionedTransaction 60 | ); 61 | // console.log( 62 | // "Jito Bundle: ", 63 | // `https://explorer.jito.wtf/bundle/${bundleResult}` 64 | // ); 65 | console.log( 66 | "Transaction Signature: ", 67 | `https://solscan.io/tx/${bundleResult}` 68 | ); 69 | 70 | // ############# Without Bundle ############# 71 | // try { 72 | // const transactionSignature = await connection.sendTransaction( 73 | // versionedTransaction, 74 | // { maxRetries: 20 } 75 | // ); 76 | // const confirmation = await connection.confirmTransaction( 77 | // { 78 | // signature: transactionSignature, 79 | // blockhash: latestBlockHash.blockhash, 80 | // lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, 81 | // }, 82 | // "confirmed" 83 | // ); 84 | 85 | // if (confirmation.value.err) { 86 | // throw new Error("Transaction not confirmed"); 87 | // } 88 | 89 | // console.log( 90 | // "Transaction Signature: ", 91 | // `https://solscan.io/tx/${transactionSignature}` 92 | // ); 93 | // } catch (error) { 94 | // console.error("Error occurred during swap:", error); 95 | // } 96 | } 97 | 98 | async function getTokenDecimals(connection: Connection, tokenAddress: string) { 99 | const mintInfo = await connection.getParsedAccountInfo( 100 | new PublicKey(tokenAddress) 101 | ); 102 | 103 | if (!mintInfo) { 104 | throw new Error("Token account not found"); 105 | } 106 | 107 | const decimals = (mintInfo.value?.data as any).parsed.info.decimals; 108 | return decimals; 109 | } 110 | 111 | const TOKEN_ADDRESS = "4aR3jtFKWuYzkNE27WG4V7Jt6DDhwKcc2qjzN5Tkpump"; 112 | // const TOKEN_ADDRESS = "FXnZdiH6zrW33tZ9CLsm81xrSeS7m53V7ZtgFsyhpump"; 113 | let amount: number; 114 | 115 | amount = 0.0001; 116 | SplTokenSwap(TOKEN_ADDRESS, amount, "buy"); // Buy token 117 | 118 | amount = 1000; 119 | SplTokenSwap(TOKEN_ADDRESS, amount, "sell"); // Sell token 120 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ 40 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 41 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 42 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 43 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 44 | // "resolveJsonModule": true, /* Enable importing .json files. */ 45 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 46 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 47 | 48 | /* JavaScript Support */ 49 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 50 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 51 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 52 | 53 | /* Emit */ 54 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 55 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 56 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 57 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "noEmit": true, /* Disable emitting files from a compilation. */ 60 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 61 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 62 | // "removeComments": true, /* Disable emitting comments. */ 63 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 64 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 65 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 80 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 81 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 82 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 83 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 84 | 85 | /* Type Checking */ 86 | "strict": true, /* Enable all strict type-checking options. */ 87 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 88 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 89 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 90 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 91 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 92 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 93 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 94 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 95 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 96 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 97 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 98 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 99 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 100 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 101 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 102 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 103 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 104 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 105 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 106 | 107 | /* Completeness */ 108 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 109 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 110 | } 111 | } 112 | --------------------------------------------------------------------------------