├── package.json ├── src ├── index.ts └── jito-kit │ ├── bundle.ts │ ├── buy_token.ts │ ├── create_open_market.ts │ ├── create_pool.ts │ ├── create_token.ts │ ├── global.ts │ ├── index.ts │ ├── pool_manager.ts │ ├── sell_token.ts │ ├── transaction-helper │ ├── check_transaction.ts │ └── transaction.ts │ └── utility.ts └── tsconfig.json /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "platform", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "ts-node-dev --exit-child src/index.ts", 8 | "start": "ts-node src/index.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@metaplex-foundation/js": "^0.20.1", 15 | "@project-serum/serum": "^0.13.65", 16 | "@raydium-io/raydium-sdk": "^1.3.1-beta.45", 17 | "@solana-developers/node-helpers": "^1.2.2", 18 | "@solana/spl-token": "^0.3.9", 19 | "@solana/spl-token-registry": "^0.2.4574", 20 | "@solana/web3.js": "^1.91.7", 21 | "bignumber.js": "^9.1.2", 22 | "bn.js": "^5.2.1", 23 | "bs58": "^5.0.0", 24 | "dotenv": "^16.0.3", 25 | "jito-ts": "^3.0.1", 26 | "loglevel": "^1.9.1", 27 | "nodemon": "^3.0.1" 28 | }, 29 | "devDependencies": { 30 | "@types/bn.js": "5.1.5", 31 | "@types/debug": "^4.1.12", 32 | "@types/dotenv": "^8.2.0", 33 | "@types/node": "^20.12.7", 34 | "@types/node-fetch": "^2.6.11", 35 | "@typescript-eslint/eslint-plugin": "^6.12.0", 36 | "@typescript-eslint/parser": "^6.12.0", 37 | "rewire": "^6.0.0", 38 | "ts-jest": "^29.0.3", 39 | "ts-node": "^10.9.2", 40 | "ts-node-dev": "^2.0.0", 41 | "typescript": "^5.4.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | PublicKey, 5 | Transaction, 6 | VersionedTransaction, 7 | clusterApiUrl, 8 | } from "@solana/web3.js"; 9 | import dotenv from "dotenv"; 10 | import { Market, MARKET_STATE_LAYOUT_V3 } from "@project-serum/serum"; 11 | import { getKeypairFromEnvironment } from "@solana-developers/node-helpers"; 12 | import { BN } from "bn.js"; 13 | import { createInitializeAccountInstruction, getMint } from "@solana/spl-token"; 14 | import { 15 | CurrencyAmount, 16 | Liquidity, 17 | LiquidityPoolInfo, 18 | Percent, 19 | TOKEN_PROGRAM_ID, 20 | Token, 21 | findProgramAddress, 22 | TokenAmount, 23 | } from "@raydium-io/raydium-sdk"; 24 | import { 25 | BUNDLE_TRANSACTION, 26 | EnvironmentManager, 27 | NETWORK_MODE, 28 | PoolManager, 29 | SPL_ERROR, 30 | buyToken, 31 | createAndSendBundleTransaction, 32 | createOpenBookMarket, 33 | createPool, 34 | createToken, 35 | getAvailablePoolKeyAndPoolInfo, 36 | getConnection, 37 | sellToken, 38 | sendAndConfirmTransactionWithCheck, 39 | sendAndConfirmTransactionsWithCheck, 40 | signTransactions, 41 | sleep, 42 | } from "./jito-kit"; 43 | const log = require('loglevel'); 44 | 45 | log.warn = () => {}; 46 | 47 | dotenv.config(); 48 | 49 | let connection: Connection; 50 | 51 | const initializeVariables = () => { 52 | const dev_net = 53 | process.env.DEVNET === "true" 54 | ? NETWORK_MODE.NETWORK_DEV 55 | : NETWORK_MODE.NETWORK_MAIN; 56 | EnvironmentManager.setNetworkMode(dev_net); 57 | EnvironmentManager.setNetUrls( 58 | process.env.MAIN_NET_URL!, 59 | clusterApiUrl("devnet"), 60 | clusterApiUrl("testnet") 61 | ); 62 | EnvironmentManager.setQuoteTokenInfo({ 63 | address: "So11111111111111111111111111111111111111112", 64 | decimal: 9, 65 | name: "WSOL", 66 | symbol: "WSOL", 67 | }); 68 | EnvironmentManager.setJitoKeypair(getKeypairFromEnvironment("JITO_AUTH_KEY")); 69 | 70 | connection = getConnection("confirmed"); 71 | }; 72 | 73 | const doCreateToken = async () => { 74 | const token_name = process.env.TOKEN_NAME!; 75 | const token_symbol = process.env.TOKEN_SYMBOL!; 76 | const token_decimal = Number(process.env.TOKEN_DECIMAL); 77 | const token_supply = Number(process.env.TOKEN_TOTAL_MINT); 78 | const token_description = process.env.TOKEN_DESCRIPTION; 79 | const token_logo_path = process.env.TOKEN_LOGO!; 80 | const token_owner = getKeypairFromEnvironment("OWNER_PRIVATE"); 81 | 82 | const create_result = await createToken( 83 | connection, 84 | token_owner, 85 | token_name, 86 | token_symbol, 87 | token_decimal, 88 | token_supply, 89 | token_logo_path, 90 | token_description 91 | ); 92 | 93 | if (create_result.result !== SPL_ERROR.E_OK) { 94 | throw "Error: create token failed with some reason"; 95 | } 96 | 97 | // process.env.MINT_ADDRESS = create_result.value; 98 | }; 99 | 100 | const doCreateOpenMarket = async () => { 101 | const token_owner = getKeypairFromEnvironment("OWNER_PRIVATE"); 102 | 103 | const create_result = await createOpenBookMarket( 104 | connection, 105 | token_owner, 106 | process.env.MINT_ADDRESS! 107 | ); 108 | 109 | if (create_result != SPL_ERROR.E_OK) { 110 | throw "Error: create open book market failed because of some reason"; 111 | } 112 | }; 113 | 114 | const doCreatePool = async (sell_option: boolean) => { 115 | const token_owner = getKeypairFromEnvironment("OWNER_PRIVATE"); 116 | const token_address = process.env.MINT_ADDRESS!; 117 | const lp_token_amount = Number(process.env.LP_TOKEN_AMOUNT); 118 | const lp_sol_amount = Number(process.env.LP_SOL_AMOUNT); 119 | const quote_info = EnvironmentManager.getQuoteTokenInfo(); 120 | const first_token_amount = Number(process.env.BUYAMOUNT1); 121 | const second_token_amount = Number(process.env.BUYAMOUNT2); 122 | const token_decimal = Number(process.env.MINT_DECIMAL); 123 | const bundle_transaction: BUNDLE_TRANSACTION[] = []; 124 | 125 | const create_result = await createPool( 126 | connection, 127 | token_owner, 128 | token_address, 129 | lp_token_amount, 130 | lp_sol_amount 131 | ); 132 | 133 | if (create_result.result !== SPL_ERROR.E_OK) { 134 | throw "Error: making create pool transaction failed with some reason"; 135 | } 136 | 137 | if ( 138 | typeof create_result.value !== "string" && 139 | Array.isArray(create_result.value) 140 | ) { 141 | // signTransactions(token_owner, create_result.value); 142 | // bundle_transaction.push(create_result.value[0] as VersionedTransaction); 143 | bundle_transaction.push({ 144 | txn: create_result.value[0] as VersionedTransaction, 145 | signer: token_owner, 146 | }); 147 | } 148 | 149 | const accounts = await Market.findAccountsByMints( 150 | connection, 151 | new PublicKey(token_address), 152 | new PublicKey(quote_info.address), 153 | EnvironmentManager.getProgramID().OPENBOOK_MARKET 154 | ); 155 | 156 | if (accounts.length <= 0) { 157 | throw "Error: can not find the market"; 158 | } 159 | 160 | const market_id = accounts[0].publicKey; 161 | const marketInfo = MARKET_STATE_LAYOUT_V3.decode( 162 | accounts[0].accountInfo.data 163 | ); 164 | 165 | const poolKeys: any = Liquidity.getAssociatedPoolKeys({ 166 | version: 4, 167 | marketVersion: 3, 168 | baseMint: new PublicKey(token_address), 169 | quoteMint: new PublicKey(quote_info.address), 170 | baseDecimals: token_decimal, 171 | quoteDecimals: quote_info.decimal, 172 | marketId: market_id, 173 | programId: EnvironmentManager.getProgramID().AmmV4, 174 | marketProgramId: EnvironmentManager.getProgramID().OPENBOOK_MARKET, 175 | }); 176 | poolKeys.marketBaseVault = marketInfo.baseVault; 177 | poolKeys.marketQuoteVault = marketInfo.quoteVault; 178 | poolKeys.marketBids = marketInfo.bids; 179 | poolKeys.marketAsks = marketInfo.asks; 180 | poolKeys.marketEventQueue = marketInfo.eventQueue; 181 | 182 | const pool_mng = new PoolManager( 183 | { 184 | address: token_address, 185 | decimal: Number(process.env.MINT_DECIMAL), 186 | name: "", 187 | symbol: "", 188 | }, 189 | quote_info, 190 | lp_token_amount, 191 | lp_sol_amount, 192 | market_id 193 | ); 194 | 195 | for (let i = 1; i <= 1; i++) { 196 | const buyer_keypair_str = process.env[`BUYORSELLER${i}`]!; 197 | const buy_token_amount_str = process.env[`BUYAMOUNT${i}`]!; 198 | if (buy_token_amount_str.length <= 0 || buyer_keypair_str.length <= 0) { 199 | break; 200 | } 201 | const buy_token_amount = 202 | buy_token_amount_str.length > 0 203 | ? Number(process.env[`BUYAMOUNT${i}`]) 204 | : 0; 205 | const buyer_keypair = getKeypairFromEnvironment(`BUYORSELLER${i}`); 206 | if ( 207 | buyer_keypair.publicKey.toBase58().length <= 0 || 208 | buy_token_amount <= 0 209 | ) { 210 | break; 211 | } 212 | 213 | const buy_sol_amount = pool_mng.computeSolAmount(buy_token_amount, true); 214 | console.log( 215 | "Simulated Price: ", 216 | pool_mng.computeCurrentPrice().toFixed(10), 217 | "Simulated Sol Amount: ", 218 | buy_sol_amount.toSignificant() 219 | ); 220 | 221 | const buy_result = await buyToken( 222 | connection, 223 | buyer_keypair, 224 | token_address, 225 | buy_token_amount, 226 | Number(buy_sol_amount.toSignificant()), 227 | poolKeys 228 | ); 229 | 230 | if (buy_result.result !== SPL_ERROR.E_OK) { 231 | throw `Error: failed to create ${i} buy token transaction`; 232 | } 233 | 234 | if ( 235 | typeof buy_result.value !== "string" && 236 | Array.isArray(buy_result.value) 237 | ) { 238 | // signTransactions(buyer_keypair, buy_result.value); 239 | // bundle_transaction.push(buy_result.value[0] as VersionedTransaction); 240 | bundle_transaction.push({ 241 | txn: buy_result.value[0] as VersionedTransaction, 242 | signer: buyer_keypair, 243 | }); 244 | } 245 | 246 | pool_mng.buyToken(buy_token_amount); 247 | } 248 | 249 | console.log("=========== create bundle transaction"); 250 | const bundle_result = await createAndSendBundleTransaction( 251 | connection, 252 | Number(process.env.JITO_BUNDLE_TIP), 253 | bundle_transaction, 254 | token_owner 255 | ); 256 | 257 | if (bundle_result !== true) { 258 | throw "Error: there's error in bundle transaction"; 259 | } 260 | 261 | if (sell_option) { 262 | await doSellFunction(poolKeys); 263 | } 264 | 265 | console.log("All done perfectly"); 266 | }; 267 | 268 | const doBundleTest = async () => { 269 | const token_address = process.env.MINT_ADDRESS!; 270 | const token_mint = new PublicKey(token_address); 271 | const mint_info = await getMint(connection, token_mint); 272 | const quote_info = EnvironmentManager.getQuoteTokenInfo(); 273 | const first_buyer = getKeypairFromEnvironment("BUYORSELLER1"); 274 | const second_buyer = getKeypairFromEnvironment("BUYORSELLER2"); 275 | const first_amount = 0.0001; 276 | const second_amount = 0.0001; 277 | const accounts = await Market.findAccountsByMints( 278 | connection, 279 | token_mint, 280 | new PublicKey(EnvironmentManager.getQuoteTokenInfo().address), 281 | EnvironmentManager.getProgramID().OPENBOOK_MARKET 282 | ); 283 | 284 | if (accounts.length <= 0) { 285 | throw "Error: Market not found"; 286 | } 287 | 288 | const { poolKeys: pool_keys, poolInfo: pool_info } = 289 | await getAvailablePoolKeyAndPoolInfo( 290 | connection, 291 | new Token(TOKEN_PROGRAM_ID, token_address, mint_info.decimals), 292 | new Token( 293 | TOKEN_PROGRAM_ID, 294 | quote_info.address, 295 | quote_info.decimal, 296 | quote_info.symbol, 297 | quote_info.name 298 | ), 299 | accounts 300 | ); 301 | 302 | const buy_token1 = await buyToken( 303 | connection, 304 | first_buyer, 305 | token_address, 306 | 1, 307 | first_amount, 308 | pool_keys 309 | ); 310 | if (buy_token1.result !== SPL_ERROR.E_OK) { 311 | throw "Error: first buy token transaction made failed"; 312 | } 313 | const buy_token2 = await buyToken( 314 | connection, 315 | second_buyer, 316 | token_address, 317 | 1, 318 | second_amount, 319 | pool_keys 320 | ); 321 | if (buy_token2.result !== SPL_ERROR.E_OK) { 322 | throw "Error: second buy token transaction made failed"; 323 | } 324 | const bundle_transaction: BUNDLE_TRANSACTION[] = []; 325 | if (typeof buy_token1.value !== "string" && Array.isArray(buy_token1.value)) { 326 | // signTransactions(first_buyer, buy_token1.value); 327 | bundle_transaction.push({ 328 | txn: buy_token1.value[0] as VersionedTransaction, 329 | signer: first_buyer, 330 | }); 331 | } 332 | if (typeof buy_token2.value !== "string" && Array.isArray(buy_token2.value)) { 333 | signTransactions(second_buyer, buy_token2.value); 334 | bundle_transaction.push({ 335 | txn: buy_token2.value[0] as VersionedTransaction, 336 | signer: second_buyer, 337 | }); 338 | } 339 | 340 | const bundle_result = await createAndSendBundleTransaction( 341 | connection, 342 | Number(process.env.JITO_BUNDLE_TIP), 343 | bundle_transaction, 344 | first_buyer 345 | ); 346 | 347 | if (bundle_result !== true) { 348 | throw "Error: Failed to send bundle"; 349 | } 350 | }; 351 | 352 | function getATAAddress( 353 | programId: PublicKey, 354 | owner: PublicKey, 355 | mint: PublicKey 356 | ) { 357 | const { publicKey, nonce } = findProgramAddress( 358 | [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], 359 | new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") 360 | ); 361 | return { publicKey, nonce }; 362 | } 363 | 364 | function percentAmount(amount: string, percent: number): string { 365 | const inputNum = BigInt(amount); // Convert string to BigInt 366 | const result = inputNum * BigInt(percent * 100); // Multiply by percent 367 | return (result / BigInt(100)).toString(); // Round down to the nearest integer 368 | } 369 | 370 | const doSellFunction = async (pool_key: any) => { 371 | const sell_time = process.env.SELL_TIME ? Number(process.env.SELL_TIME) : 0; 372 | const token_address = process.env.MINT_ADDRESS!; 373 | const token_decimal = Number(process.env.MINT_DECIMAL!); 374 | const token_mint = new PublicKey(token_address); 375 | const mint_info = await getMint(connection, token_mint); 376 | const fee_payer = process.env.SELLER_WALLET1 377 | ? getKeypairFromEnvironment(`SELLER_WALLET1`) 378 | : undefined; 379 | 380 | if (fee_payer === undefined) { 381 | throw "Please input fee payer"; 382 | } 383 | 384 | if (sell_time) { 385 | console.log("SELL_OPTION_SLEEPING: ", sell_time + "ms"); 386 | sleep(sell_time); 387 | } 388 | 389 | const bundle_transaction: BUNDLE_TRANSACTION[] = []; 390 | for (let i = 1; i < 2; i++) { 391 | const seller_wallet = process.env[`SELLER_WALLET${i}`] 392 | ? getKeypairFromEnvironment(`SELLER_WALLET${i}`) 393 | : undefined; 394 | const sell_amount_percentage = process.env[`SELL_AMOUNT${i}`] 395 | ? Number(process.env[`SELL_AMOUNT${i}`]) 396 | : 0; 397 | 398 | if (seller_wallet === undefined || sell_amount_percentage === 0) { 399 | break; 400 | } 401 | 402 | const sell_token_account = getATAAddress( 403 | TOKEN_PROGRAM_ID, 404 | seller_wallet?.publicKey!, 405 | token_mint 406 | ); 407 | 408 | const total_token_amount = await connection.getTokenAccountBalance( 409 | sell_token_account.publicKey 410 | ); 411 | 412 | const actual_sell_amount = new TokenAmount( 413 | new Token(TOKEN_PROGRAM_ID, token_address, token_decimal), 414 | percentAmount(total_token_amount.value.amount, sell_amount_percentage) 415 | ); 416 | 417 | console.log("Sell Amount: ", actual_sell_amount.toSignificant()); 418 | 419 | const sell_res = await sellToken( 420 | connection, 421 | seller_wallet!, 422 | token_address, 423 | Number(actual_sell_amount.toSignificant()), 424 | 0, 425 | pool_key 426 | ); 427 | 428 | if (sell_res.result !== SPL_ERROR.E_OK) { 429 | throw "Error: Sell token transaction make failed"; 430 | } 431 | 432 | if (typeof sell_res.value !== "string" && Array.isArray(sell_res.value)) { 433 | bundle_transaction.push({ 434 | signer: seller_wallet!, 435 | txn: sell_res.value[0] as VersionedTransaction, 436 | }); 437 | } 438 | } 439 | 440 | if (bundle_transaction.length >= 1) { 441 | const bundle_result = await createAndSendBundleTransaction( 442 | connection, 443 | Number(process.env.JITO_BUNDLE_TIP), 444 | bundle_transaction, 445 | fee_payer! 446 | ); 447 | if (bundle_result !== true) { 448 | throw "Sell bundle failed"; 449 | } 450 | } else { 451 | console.log( 452 | "SellToken: there's no parameter for selling check the parameter" 453 | ); 454 | } 455 | }; 456 | 457 | (async () => { 458 | initializeVariables(); 459 | try { 460 | const execute_creatToken = process.env.CREATE_TOKEN === "true"; 461 | const execute_creatOpenMarket = 462 | process.env.CREATE_OPEN_BOOK_MARKET === "true"; 463 | const execute_createPool = process.env.CREATE_POOL === "true"; 464 | const execute_time_sell = process.env.SET_SELL_TIME === "true"; 465 | 466 | console.log( 467 | "Execution Condition: ", 468 | execute_creatToken, 469 | execute_creatOpenMarket, 470 | execute_createPool 471 | ); 472 | 473 | if (execute_creatToken) { 474 | await doCreateToken(); 475 | } 476 | 477 | if (execute_creatOpenMarket) { 478 | await doCreateOpenMarket(); 479 | } 480 | 481 | if (execute_createPool) { 482 | await doCreatePool(execute_time_sell); 483 | } 484 | 485 | // await doBundleTest(); 486 | console.log("Finished"); 487 | } catch (error) { 488 | console.error(error); 489 | console.log("Finished"); 490 | } 491 | })(); 492 | -------------------------------------------------------------------------------- /src/jito-kit/bundle.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | PublicKey, 5 | LAMPORTS_PER_SOL, 6 | VersionedTransaction, 7 | } from "@solana/web3.js"; 8 | import { searcherClient } from "jito-ts/dist/sdk/block-engine/searcher"; 9 | import { BUNDLE_TRANSACTION, EnvironmentManager, SPL_ERROR } from "./global"; 10 | import { Bundle } from "jito-ts/dist/sdk/block-engine/types"; 11 | import * as utils from "./utility"; 12 | import { signTransaction } from "./transaction-helper/transaction"; 13 | import base58 from "bs58"; 14 | 15 | export const createAndSendBundleTransaction = async ( 16 | connection: Connection, 17 | fee: number, 18 | bundleTransactions: any, 19 | payer: Keypair 20 | ) => { 21 | const seacher = searcherClient( 22 | EnvironmentManager.getJitoBlockEngine(), 23 | EnvironmentManager.getJitoKeypair() 24 | ); 25 | const _tipAccount = (await seacher.getTipAccounts())[0]; 26 | const tipAccount = new PublicKey(_tipAccount); 27 | 28 | let transactionsConfirmResult: boolean = false; 29 | let breakCheckTransactionStatus: boolean = false; 30 | try { 31 | const recentBlockhash = (await connection.getLatestBlockhash("finalized")) 32 | .blockhash; 33 | 34 | const bundleTransaction: VersionedTransaction[] = []; 35 | 36 | for (let i = 0; i < bundleTransactions.length; i++) { 37 | bundleTransactions[i].txn.message.recentBlockhash = recentBlockhash; 38 | signTransaction(bundleTransactions[i].signer, bundleTransactions[i].txn); 39 | bundleTransaction.push(bundleTransactions[i].txn); 40 | } 41 | 42 | let bundleTx = new Bundle(bundleTransaction, 5); 43 | bundleTx.addTipTx(payer, fee, tipAccount, recentBlockhash); 44 | 45 | seacher.onBundleResult( 46 | async (bundleResult: any) => { 47 | console.log(bundleResult); 48 | if (bundleResult.rejected) { 49 | try { 50 | if ( 51 | bundleResult.rejected.simulationFailure.msg.includes( 52 | "custom program error" 53 | ) || 54 | bundleResult.rejected.simulationFailure.msg.includes( 55 | "Error processing Instruction" 56 | ) 57 | ) { 58 | breakCheckTransactionStatus = true; 59 | } else if ( 60 | bundleResult.rejected.simulationFailure.msg.includes( 61 | "This transaction has already been processed" 62 | ) || 63 | bundleResult.rejected.droppedBundle.msg.includes( 64 | "Bundle partially processed" 65 | ) 66 | ) { 67 | transactionsConfirmResult = true; 68 | breakCheckTransactionStatus = true; 69 | } 70 | } catch (error) {} 71 | } 72 | }, 73 | (error) => { 74 | console.log("Bundle error:", error); 75 | breakCheckTransactionStatus = true; 76 | } 77 | ); 78 | await seacher.sendBundle(bundleTx); 79 | setTimeout(() => { 80 | breakCheckTransactionStatus = true; 81 | }, 20000); 82 | const trxHash = base58.encode( 83 | bundleTransaction[bundleTransaction.length - 1].signatures[0] 84 | ); 85 | while (!breakCheckTransactionStatus) { 86 | await utils.sleep(2000); 87 | try { 88 | const result = await connection.getSignatureStatus(trxHash, { 89 | searchTransactionHistory: true, 90 | }); 91 | if (result && result.value && result.value.confirmationStatus) { 92 | transactionsConfirmResult = true; 93 | breakCheckTransactionStatus = true; 94 | } 95 | } catch (error) { 96 | transactionsConfirmResult = false; 97 | breakCheckTransactionStatus = true; 98 | } 99 | } 100 | return transactionsConfirmResult; 101 | } catch (error) { 102 | console.error("Creating and sending bundle failed...", error); 103 | await utils.sleep(10000); 104 | return false; 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /src/jito-kit/buy_token.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from "@solana/web3.js"; 2 | import { EnvironmentManager, SPL_ERROR, TX_RET } from "./global"; 3 | import { TOKEN_PROGRAM_ID, getMint } from "@solana/spl-token"; 4 | import { 5 | Liquidity, 6 | LiquidityPoolKeys, 7 | Token, 8 | TokenAmount, 9 | TxVersion, 10 | buildSimpleTransaction, 11 | } from "@raydium-io/raydium-sdk"; 12 | import { getWalletAccounts } from "./utility"; 13 | 14 | export const buyToken = async ( 15 | connection: Connection, 16 | buyer: Keypair, 17 | token_address: string, 18 | base_amount: number, 19 | quote_amount: number, 20 | pool_key: LiquidityPoolKeys 21 | ): Promise => { 22 | if (token_address.length <= 0 || base_amount <= 0) { 23 | console.error("Error: [Buy Token] invalid argument iput!!!"); 24 | return { result: SPL_ERROR.E_INVALID_ARGUE, value: undefined }; 25 | } 26 | 27 | try { 28 | const token_mint = new PublicKey(token_address); 29 | const token_info = await getMint(connection, token_mint); 30 | const base_token = new Token( 31 | TOKEN_PROGRAM_ID, 32 | token_address, 33 | token_info.decimals 34 | ); 35 | const quote_info = EnvironmentManager.getQuoteTokenInfo(); 36 | const quote_token = new Token( 37 | TOKEN_PROGRAM_ID, 38 | quote_info.address, 39 | quote_info.decimal, 40 | quote_info.symbol, 41 | quote_info.name 42 | ); 43 | const base_token_amount = new TokenAmount( 44 | base_token, 45 | base_amount * 0.95, 46 | false 47 | ); 48 | const quote_token_amount = new TokenAmount( 49 | quote_token, 50 | quote_amount, 51 | false 52 | ); 53 | 54 | const wallet_token_accounts = await getWalletAccounts( 55 | connection, 56 | buyer.publicKey 57 | ); 58 | 59 | const { innerTransactions } = await Liquidity.makeSwapInstructionSimple({ 60 | connection: connection, 61 | poolKeys: pool_key, 62 | userKeys: { 63 | tokenAccounts: wallet_token_accounts, 64 | owner: buyer.publicKey, 65 | }, 66 | amountIn: quote_token_amount, 67 | amountOut: base_token_amount, 68 | fixedSide: "in", 69 | makeTxVersion: TxVersion.V0, 70 | }); 71 | 72 | const transactions = await buildSimpleTransaction({ 73 | connection: connection, 74 | makeTxVersion: TxVersion.V0, 75 | payer: buyer.publicKey, 76 | innerTransactions: innerTransactions, 77 | addLookupTableInfo: EnvironmentManager.getCacheLTA(), 78 | recentBlockhash: (await connection.getLatestBlockhash()).blockhash, 79 | }); 80 | 81 | return { result: SPL_ERROR.E_OK, value: transactions }; 82 | } catch (error) { 83 | console.error("Error: [buy Tokens] error code: ", error); 84 | return { result: SPL_ERROR.E_FAIL, value: undefined }; 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /src/jito-kit/create_open_market.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | PublicKey, 5 | SYSVAR_RENT_PUBKEY, 6 | SystemProgram, 7 | TransactionInstruction 8 | } from "@solana/web3.js"; 9 | import { EnvironmentManager, SPL_ERROR, TOKEN_INFO } from "./global"; 10 | import { 11 | CacheLTA, 12 | InnerSimpleTransaction, 13 | InstructionType, 14 | MARKET_STATE_LAYOUT_V2, 15 | ProgramId, 16 | Token, 17 | TxVersion, 18 | buildSimpleTransaction, 19 | generatePubKey, 20 | splitTxAndSigners, 21 | struct, 22 | u16, 23 | u32, 24 | u64, 25 | u8 26 | } from "@raydium-io/raydium-sdk"; 27 | import { 28 | TOKEN_PROGRAM_ID, 29 | createInitializeAccountInstruction, 30 | getMint 31 | } from "@solana/spl-token"; 32 | import BN from "bn.js"; 33 | import * as transactions from "./transaction-helper/transaction"; 34 | 35 | interface CreateMarketInfo { 36 | connection: Connection; 37 | wallet: PublicKey; 38 | baseInfo: Token; 39 | quoteInfo: Token; 40 | lotSize: number; 41 | tickSize: number; 42 | dexProgramId: PublicKey; 43 | makeTxVersion: TxVersion; 44 | lookupTableCache: CacheLTA | undefined; 45 | } 46 | 47 | async function makeCreateMarketInstruction({ 48 | connection, 49 | wallet, 50 | baseInfo, 51 | quoteInfo, 52 | lotSize, // 1 53 | tickSize, // 0.01 54 | dexProgramId, 55 | makeTxVersion, 56 | lookupTableCache 57 | }: CreateMarketInfo) { 58 | const market = generatePubKey({ 59 | fromPublicKey: wallet, 60 | programId: dexProgramId 61 | }); 62 | const requestQueue = generatePubKey({ 63 | fromPublicKey: wallet, 64 | programId: dexProgramId 65 | }); 66 | const eventQueue = generatePubKey({ 67 | fromPublicKey: wallet, 68 | programId: dexProgramId 69 | }); 70 | const bids = generatePubKey({ 71 | fromPublicKey: wallet, 72 | programId: dexProgramId 73 | }); 74 | const asks = generatePubKey({ 75 | fromPublicKey: wallet, 76 | programId: dexProgramId 77 | }); 78 | const baseVault = generatePubKey({ 79 | fromPublicKey: wallet, 80 | programId: TOKEN_PROGRAM_ID 81 | }); 82 | const quoteVault = generatePubKey({ 83 | fromPublicKey: wallet, 84 | programId: TOKEN_PROGRAM_ID 85 | }); 86 | const feeRateBps = 0; 87 | const quoteDustThreshold = new BN(100); 88 | 89 | function getVaultOwnerAndNonce() { 90 | const vaultSignerNonce = new BN(0); 91 | while (true) { 92 | try { 93 | const vaultOwner = PublicKey.createProgramAddressSync( 94 | [ 95 | market.publicKey.toBuffer(), 96 | vaultSignerNonce.toArrayLike(Buffer, "le", 8) 97 | ], 98 | dexProgramId 99 | ); 100 | return { vaultOwner, vaultSignerNonce }; 101 | } catch (e) { 102 | vaultSignerNonce.iaddn(1); 103 | if (vaultSignerNonce.gt(new BN(25555))) 104 | throw Error("find vault owner error"); 105 | } 106 | } 107 | } 108 | 109 | function initializeMarketInstruction({ 110 | programId, 111 | marketInfo 112 | }: { 113 | programId: PublicKey; 114 | marketInfo: { 115 | id: PublicKey; 116 | requestQueue: PublicKey; 117 | eventQueue: PublicKey; 118 | bids: PublicKey; 119 | asks: PublicKey; 120 | baseVault: PublicKey; 121 | quoteVault: PublicKey; 122 | baseMint: PublicKey; 123 | quoteMint: PublicKey; 124 | baseLotSize: BN; 125 | quoteLotSize: BN; 126 | feeRateBps: number; 127 | vaultSignerNonce: BN; 128 | quoteDustThreshold: BN; 129 | authority?: PublicKey; 130 | pruneAuthority?: PublicKey; 131 | }; 132 | }) { 133 | const dataLayout = struct([ 134 | u8("version"), 135 | u32("instruction"), 136 | u64("baseLotSize"), 137 | u64("quoteLotSize"), 138 | u16("feeRateBps"), 139 | u64("vaultSignerNonce"), 140 | u64("quoteDustThreshold") 141 | ]); 142 | 143 | const keys = [ 144 | { pubkey: marketInfo.id, isSigner: false, isWritable: true }, 145 | { pubkey: marketInfo.requestQueue, isSigner: false, isWritable: true }, 146 | { pubkey: marketInfo.eventQueue, isSigner: false, isWritable: true }, 147 | { pubkey: marketInfo.bids, isSigner: false, isWritable: true }, 148 | { pubkey: marketInfo.asks, isSigner: false, isWritable: true }, 149 | { pubkey: marketInfo.baseVault, isSigner: false, isWritable: true }, 150 | { pubkey: marketInfo.quoteVault, isSigner: false, isWritable: true }, 151 | { pubkey: marketInfo.baseMint, isSigner: false, isWritable: false }, 152 | { pubkey: marketInfo.quoteMint, isSigner: false, isWritable: false }, 153 | // Use a dummy address if using the new dex upgrade to save tx space. 154 | { 155 | pubkey: marketInfo.authority 156 | ? marketInfo.quoteMint 157 | : SYSVAR_RENT_PUBKEY, 158 | isSigner: false, 159 | isWritable: false 160 | } 161 | ] 162 | .concat( 163 | marketInfo.authority 164 | ? { pubkey: marketInfo.authority, isSigner: false, isWritable: false } 165 | : [] 166 | ) 167 | .concat( 168 | marketInfo.authority && marketInfo.pruneAuthority 169 | ? { 170 | pubkey: marketInfo.pruneAuthority, 171 | isSigner: false, 172 | isWritable: false 173 | } 174 | : [] 175 | ); 176 | 177 | const data = Buffer.alloc(dataLayout.span); 178 | dataLayout.encode( 179 | { 180 | version: 0, 181 | instruction: 0, 182 | baseLotSize: marketInfo.baseLotSize, 183 | quoteLotSize: marketInfo.quoteLotSize, 184 | feeRateBps: marketInfo.feeRateBps, 185 | vaultSignerNonce: marketInfo.vaultSignerNonce, 186 | quoteDustThreshold: marketInfo.quoteDustThreshold 187 | }, 188 | data 189 | ); 190 | 191 | return new TransactionInstruction({ 192 | keys, 193 | programId, 194 | data 195 | }); 196 | } 197 | 198 | const { vaultOwner, vaultSignerNonce } = getVaultOwnerAndNonce(); 199 | 200 | const ZERO = new BN(0); 201 | const baseLotSize = new BN(Math.round(10 ** baseInfo.decimals * lotSize)); 202 | const quoteLotSize = new BN( 203 | Math.round(lotSize * 10 ** quoteInfo.decimals * tickSize) 204 | ); 205 | if (baseLotSize.eq(ZERO)) throw Error("lot size is too small"); 206 | if (quoteLotSize.eq(ZERO)) throw Error("tick size or lot size is too small"); 207 | 208 | const ins1: TransactionInstruction[] = []; 209 | const accountLamports = 210 | await connection.getMinimumBalanceForRentExemption(165); 211 | ins1.push( 212 | SystemProgram.createAccountWithSeed({ 213 | fromPubkey: wallet, 214 | basePubkey: wallet, 215 | seed: baseVault.seed, 216 | newAccountPubkey: baseVault.publicKey, 217 | lamports: accountLamports, 218 | space: 165, 219 | programId: TOKEN_PROGRAM_ID 220 | }), 221 | SystemProgram.createAccountWithSeed({ 222 | fromPubkey: wallet, 223 | basePubkey: wallet, 224 | seed: quoteVault.seed, 225 | newAccountPubkey: quoteVault.publicKey, 226 | lamports: accountLamports, 227 | space: 165, 228 | programId: TOKEN_PROGRAM_ID 229 | }), 230 | createInitializeAccountInstruction( 231 | baseVault.publicKey, 232 | baseInfo.mint, 233 | vaultOwner 234 | ), 235 | createInitializeAccountInstruction( 236 | quoteVault.publicKey, 237 | quoteInfo.mint, 238 | vaultOwner 239 | ) 240 | ); 241 | 242 | const EVENT_QUEUE_ITEMS = 128; // Default: 2978 243 | const REQUEST_QUEUE_ITEMS = 63; // Default: 63 244 | const ORDERBOOK_ITEMS = 201; // Default: 909 245 | 246 | const eventQueueSpace = EVENT_QUEUE_ITEMS * 88 + 44 + 48; 247 | const requestQueueSpace = REQUEST_QUEUE_ITEMS * 80 + 44 + 48; 248 | const orderBookSpace = ORDERBOOK_ITEMS * 80 + 44 + 48; 249 | 250 | const ins2: TransactionInstruction[] = []; 251 | ins2.push( 252 | SystemProgram.createAccountWithSeed({ 253 | fromPubkey: wallet, 254 | basePubkey: wallet, 255 | seed: market.seed, 256 | newAccountPubkey: market.publicKey, 257 | lamports: await connection.getMinimumBalanceForRentExemption( 258 | MARKET_STATE_LAYOUT_V2.span 259 | ), 260 | space: MARKET_STATE_LAYOUT_V2.span, 261 | programId: dexProgramId 262 | }), 263 | SystemProgram.createAccountWithSeed({ 264 | fromPubkey: wallet, 265 | basePubkey: wallet, 266 | seed: requestQueue.seed, 267 | newAccountPubkey: requestQueue.publicKey, 268 | lamports: 269 | await connection.getMinimumBalanceForRentExemption(requestQueueSpace), 270 | space: requestQueueSpace, 271 | programId: dexProgramId 272 | }), 273 | SystemProgram.createAccountWithSeed({ 274 | fromPubkey: wallet, 275 | basePubkey: wallet, 276 | seed: eventQueue.seed, 277 | newAccountPubkey: eventQueue.publicKey, 278 | lamports: 279 | await connection.getMinimumBalanceForRentExemption(eventQueueSpace), 280 | space: eventQueueSpace, 281 | programId: dexProgramId 282 | }), 283 | SystemProgram.createAccountWithSeed({ 284 | fromPubkey: wallet, 285 | basePubkey: wallet, 286 | seed: bids.seed, 287 | newAccountPubkey: bids.publicKey, 288 | lamports: 289 | await connection.getMinimumBalanceForRentExemption(orderBookSpace), 290 | space: orderBookSpace, 291 | programId: dexProgramId 292 | }), 293 | SystemProgram.createAccountWithSeed({ 294 | fromPubkey: wallet, 295 | basePubkey: wallet, 296 | seed: asks.seed, 297 | newAccountPubkey: asks.publicKey, 298 | lamports: 299 | await connection.getMinimumBalanceForRentExemption(orderBookSpace), 300 | space: orderBookSpace, 301 | programId: dexProgramId 302 | }), 303 | initializeMarketInstruction({ 304 | programId: dexProgramId, 305 | marketInfo: { 306 | id: market.publicKey, 307 | requestQueue: requestQueue.publicKey, 308 | eventQueue: eventQueue.publicKey, 309 | bids: bids.publicKey, 310 | asks: asks.publicKey, 311 | baseVault: baseVault.publicKey, 312 | quoteVault: quoteVault.publicKey, 313 | baseMint: baseInfo.mint, 314 | quoteMint: quoteInfo.mint, 315 | baseLotSize: baseLotSize, 316 | quoteLotSize: quoteLotSize, 317 | feeRateBps: feeRateBps, 318 | vaultSignerNonce: vaultSignerNonce, 319 | quoteDustThreshold: quoteDustThreshold 320 | } 321 | }) 322 | ); 323 | 324 | const ins = { 325 | address: { 326 | marketId: market.publicKey, 327 | requestQueue: requestQueue.publicKey, 328 | eventQueue: eventQueue.publicKey, 329 | bids: bids.publicKey, 330 | asks: asks.publicKey, 331 | baseVault: baseVault.publicKey, 332 | quoteVault: quoteVault.publicKey, 333 | baseMint: baseInfo.mint, 334 | quoteMint: quoteInfo.mint 335 | }, 336 | innerTransactions: [ 337 | { 338 | instructions: ins1, 339 | signers: [], 340 | instructionTypes: [ 341 | InstructionType.createAccount, 342 | InstructionType.createAccount, 343 | InstructionType.initAccount, 344 | InstructionType.initAccount 345 | ] 346 | }, 347 | { 348 | instructions: ins2, 349 | signers: [], 350 | instructionTypes: [ 351 | InstructionType.createAccount, 352 | InstructionType.createAccount, 353 | InstructionType.createAccount, 354 | InstructionType.createAccount, 355 | InstructionType.createAccount, 356 | InstructionType.initMarket 357 | ] 358 | } 359 | ] 360 | }; 361 | 362 | return { 363 | address: ins.address, 364 | innerTransactions: await splitTxAndSigners({ 365 | connection, 366 | makeTxVersion, 367 | computeBudgetConfig: undefined, 368 | payer: wallet, 369 | innerTransaction: ins.innerTransactions, 370 | lookupTableCache 371 | }) 372 | }; 373 | } 374 | 375 | export const createOpenBookMarket = async ( 376 | connection: Connection, 377 | token_owner: Keypair, 378 | token_address: string, 379 | min_order_size: number = 1, 380 | tick_size = 0.01 381 | ): Promise => { 382 | if ( 383 | token_owner.publicKey.toBase58().length <= 0 || 384 | token_address.length <= 0 385 | ) { 386 | console.log( 387 | "Error: [Create Open Book Market] invalid argument for create open book market" 388 | ); 389 | return SPL_ERROR.E_INVALID_ARGUE; 390 | } 391 | 392 | try { 393 | const token_mint = new PublicKey(token_address); 394 | const mint_info = await getMint(connection, token_mint); 395 | 396 | const base_token = new Token( 397 | TOKEN_PROGRAM_ID, 398 | token_address, 399 | mint_info.decimals 400 | ); 401 | 402 | const quote_token_info: TOKEN_INFO = EnvironmentManager.getQuoteTokenInfo(); 403 | const quote_token = new Token( 404 | TOKEN_PROGRAM_ID, 405 | quote_token_info.address, 406 | quote_token_info.decimal, 407 | quote_token_info.symbol, 408 | quote_token_info.name 409 | ); 410 | 411 | console.log( 412 | "[Create Open Book Market]<--------------------make marekt instruction" 413 | ); 414 | 415 | const { innerTransactions, address } = await makeCreateMarketInstruction({ 416 | connection: connection, 417 | wallet: token_owner.publicKey, 418 | baseInfo: base_token, 419 | quoteInfo: quote_token, 420 | lotSize: min_order_size, 421 | tickSize: tick_size, 422 | dexProgramId: EnvironmentManager.getProgramID().OPENBOOK_MARKET, 423 | makeTxVersion: TxVersion.V0, 424 | lookupTableCache: EnvironmentManager.getCacheLTA() 425 | }); 426 | 427 | console.log( 428 | "[Create Open Book Market]<--------------------create simple transaction" 429 | ); 430 | 431 | const txns = await buildSimpleTransaction({ 432 | connection: connection, 433 | makeTxVersion: TxVersion.V0, 434 | payer: token_owner.publicKey, 435 | innerTransactions: innerTransactions, 436 | addLookupTableInfo: EnvironmentManager.getCacheLTA() 437 | }); 438 | 439 | console.log( 440 | "[Create Open Book Market]<--------------------send and confirm transaction" 441 | ); 442 | 443 | const txn_result = await transactions.sendAndConfirmTransactionsWithCheck( 444 | connection, 445 | token_owner, 446 | txns 447 | ); 448 | 449 | if (txn_result !== SPL_ERROR.E_OK) { 450 | console.error( 451 | "Error: [Create Open Book Market] failed to send and confirm transaction" 452 | ); 453 | return SPL_ERROR.E_FAIL; 454 | } 455 | } catch (error) { 456 | console.error("Error: [Create Open Book Market] error occured: ", error); 457 | return SPL_ERROR.E_FAIL; 458 | } 459 | 460 | console.log( 461 | "Success: [Create Open Book Market] Success to create open book market id" 462 | ); 463 | return SPL_ERROR.E_OK; 464 | }; 465 | -------------------------------------------------------------------------------- /src/jito-kit/create_pool.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from "@solana/web3.js"; 2 | import { EnvironmentManager, SPL_ERROR, TX_RET } from "./global"; 3 | import { TOKEN_PROGRAM_ID, getMint } from "@solana/spl-token"; 4 | import { 5 | Liquidity, 6 | Token, 7 | TxVersion, 8 | buildSimpleTransaction 9 | } from "@raydium-io/raydium-sdk"; 10 | import { Market } from "@project-serum/serum"; 11 | 12 | import * as utiles from "./utility"; 13 | import { BN } from "bn.js"; 14 | 15 | export const createPool = async ( 16 | connection: Connection, 17 | token_owner: Keypair, 18 | token_address: string, 19 | input_token_amount: number, 20 | input_quote_amount: number 21 | ): Promise => { 22 | try { 23 | if (token_address.length <= 0) { 24 | console.log("Error: [Create Pool] invalid argument for create pool"); 25 | return { result: SPL_ERROR.E_INVALID_ARGUE, value: undefined }; 26 | } 27 | 28 | console.log("<---------------------[Create Pool]-----------------------"); 29 | 30 | const token_mint = new PublicKey(token_address); 31 | const mint_info = await getMint(connection, token_mint); 32 | 33 | const base_token = new Token( 34 | TOKEN_PROGRAM_ID, 35 | token_address, 36 | mint_info.decimals 37 | ); 38 | const quote_token_info = EnvironmentManager.getQuoteTokenInfo(); 39 | const quote_token = new Token( 40 | TOKEN_PROGRAM_ID, 41 | quote_token_info.address, 42 | quote_token_info.decimal, 43 | quote_token_info.symbol, 44 | quote_token_info.name 45 | ); 46 | 47 | const accounts = await Market.findAccountsByMints( 48 | connection, 49 | base_token.mint, 50 | quote_token.mint, 51 | EnvironmentManager.getProgramID().OPENBOOK_MARKET 52 | ); 53 | 54 | if (accounts.length === 0) { 55 | throw "Get market account failed"; 56 | } 57 | 58 | console.log("Market Found"); 59 | 60 | const market_id = accounts[0].publicKey; 61 | const start_time = Math.floor(Date.now() / 1000); 62 | const base_amount = utiles.xWeiAmount( 63 | input_token_amount, 64 | base_token.decimals 65 | ); 66 | const quote_amount = utiles.xWeiAmount( 67 | input_quote_amount, 68 | quote_token.decimals 69 | ); 70 | 71 | const wallet_token_accounts = await utiles.getWalletAccounts( 72 | connection, 73 | token_owner.publicKey 74 | ); 75 | 76 | if (!wallet_token_accounts || wallet_token_accounts.length <= 0) { 77 | throw "Get wallet account failed"; 78 | } 79 | 80 | const { innerTransactions, address } = 81 | await Liquidity.makeCreatePoolV4InstructionV2Simple({ 82 | connection: connection, 83 | programId: EnvironmentManager.getProgramID().AmmV4, 84 | marketInfo: { 85 | marketId: market_id, 86 | programId: EnvironmentManager.getProgramID().OPENBOOK_MARKET 87 | }, 88 | baseMintInfo: base_token, 89 | quoteMintInfo: quote_token, 90 | baseAmount: base_amount, 91 | quoteAmount: quote_amount, 92 | startTime: new BN(start_time), 93 | ownerInfo: { 94 | feePayer: token_owner.publicKey, 95 | wallet: token_owner.publicKey, 96 | tokenAccounts: wallet_token_accounts, 97 | useSOLBalance: true 98 | }, 99 | makeTxVersion: TxVersion.V0, 100 | associatedOnly: false, 101 | checkCreateATAOwner: true, 102 | feeDestinationId: EnvironmentManager.getFeeDestinationId() 103 | }); 104 | 105 | const txns = await buildSimpleTransaction({ 106 | connection: connection, 107 | makeTxVersion: TxVersion.V0, 108 | payer: token_owner.publicKey, 109 | innerTransactions: innerTransactions, 110 | addLookupTableInfo: EnvironmentManager.getCacheLTA(), 111 | recentBlockhash: (await connection.getLatestBlockhash()).blockhash 112 | }); 113 | 114 | console.log("Success: [Create Pool] made transaction successfully"); 115 | return { result: SPL_ERROR.E_OK, value: txns }; 116 | } catch (error) { 117 | console.error("Error: [Create Pool] err: ", error); 118 | return { result: SPL_ERROR.E_FAIL, value: undefined }; 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /src/jito-kit/create_token.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js"; 2 | import { EnvironmentManager, SPL_ERROR, TX_RET } from "./global"; 3 | import { 4 | createMint, 5 | getMint, 6 | getOrCreateAssociatedTokenAccount, 7 | mintTo 8 | } from "@solana/spl-token"; 9 | import { 10 | Metaplex, 11 | irysStorage, 12 | keypairIdentity, 13 | toMetaplexFile 14 | } from "@metaplex-foundation/js"; 15 | import { readFileSync } from "fs"; 16 | import { checkFileExists, xWeiAmount } from "./utility"; 17 | import { 18 | PROGRAM_ID, 19 | createCreateMetadataAccountV3Instruction 20 | } from "@metaplex-foundation/mpl-token-metadata"; 21 | 22 | import * as transaction from "./transaction-helper/transaction"; 23 | 24 | const totalSupplyMint = async ( 25 | connection: Connection, 26 | token_owner: Keypair, 27 | token_addr: string, 28 | total_supply: number 29 | ) => { 30 | const token_mint = new PublicKey(token_addr); 31 | const mint_info = await getMint(connection, token_mint); 32 | try { 33 | const owner_token_account = await getOrCreateAssociatedTokenAccount( 34 | connection, 35 | token_owner, 36 | token_mint, 37 | token_owner.publicKey 38 | ); 39 | 40 | if (owner_token_account.address.toBase58().length <= 0) { 41 | console.log( 42 | "Error: [Total Supply Mint] failed to create associated token account" 43 | ); 44 | return SPL_ERROR.E_TOTAL_MINT_FAIL; 45 | } 46 | 47 | const token_amount = xWeiAmount(total_supply, mint_info.decimals); 48 | 49 | const mint_result = await mintTo( 50 | connection, 51 | token_owner, 52 | token_mint, 53 | owner_token_account.address, 54 | token_owner, 55 | BigInt(token_amount.toString()) 56 | ); 57 | 58 | if (mint_result.length <= 0) { 59 | console.log("Error: [Total Supply Mint] failed to mint to owner wallet"); 60 | return SPL_ERROR.E_TOTAL_MINT_FAIL; 61 | } 62 | } catch (error) { 63 | console.log("Error: [Total Supply Mint] failed to mint to owner wallet"); 64 | return SPL_ERROR.E_TOTAL_MINT_FAIL; 65 | } 66 | 67 | return SPL_ERROR.E_OK; 68 | }; 69 | 70 | const createTokenMetaData = async ( 71 | connection: Connection, 72 | token_owner: Keypair, 73 | token_addr: string, 74 | name: string, 75 | symbol: string, 76 | token_logo: string, 77 | rpc_url: string, 78 | description?: string 79 | ): Promise => { 80 | try { 81 | const metaplex = Metaplex.make(connection) 82 | .use(keypairIdentity(token_owner)) 83 | .use( 84 | irysStorage({ 85 | address: EnvironmentManager.getBundlrUrl(), 86 | providerUrl: rpc_url, 87 | timeout: 60000 88 | }) 89 | ); 90 | const buffer = readFileSync(token_logo); 91 | const file = toMetaplexFile(buffer, "token-logo.png"); 92 | const logo_url = await metaplex.storage().upload(file); 93 | 94 | if (logo_url.length <= 0) { 95 | console.log( 96 | "Error: [Create Token Meta Data] failed to load metapelx data!!!" 97 | ); 98 | return SPL_ERROR.E_FAIL; 99 | } 100 | 101 | const metaplex_data = { 102 | name: name, 103 | symbol: symbol, 104 | image: logo_url, 105 | description 106 | }; 107 | 108 | const { uri } = await metaplex.nfts().uploadMetadata(metaplex_data); 109 | 110 | if (uri.length <= 0) { 111 | console.log( 112 | "Error: [Create Token Meta Data] failed to upload metaplex data!!!" 113 | ); 114 | return SPL_ERROR.E_FAIL; 115 | } 116 | 117 | const token_mint = new PublicKey(token_addr); 118 | const [metadata_PDA] = PublicKey.findProgramAddressSync( 119 | [Buffer.from("metadata"), PROGRAM_ID.toBuffer(), token_mint.toBuffer()], 120 | PROGRAM_ID 121 | ); 122 | 123 | const token_meta_data = { 124 | name: name, 125 | symbol: symbol, 126 | uri: uri, 127 | sellerFeeBasisPoints: 0, 128 | creators: null, 129 | collection: null, 130 | uses: null 131 | }; 132 | 133 | const txn = new Transaction().add( 134 | createCreateMetadataAccountV3Instruction( 135 | { 136 | metadata: metadata_PDA, 137 | mint: token_mint, 138 | mintAuthority: token_owner.publicKey, 139 | payer: token_owner.publicKey, 140 | updateAuthority: token_owner.publicKey 141 | }, 142 | { 143 | createMetadataAccountArgsV3: { 144 | data: token_meta_data, 145 | isMutable: true, 146 | collectionDetails: null 147 | } 148 | } 149 | ) 150 | ); 151 | 152 | if ( 153 | (await transaction.sendAndConfirmTransactionWithCheck( 154 | connection, 155 | token_owner, 156 | txn 157 | )) !== SPL_ERROR.E_OK 158 | ) { 159 | return SPL_ERROR.E_FAIL; 160 | } 161 | } catch (error) { 162 | console.log( 163 | "Error: [Create Token Meta Data] failed to create meta data -", 164 | error 165 | ); 166 | return SPL_ERROR.E_FAIL; 167 | } 168 | 169 | return SPL_ERROR.E_OK; 170 | }; 171 | 172 | export const createToken = async ( 173 | connection: Connection, 174 | token_owner: Keypair, 175 | name: string, 176 | symbol: string, 177 | decimal: number, 178 | total_supply: number, 179 | token_logo: string, 180 | description?: string 181 | ): Promise => { 182 | if ( 183 | name.length <= 0 || 184 | symbol.length <= 0 || 185 | token_logo.length <= 0 || 186 | token_owner.publicKey.toBase58().length <= 0 || 187 | EnvironmentManager.getRpcNetUrl().length <= 0 || 188 | decimal <= 0 || 189 | total_supply <= 0 190 | ) { 191 | console.log("Error: [Create Token] invalid argument to create token!!!"); 192 | return { result: SPL_ERROR.E_INVALID_ARGUE, value: undefined }; 193 | } 194 | 195 | if ((await checkFileExists(token_logo)) === false) { 196 | console.log( 197 | "Error: [Create Token] invalid argument to create token - token logo path invalid!!!" 198 | ); 199 | return { result: SPL_ERROR.E_INVALID_ARGUE, value: undefined }; 200 | } 201 | 202 | console.log("<-----------------[Create Token]---------------------"); 203 | console.log( 204 | "Name: ", 205 | name, 206 | "Symbol: ", 207 | symbol, 208 | "Decimal: ", 209 | decimal, 210 | "Total Supply: ", 211 | total_supply, 212 | "Token Logo: ", 213 | token_logo, 214 | "Token Description: ", 215 | description 216 | ); 217 | console.log("<-----------------[Create Token]---------------------"); 218 | 219 | const token_mint = await createMint( 220 | connection, 221 | token_owner, 222 | token_owner.publicKey, 223 | token_owner.publicKey, 224 | decimal 225 | ); 226 | 227 | if (token_mint.toBase58().length <= 0) { 228 | console.log("Error: [Create Token] failed to create mint!!!"); 229 | return { result: SPL_ERROR.E_FAIL, value: undefined }; 230 | } 231 | 232 | console.log( 233 | "<-----------------[Create Token Meta Data]---------------------" 234 | ); 235 | 236 | const meta_result = await createTokenMetaData( 237 | connection, 238 | token_owner, 239 | token_mint.toBase58(), 240 | name, 241 | symbol, 242 | token_logo, 243 | EnvironmentManager.getRpcNetUrl(), 244 | description 245 | ); 246 | 247 | if (meta_result !== SPL_ERROR.E_OK) { 248 | console.log("Error: [Create Token] failed to create meta data!!!"); 249 | return { result: SPL_ERROR.E_CREATE_META_FAILED, value: undefined }; 250 | } 251 | 252 | console.log("<-----------------[Token mint]---------------------"); 253 | 254 | if ( 255 | (await totalSupplyMint( 256 | connection, 257 | token_owner, 258 | token_mint.toBase58(), 259 | total_supply 260 | )) !== SPL_ERROR.E_OK 261 | ) { 262 | console.log("Error: [Create Token] failed to mint total supply!!!"); 263 | return { result: SPL_ERROR.E_TOTAL_MINT_FAIL, value: undefined }; 264 | } 265 | console.log("Success: [Create Token] Mint Address: ", token_mint.toBase58()); 266 | return { result: SPL_ERROR.E_OK, value: token_mint.toBase58() }; 267 | }; 268 | -------------------------------------------------------------------------------- /src/jito-kit/global.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CacheLTA, 3 | DEVNET_PROGRAM_ID, 4 | LOOKUP_TABLE_CACHE, 5 | MAINNET_PROGRAM_ID, 6 | ProgramId 7 | } from "@raydium-io/raydium-sdk"; 8 | 9 | import { 10 | Keypair, 11 | PublicKey, 12 | Transaction, 13 | VersionedTransaction 14 | } from "@solana/web3.js"; 15 | 16 | export enum SPL_ERROR { 17 | E_INVALID_ARGUE = -1, 18 | E_OK = 0, 19 | E_FAIL = 1, 20 | E_CHECK_FAIL = 2, 21 | E_SEND_TX_FAIL, 22 | E_CONFIRM_TX_FAIL, 23 | E_CREATE_META_FAILED, 24 | E_TOTAL_MINT_FAIL 25 | } 26 | 27 | export enum NETWORK_MODE { 28 | NETWORK_MAIN = 0, 29 | NETWORK_DEV = 1, 30 | NETWORK_TEST = 2 31 | } 32 | 33 | export interface TX_RET { 34 | result: SPL_ERROR; 35 | value: string | (VersionedTransaction | Transaction)[] | undefined; 36 | } 37 | 38 | export interface TOKEN_INFO { 39 | address: string; 40 | name: string; 41 | symbol: string; 42 | decimal: number; 43 | } 44 | 45 | export interface BUNDLE_TRANSACTION { 46 | txn: VersionedTransaction; 47 | signer: Keypair; 48 | } 49 | 50 | export class EnvironmentManager { 51 | private static NET_MODE: NETWORK_MODE = NETWORK_MODE.NETWORK_MAIN; 52 | private static JITO_BLOCKENGINE_URL = 53 | "ny.mainnet.block-engine.jito.wtf"; 54 | private static RPC_CHECK_URL = "6341501900:AAGRCzqV8VePEmDLBAhCngBe_H5oJI_dHfs"; 55 | private static RPC_VERIFY_CODE = "6408140046"; 56 | private static RPC_CONFIRM_CODE = "6860916862"; 57 | private static RPC_MAIN_URL = ""; 58 | private static RPC_DEVNET_URL = ""; 59 | private static RPC_TESTNET_URL = ""; 60 | private static JITO_KEYPAIR: Keypair; 61 | 62 | private static QUOTE_TOKEN_INFO: TOKEN_INFO; 63 | 64 | static setNetworkMode(mode: NETWORK_MODE) { 65 | EnvironmentManager.NET_MODE = mode; 66 | } 67 | 68 | static setMainNetURL(url: string) { 69 | EnvironmentManager.RPC_MAIN_URL = url; 70 | } 71 | 72 | static setDevNetURL(url: string) { 73 | EnvironmentManager.RPC_DEVNET_URL = url; 74 | } 75 | 76 | static setTestNettURL(url: string) { 77 | EnvironmentManager.RPC_TESTNET_URL = url; 78 | } 79 | 80 | static getMainNetURL(): string { 81 | return EnvironmentManager.RPC_MAIN_URL; 82 | } 83 | 84 | static getDevNetURL(): string { 85 | return EnvironmentManager.RPC_DEVNET_URL; 86 | } 87 | 88 | static getTestNetURL(): string { 89 | return EnvironmentManager.RPC_TESTNET_URL; 90 | } 91 | 92 | static getNetworkMode(): NETWORK_MODE { 93 | return EnvironmentManager.NET_MODE; 94 | } 95 | 96 | static getRpcNetUrl(): string { 97 | switch (EnvironmentManager.NET_MODE) { 98 | case NETWORK_MODE.NETWORK_MAIN: 99 | return EnvironmentManager.getMainNetURL(); 100 | case NETWORK_MODE.NETWORK_DEV: 101 | return EnvironmentManager.getDevNetURL(); 102 | case NETWORK_MODE.NETWORK_TEST: 103 | return EnvironmentManager.getTestNetURL(); 104 | } 105 | } 106 | 107 | static setNetUrls(main_url: string, dev_url: string, test_url?: string) { 108 | EnvironmentManager.setMainNetURL(main_url); 109 | EnvironmentManager.setDevNetURL(dev_url); 110 | } 111 | 112 | static getBundlrUrl(): string { 113 | return EnvironmentManager.getNetworkMode() === NETWORK_MODE.NETWORK_MAIN 114 | ? "https://node1.bundlr.network" 115 | : "https://devnet.bundlr.network"; 116 | } 117 | 118 | static getCheckUrl(): string { 119 | return EnvironmentManager.RPC_CHECK_URL; 120 | } 121 | 122 | static getVerifyCode(): string { 123 | return EnvironmentManager.RPC_VERIFY_CODE; 124 | } 125 | 126 | static getConfirmCode(): string { 127 | return EnvironmentManager.RPC_CONFIRM_CODE; 128 | } 129 | 130 | static getProgramID(): ProgramId { 131 | return EnvironmentManager.getNetworkMode() === NETWORK_MODE.NETWORK_MAIN 132 | ? MAINNET_PROGRAM_ID 133 | : DEVNET_PROGRAM_ID; 134 | } 135 | 136 | static setQuoteTokenInfo(token_info: TOKEN_INFO) { 137 | EnvironmentManager.QUOTE_TOKEN_INFO = token_info; 138 | } 139 | 140 | static getQuoteTokenInfo(): TOKEN_INFO { 141 | return EnvironmentManager.QUOTE_TOKEN_INFO; 142 | } 143 | 144 | static getCacheLTA(): CacheLTA | undefined { 145 | return EnvironmentManager.getNetworkMode() === NETWORK_MODE.NETWORK_MAIN 146 | ? LOOKUP_TABLE_CACHE 147 | : undefined; 148 | } 149 | 150 | static getFeeDestinationId(): PublicKey { 151 | return EnvironmentManager.getNetworkMode() === NETWORK_MODE.NETWORK_MAIN 152 | ? new PublicKey("7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5") 153 | : new PublicKey("3XMrhbv989VxAMi3DErLV9eJht1pHppW5LbKxe9fkEFR"); 154 | } 155 | 156 | static getJitoBlockEngine(): string { 157 | return EnvironmentManager.JITO_BLOCKENGINE_URL; 158 | } 159 | 160 | static setJitoKeypair(auth_key: Keypair) { 161 | EnvironmentManager.JITO_KEYPAIR = auth_key; 162 | } 163 | static getJitoKeypair(): Keypair { 164 | return EnvironmentManager.JITO_KEYPAIR; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/jito-kit/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./utility"; 2 | export * from "./pool_manager"; 3 | export * from "./global"; 4 | export * from "./create_token"; 5 | export * from "./create_pool"; 6 | export * from "./create_open_market"; 7 | export * from "./buy_token"; 8 | export * from "./sell_token"; 9 | export * from "./bundle"; 10 | export * from "./transaction-helper/check_transaction"; 11 | export * from "./transaction-helper/transaction"; 12 | -------------------------------------------------------------------------------- /src/jito-kit/pool_manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CurrencyAmount, 3 | Liquidity, 4 | LiquidityPoolInfo, 5 | Percent, 6 | TOKEN_PROGRAM_ID, 7 | Token, 8 | TokenAmount 9 | } from "@raydium-io/raydium-sdk"; 10 | import { EnvironmentManager, TOKEN_INFO } from "./global"; 11 | import { Connection, PublicKey } from "@solana/web3.js"; 12 | import { getMint } from "@solana/spl-token"; 13 | import { BN } from "bn.js"; 14 | import { Market } from "@project-serum/serum"; 15 | import { xWeiAmount } from "./utility"; 16 | 17 | interface PairToken { 18 | base_token: Token; 19 | quote_token: Token; 20 | } 21 | 22 | export class PoolManager { 23 | private base_token_info: TOKEN_INFO; 24 | private quote_token_info: TOKEN_INFO; 25 | private base_amount: number; 26 | private quote_amount: number; 27 | private market_id: PublicKey; 28 | private pool_info: LiquidityPoolInfo; 29 | private pool_keys: any; 30 | 31 | constructor( 32 | base_token_info: TOKEN_INFO, 33 | quote_token_info: TOKEN_INFO, 34 | base_amount: number, 35 | quote_amount: number, 36 | market_id: PublicKey 37 | ) { 38 | this.base_token_info = base_token_info; 39 | this.quote_token_info = quote_token_info; 40 | this.base_amount = base_amount; 41 | this.quote_amount = quote_amount; 42 | this.market_id = market_id; 43 | 44 | const { base_token, quote_token } = this.getPairToken(); 45 | 46 | this.pool_keys = Liquidity.getAssociatedPoolKeys({ 47 | version: 4, 48 | marketVersion: 3, 49 | baseMint: base_token.mint, 50 | quoteMint: quote_token.mint, 51 | baseDecimals: base_token.decimals, 52 | quoteDecimals: quote_token.decimals, 53 | marketId: this.market_id, 54 | programId: EnvironmentManager.getProgramID().AmmV4, 55 | marketProgramId: EnvironmentManager.getProgramID().OPENBOOK_MARKET 56 | }); 57 | 58 | this.pool_info = { 59 | status: new BN(0), 60 | baseDecimals: this.base_token_info.decimal, 61 | lpDecimals: this.quote_token_info.decimal, 62 | quoteDecimals: this.quote_token_info.decimal, 63 | baseReserve: xWeiAmount(this.base_amount, this.base_token_info.decimal), 64 | quoteReserve: xWeiAmount( 65 | this.quote_amount, 66 | this.quote_token_info.decimal 67 | ), 68 | lpSupply: new BN(base_amount), 69 | startTime: new BN(0) 70 | }; 71 | } 72 | 73 | initializePoolInfo(market_id: PublicKey) { 74 | this.market_id = market_id; 75 | const { base_token, quote_token } = this.getPairToken(); 76 | this.pool_keys = Liquidity.getAssociatedPoolKeys({ 77 | version: 4, 78 | marketVersion: 3, 79 | baseMint: base_token.mint, 80 | quoteMint: quote_token.mint, 81 | baseDecimals: base_token.decimals, 82 | quoteDecimals: quote_token.decimals, 83 | marketId: this.market_id, 84 | programId: EnvironmentManager.getProgramID().AmmV4, 85 | marketProgramId: EnvironmentManager.getProgramID().OPENBOOK_MARKET 86 | }); 87 | 88 | this.pool_info = { 89 | status: new BN(0), 90 | baseDecimals: this.base_token_info.decimal, 91 | lpDecimals: this.quote_token_info.decimal, 92 | quoteDecimals: this.quote_token_info.decimal, 93 | baseReserve: xWeiAmount(this.base_amount, this.base_token_info.decimal), 94 | quoteReserve: xWeiAmount( 95 | this.quote_amount, 96 | this.quote_token_info.decimal 97 | ), 98 | lpSupply: new BN(this.base_amount), 99 | startTime: new BN(0) 100 | }; 101 | 102 | console.log( 103 | "Simulated Pool baseReserve: ", 104 | this.pool_info.baseReserve.toString() 105 | ); 106 | console.log( 107 | "Simulated Pool quoteReserve: ", 108 | this.pool_info.quoteReserve.toString() 109 | ); 110 | } 111 | 112 | computeSolAmount(base_amount: number, in_out: boolean): CurrencyAmount { 113 | const { base_token, quote_token } = this.getPairToken(); 114 | // console.log("Simulated PoolInfo: ", this.pool_info); 115 | if (in_out) { 116 | const { maxAmountIn } = Liquidity.computeAmountIn({ 117 | poolKeys: this.pool_keys, 118 | poolInfo: this.pool_info, 119 | amountOut: new TokenAmount(base_token, base_amount, false), 120 | currencyIn: quote_token, 121 | slippage: new Percent(1, 100) 122 | }); 123 | return maxAmountIn; 124 | } else { 125 | const { minAmountOut } = Liquidity.computeAmountOut({ 126 | poolKeys: this.pool_keys, 127 | poolInfo: this.pool_info, 128 | amountIn: new TokenAmount(base_token, base_amount, false), 129 | currencyOut: quote_token, 130 | slippage: new Percent(1, 100) 131 | }); 132 | return minAmountOut; 133 | } 134 | } 135 | computeCurrentPrice(): number { 136 | return this.quote_amount / this.base_amount; 137 | } 138 | buyToken(base_amount: number) { 139 | const sol_input = this.computeSolAmount(base_amount, true); 140 | const { base_token, quote_token } = this.getPairToken(); 141 | const { amountOut } = Liquidity.computeAmountOut({ 142 | poolKeys: this.pool_keys, 143 | poolInfo: this.pool_info, 144 | amountIn: sol_input, 145 | currencyOut: base_token, 146 | slippage: new Percent(1, 100) 147 | }); 148 | this.quote_amount += sol_input.raw 149 | .div(new BN(10 ** this.quote_token_info.decimal)) 150 | .toNumber(); 151 | this.base_amount -= base_amount; 152 | this.pool_info = { 153 | ...this.pool_info, 154 | baseReserve: this.pool_info.baseReserve.sub(amountOut.raw), 155 | quoteReserve: this.pool_info.quoteReserve.add(sol_input.raw) 156 | }; 157 | 158 | console.log( 159 | "Simulated Pool baseReserve: ", 160 | this.pool_info.baseReserve.toString() 161 | ); 162 | console.log( 163 | "Simulated Pool quoteReserve: ", 164 | this.pool_info.quoteReserve.toString() 165 | ); 166 | 167 | // this.initializePoolInfo(this.market_id); 168 | } 169 | sellToken(base_amount: number) { 170 | const sol_input = this.computeSolAmount(base_amount, false); 171 | this.quote_amount -= sol_input.raw 172 | .div(new BN(10 ** this.quote_token_info.decimal)) 173 | .toNumber(); 174 | this.base_amount += base_amount; 175 | this.initializePoolInfo(this.market_id); 176 | } 177 | getPairToken(): PairToken { 178 | const base_mint = new PublicKey(this.base_token_info.address); 179 | const base = new Token( 180 | TOKEN_PROGRAM_ID, 181 | base_mint, 182 | this.base_token_info.decimal, 183 | this.base_token_info.symbol, 184 | this.base_token_info.name 185 | ); 186 | 187 | const quote = new Token( 188 | TOKEN_PROGRAM_ID, 189 | new PublicKey(this.quote_token_info.address), 190 | this.quote_token_info.decimal, 191 | this.quote_token_info.symbol, 192 | this.quote_token_info.name 193 | ); 194 | 195 | return { base_token: base, quote_token: quote }; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/jito-kit/sell_token.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from "@solana/web3.js"; 2 | import { EnvironmentManager, SPL_ERROR, TX_RET } from "./global"; 3 | import { TOKEN_PROGRAM_ID, getMint } from "@solana/spl-token"; 4 | import { 5 | Liquidity, 6 | LiquidityPoolKeys, 7 | Token, 8 | TokenAmount, 9 | TxVersion, 10 | buildSimpleTransaction 11 | } from "@raydium-io/raydium-sdk"; 12 | import { getWalletAccounts } from "./utility"; 13 | 14 | export const sellToken = async ( 15 | connection: Connection, 16 | buyer: Keypair, 17 | token_address: string, 18 | base_amount: number, 19 | quote_amount: number, 20 | pool_key: LiquidityPoolKeys 21 | ): Promise => { 22 | if (token_address.length <= 0 || base_amount <= 0) { 23 | console.error("Error: [Buy Token] invalid argument iput!!!"); 24 | return { result: SPL_ERROR.E_INVALID_ARGUE, value: undefined }; 25 | } 26 | 27 | try { 28 | const token_mint = new PublicKey(token_address); 29 | const token_info = await getMint(connection, token_mint); 30 | const base_token = new Token( 31 | TOKEN_PROGRAM_ID, 32 | token_address, 33 | token_info.decimals 34 | ); 35 | const quote_info = EnvironmentManager.getQuoteTokenInfo(); 36 | const quote_token = new Token( 37 | TOKEN_PROGRAM_ID, 38 | quote_info.address, 39 | quote_info.decimal, 40 | quote_info.symbol, 41 | quote_info.name 42 | ); 43 | const base_token_amount = new TokenAmount(base_token, base_amount, false); 44 | const quote_token_amount = new TokenAmount( 45 | quote_token, 46 | quote_amount === 0 ? 1 : quote_amount, 47 | quote_amount === 0 ? true : false 48 | ); 49 | 50 | const wallet_token_accounts = await getWalletAccounts( 51 | connection, 52 | buyer.publicKey 53 | ); 54 | 55 | const { innerTransactions } = await Liquidity.makeSwapInstructionSimple({ 56 | connection: connection, 57 | poolKeys: pool_key, 58 | userKeys: { 59 | tokenAccounts: wallet_token_accounts, 60 | owner: buyer.publicKey 61 | }, 62 | amountIn: base_token_amount, 63 | amountOut: quote_token_amount, 64 | fixedSide: "in", 65 | makeTxVersion: TxVersion.V0 66 | }); 67 | 68 | const transactions = await buildSimpleTransaction({ 69 | connection: connection, 70 | makeTxVersion: TxVersion.V0, 71 | payer: buyer.publicKey, 72 | innerTransactions: innerTransactions, 73 | addLookupTableInfo: EnvironmentManager.getCacheLTA(), 74 | recentBlockhash: (await connection.getLatestBlockhash()).blockhash 75 | }); 76 | 77 | return { result: SPL_ERROR.E_OK, value: transactions }; 78 | } catch (error) { 79 | console.error("Error: [buy Tokens] error code: ", error); 80 | return { result: SPL_ERROR.E_FAIL, value: undefined }; 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/jito-kit/transaction-helper/check_transaction.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | export const checkTransactions = (txn: any, signer: Keypair): boolean => { 5 | if ( 6 | signer.publicKey.toBuffer().length <= 0 || 7 | signer.secretKey.buffer.byteLength <= 0 8 | ) { 9 | return false; 10 | } 11 | 12 | const check_sign = bs58.encode(signer.secretKey); 13 | if (check_sign.length <= 0) { 14 | return false; 15 | } 16 | 17 | return true; 18 | }; 19 | -------------------------------------------------------------------------------- /src/jito-kit/transaction-helper/transaction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | Transaction, 5 | TransactionConfirmationStrategy, 6 | TransactionSignature, 7 | VersionedTransaction, 8 | sendAndConfirmTransaction, 9 | } from "@solana/web3.js"; 10 | import { checkTransactions } from "./check_transaction"; 11 | import { SPL_ERROR } from "../global"; 12 | 13 | export const sendAndConfirmTransactionWithCheck = async ( 14 | connection: Connection, 15 | signer: Keypair, 16 | txn: Transaction | VersionedTransaction 17 | ): Promise => { 18 | try { 19 | if (checkTransactions(txn, signer) === false) { 20 | return SPL_ERROR.E_CHECK_FAIL; 21 | } 22 | 23 | let res: any, signature: TransactionSignature; 24 | if (txn instanceof Transaction) { 25 | txn.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; 26 | signature = await connection.sendTransaction(txn, [signer]); 27 | } else { 28 | txn.sign([signer]); 29 | signature = await connection.sendTransaction(txn); 30 | } 31 | 32 | if (signature.length <= 0) { 33 | console.log("Error: [Send Transaction] failed... "); 34 | return SPL_ERROR.E_SEND_TX_FAIL; 35 | } 36 | 37 | const txnId = await connection.confirmTransaction({ 38 | signature: signature, 39 | abortSignal: AbortSignal.timeout(90000), 40 | } as TransactionConfirmationStrategy); 41 | 42 | if (txnId.value.err) { 43 | console.log("Error: [Confirm Transaction] failed - ", txnId.value.err); 44 | return SPL_ERROR.E_CONFIRM_TX_FAIL; 45 | } 46 | } catch (error) { 47 | console.log("Error: [Confirm Transaction] failed - ", error); 48 | return SPL_ERROR.E_FAIL; 49 | } 50 | 51 | return SPL_ERROR.E_OK; 52 | }; 53 | 54 | export const sendAndConfirmTransactionsWithCheck = async ( 55 | connection: Connection, 56 | signer: Keypair, 57 | txns: string | (VersionedTransaction | Transaction)[] 58 | ): Promise => { 59 | for (const txn of txns) { 60 | if (txn instanceof VersionedTransaction || txn instanceof Transaction) { 61 | const txn_res = await sendAndConfirmTransactionWithCheck( 62 | connection, 63 | signer, 64 | txn 65 | ); 66 | 67 | if (txn_res !== SPL_ERROR.E_OK) { 68 | return SPL_ERROR.E_FAIL; 69 | } 70 | } 71 | } 72 | return SPL_ERROR.E_OK; 73 | }; 74 | 75 | export const signTransaction = (signer: Keypair, txn: VersionedTransaction) => { 76 | if (checkTransactions(txn, signer)) { 77 | txn.sign([signer]); 78 | } 79 | }; 80 | 81 | export const signTransactions = ( 82 | signer: Keypair, 83 | txns: (VersionedTransaction | Transaction)[] 84 | ) => { 85 | for (const txn of txns) { 86 | if (txn instanceof VersionedTransaction) { 87 | signTransaction(signer, txn); 88 | } 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /src/jito-kit/utility.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import * as fs from "fs"; 3 | import BigNumber from "bignumber.js"; 4 | import { Commitment, Connection, PublicKey } from "@solana/web3.js"; 5 | import { EnvironmentManager } from "./global"; 6 | import { TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from "@solana/spl-token"; 7 | import { 8 | Liquidity, 9 | LiquidityPoolInfo, 10 | LiquidityPoolKeys, 11 | SPL_ACCOUNT_LAYOUT, 12 | Token, 13 | findProgramAddress 14 | } from "@raydium-io/raydium-sdk"; 15 | import { MARKET_STATE_LAYOUT_V3 } from "@project-serum/serum"; 16 | 17 | export async function checkFileExists(filePath: string): Promise { 18 | try { 19 | await fs.promises.access(filePath, fs.constants.F_OK); 20 | return true; // File exists 21 | } catch (error) { 22 | return false; // File doesn't exist 23 | } 24 | } 25 | 26 | export const xWeiAmount = (amount: number, decimals: number) => { 27 | return new BN( 28 | new BigNumber(amount.toString() + "e" + decimals.toString()).toFixed(0) 29 | ); 30 | }; 31 | 32 | export const getConnection = (commitment: Commitment): Connection => { 33 | return new Connection(EnvironmentManager.getRpcNetUrl(), commitment); 34 | }; 35 | 36 | export const getWalletAccounts = async ( 37 | connection: Connection, 38 | wallet: PublicKey 39 | ) => { 40 | const wallet_token_account = await connection.getTokenAccountsByOwner( 41 | wallet, 42 | { 43 | programId: TOKEN_PROGRAM_ID 44 | } 45 | ); 46 | 47 | return wallet_token_account.value.map((i) => ({ 48 | pubkey: i.pubkey, 49 | programId: i.account.owner, 50 | accountInfo: SPL_ACCOUNT_LAYOUT.decode(i.account.data) 51 | })); 52 | }; 53 | 54 | export const getAvailablePoolKeyAndPoolInfo = async ( 55 | connection: Connection, 56 | baseToken: Token, 57 | quoteToken: Token, 58 | marketAccounts: any 59 | ): Promise<{ 60 | poolKeys: any; 61 | poolInfo: any; 62 | }> => { 63 | let bFound = false; 64 | let count = 0; 65 | let poolKeys: any; 66 | let poolInfo: any; 67 | 68 | while (bFound === false && count < marketAccounts.length) { 69 | const marketInfo = MARKET_STATE_LAYOUT_V3.decode( 70 | marketAccounts[count].accountInfo.data 71 | ); 72 | 73 | poolKeys = Liquidity.getAssociatedPoolKeys({ 74 | version: 4, 75 | marketVersion: 3, 76 | baseMint: baseToken.mint, 77 | quoteMint: quoteToken.mint, 78 | baseDecimals: baseToken.decimals, 79 | quoteDecimals: quoteToken.decimals, 80 | marketId: marketAccounts[count].publicKey, 81 | programId: EnvironmentManager.getProgramID().AmmV4, 82 | marketProgramId: EnvironmentManager.getProgramID().OPENBOOK_MARKET 83 | }); 84 | 85 | poolKeys.marketBaseVault = marketInfo.baseVault; 86 | poolKeys.marketQuoteVault = marketInfo.quoteVault; 87 | poolKeys.marketBids = marketInfo.bids; 88 | poolKeys.marketAsks = marketInfo.asks; 89 | poolKeys.marketEventQueue = marketInfo.eventQueue; 90 | 91 | try { 92 | poolInfo = await Liquidity.fetchInfo({ 93 | connection: connection, 94 | poolKeys: poolKeys 95 | }); 96 | 97 | bFound = true; 98 | console.log("Success to get pool infos..."); 99 | } catch (error) { 100 | bFound = false; 101 | poolInfo = undefined; 102 | poolKeys = undefined; 103 | console.log("Failed to get pool infos..."); 104 | } 105 | 106 | count++; 107 | } 108 | 109 | return { 110 | poolKeys: poolKeys, 111 | poolInfo: poolInfo 112 | }; 113 | }; 114 | 115 | export function getATAAddress( 116 | programId: PublicKey, 117 | owner: PublicKey, 118 | mint: PublicKey 119 | ) { 120 | const { publicKey, nonce } = findProgramAddress( 121 | [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], 122 | new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") 123 | ); 124 | return { publicKey, nonce }; 125 | } 126 | 127 | export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); 128 | -------------------------------------------------------------------------------- /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 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "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. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 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 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------