├── constants ├── index.ts └── constants.ts ├── market ├── index.ts └── market.ts ├── liquidity ├── index.ts └── liquidity.ts ├── utils ├── index.ts ├── logger.ts └── utils.ts ├── .env copy ├── README.md ├── package.json ├── buffer └── buffer.ts ├── test.ts ├── .gitignore ├── streaming ├── openbook.ts └── raydium.ts ├── jito └── bundle.ts ├── transaction └── transaction.ts └── tsconfig.json /constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; -------------------------------------------------------------------------------- /market/index.ts: -------------------------------------------------------------------------------- 1 | export * from './market'; 2 | -------------------------------------------------------------------------------- /liquidity/index.ts: -------------------------------------------------------------------------------- 1 | export * from './liquidity'; 2 | -------------------------------------------------------------------------------- /utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export * from './logger'; -------------------------------------------------------------------------------- /utils/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | const transport = pino.transport({ 4 | target: 'pino-pretty', 5 | }); 6 | 7 | export const logger = pino( 8 | { 9 | level: 'info', 10 | redact: ['poolKeys'], 11 | serializers: { 12 | error: pino.stdSerializers.err, 13 | }, 14 | base: undefined, 15 | }, 16 | transport, 17 | ); 18 | -------------------------------------------------------------------------------- /utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from 'pino'; 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | export const retrieveEnvVariable = (variableName: string, logger: Logger) => { 7 | const variable = process.env[variableName] || ''; 8 | if (!variable) { 9 | logger.error(`${variableName} is not set`); 10 | process.exit(1); 11 | } 12 | return variable; 13 | }; 14 | -------------------------------------------------------------------------------- /.env copy: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | RPC_ENDPOINT=http://grpc.solanavibestation.com 3 | RPC_WEBSOCKET_ENDPOINT=wss://grpc.solanavibestation.com 4 | QUOTE_MINT=WSOL 5 | QUOTE_AMOUNT=0.01 6 | COMMITMENT_LEVEL=confirmed 7 | LOG_LEVEL=info 8 | CHECK_IF_IS_BURNED=false 9 | CHECK_IF_IS_LOCKED=false 10 | CHECK_IF_MINT_IS_RENOUNCED=false 11 | AUTO_SELL=false 12 | MAX_SELL_RETRIES=20 13 | AUTO_SELL_DELAY=50 14 | MIN_POOL_SIZE=0 15 | 16 | BLOCK_ENGINE_URL=ny.mainnet.block-engine.jito.wtf 17 | AUTH_KEYPAIR_PATH=./id-bundles.json 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | - Jito bundle engine keypair. Please place this keypaid as `id-bundles.json` in the repository folder. 3 | 4 | 5 | # Instructions 6 | - Add Jito bundle engine keypair .json file to directory 7 | - Rename `.env.copy` to `.env` 8 | - Add Solana Vibe Station API key to both the `RPC_ENDPOINT` and `RPC_WEBSOCKET_ENDPOINT` fields in the .env file 9 | - Add your private key in base64 format which can be exported from either Phantom or derived from your JSON keypair for your wallet. 10 | 11 | -------------------------------------------------------------------------------- /market/market.ts: -------------------------------------------------------------------------------- 1 | import { Commitment, Connection, PublicKey } from '@solana/web3.js'; 2 | import { GetStructureSchema, MARKET_STATE_LAYOUT_V3, LiquidityStateV4, Token, TokenAmount } from '@raydium-io/raydium-sdk'; 3 | import { MINIMAL_MARKET_STATE_LAYOUT_V3 } from '../liquidity'; 4 | import BN from 'bn.js'; 5 | import { logger } from '../utils'; 6 | 7 | export type MinimalMarketStateLayoutV3 = typeof MINIMAL_MARKET_STATE_LAYOUT_V3; 8 | export type MinimalMarketLayoutV3 = 9 | GetStructureSchema; 10 | 11 | export async function getMinimalMarketV3( 12 | connection: Connection, 13 | marketId: PublicKey, 14 | commitment?: Commitment, 15 | ): Promise { 16 | const marketInfo = await connection.getAccountInfo(marketId, { 17 | commitment, 18 | dataSlice: { 19 | offset: MARKET_STATE_LAYOUT_V3.offsetOf('eventQueue'), 20 | length: 32 * 3, 21 | }, 22 | }); 23 | 24 | return MINIMAL_MARKET_STATE_LAYOUT_V3.decode(marketInfo!.data); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test.ts", 3 | "version": "1.0.0", 4 | "main": "test.ts", 5 | "scripts": { 6 | "test": "ts-node test.ts" 7 | }, 8 | "dependencies": { 9 | "@project-serum/serum": "^0.13.65", 10 | "@raydium-io/raydium-sdk": "^1.3.1-beta.47", 11 | "@solana/spl-token": "^0.4.0", 12 | "@solana/web3.js": "^1.89.1", 13 | "@triton-one/yellowstone-grpc": "^0.4.0", 14 | "bigint-buffer": "^1.1.5", 15 | "bn.js": "^5.2.1", 16 | "bs58": "^5.0.0", 17 | "dotenv": "^16.4.1", 18 | "global-agent": "^3.0.0", 19 | "global-tunnel-ng": "^2.7.1", 20 | "http-proxy-agent": "^7.0.2", 21 | "https-proxy-agent": "^7.0.4", 22 | "jito-ts": "^3.0.1", 23 | "pino": "^8.18.0", 24 | "pino-pretty": "^10.3.1", 25 | "pino-std-serializers": "^6.2.2" 26 | }, 27 | "devDependencies": { 28 | "@types/bn.js": "^5.1.5", 29 | "@types/global-tunnel-ng": "^2.1.4", 30 | "prettier": "^3.2.4", 31 | "ts-node": "^10.9.2", 32 | "typescript": "^5.3.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /constants/constants.ts: -------------------------------------------------------------------------------- 1 | import { Commitment } from "@solana/web3.js"; 2 | import { logger, retrieveEnvVariable } from "../utils"; 3 | 4 | export const NETWORK = 'mainnet-beta'; 5 | export const COMMITMENT_LEVEL: Commitment = retrieveEnvVariable('COMMITMENT_LEVEL', logger) as Commitment; 6 | export const RPC_ENDPOINT = retrieveEnvVariable('RPC_ENDPOINT', logger); 7 | export const RPC_WEBSOCKET_ENDPOINT = retrieveEnvVariable('RPC_WEBSOCKET_ENDPOINT', logger); 8 | export const LOG_LEVEL = retrieveEnvVariable('LOG_LEVEL', logger); 9 | export const CHECK_IF_MINT_IS_RENOUNCED = retrieveEnvVariable('CHECK_IF_MINT_IS_RENOUNCED', logger) === 'true'; 10 | export const AUTO_SELL = retrieveEnvVariable('AUTO_SELL', logger) === 'true'; 11 | export const MAX_SELL_RETRIES = Number(retrieveEnvVariable('MAX_SELL_RETRIES', logger)); 12 | export const AUTO_SELL_DELAY = Number(retrieveEnvVariable('AUTO_SELL_DELAY', logger)); 13 | export const PRIVATE_KEY = retrieveEnvVariable('PRIVATE_KEY', logger); 14 | export const QUOTE_MINT = retrieveEnvVariable('QUOTE_MINT', logger); 15 | export const QUOTE_AMOUNT = retrieveEnvVariable('QUOTE_AMOUNT', logger); 16 | -------------------------------------------------------------------------------- /buffer/buffer.ts: -------------------------------------------------------------------------------- 1 | import { MARKET_STATE_LAYOUT_V3 } from '@raydium-io/raydium-sdk'; 2 | import { PublicKey } from '@solana/web3.js'; 3 | import { Buffer } from 'buffer'; 4 | 5 | export class BufferRingBuffer { 6 | private buffer: Array; 7 | private head: number; 8 | private tail: number; 9 | private count: number; 10 | private capacity: number; 11 | 12 | constructor(capacity: number) { 13 | this.capacity = capacity; 14 | this.buffer = new Array(capacity).fill(null); 15 | this.head = 0; 16 | this.tail = 0; 17 | this.count = 0; 18 | } 19 | 20 | isFull(): boolean { 21 | return this.count === this.capacity; 22 | } 23 | 24 | isEmpty(): boolean { 25 | return this.count === 0; 26 | } 27 | 28 | enqueue(item: Buffer): void { 29 | if (this.isFull()) { 30 | this.head = (this.head + 1) % this.capacity; 31 | } else { 32 | this.count++; 33 | } 34 | this.buffer[this.tail] = item; 35 | this.tail = (this.tail + 1) % this.capacity; 36 | } 37 | 38 | dequeue(): Buffer | null { 39 | if (this.isEmpty()) { 40 | return null; 41 | } 42 | const item = this.buffer[this.head]; 43 | this.buffer[this.head] = null; 44 | this.head = (this.head + 1) % this.capacity; 45 | this.count--; 46 | return item; 47 | } 48 | 49 | findPattern(publicKey: PublicKey): Buffer | false { 50 | const publicKeyBuffer = publicKey.toBuffer(); 51 | const baseMintOffset = MARKET_STATE_LAYOUT_V3.offsetOf('baseMint'); 52 | const baseMintEndOffset = baseMintOffset + publicKeyBuffer.length; 53 | 54 | for (let i = this.capacity - 1; i >= 0; i--) { 55 | if (this.buffer[i]) { 56 | const baseMintInBuffer = this.buffer[i]!.slice(baseMintOffset, baseMintEndOffset); 57 | if (baseMintInBuffer.equals(publicKeyBuffer)) { 58 | console.log(i); 59 | return this.buffer[i] as Buffer; // PublicKey found 60 | } 61 | } 62 | } 63 | return false; // PublicKey not found 64 | } 65 | 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import { streamNewTokens } from './streaming/raydium'; 2 | import { streamOpenbook } from './streaming/openbook'; 3 | 4 | require('dotenv').config(); 5 | 6 | 7 | import * as Fs from 'fs'; 8 | 9 | import { Connection, Keypair, PublicKey, TransactionMessage, VersionedMessage, VersionedTransaction } from '@solana/web3.js'; 10 | import { logger } from './utils/logger'; 11 | import { init } from './transaction/transaction'; 12 | 13 | const blockEngineUrl = process.env.BLOCK_ENGINE_URL || ''; 14 | console.log('BLOCK_ENGINE_URL:', blockEngineUrl); 15 | 16 | // const authKeypairPath = process.env.AUTH_KEYPAIR_PATH || ''; 17 | // console.log('AUTH_KEYPAIR_PATH:', authKeypairPath); 18 | // const decodedKey = new Uint8Array( 19 | // JSON.parse(Fs.readFileSync(authKeypairPath).toString()) as number[] 20 | // ); 21 | // const keypair = Keypair.fromSecretKey(decodedKey); 22 | 23 | import { 24 | ChannelCredentials, 25 | ChannelOptions, 26 | ClientReadableStream, 27 | ServiceError, 28 | } from '@grpc/grpc-js'; 29 | 30 | 31 | 32 | 33 | import { SearcherServiceClient } from 'jito-ts/dist/gen/block-engine/searcher' 34 | import { AuthServiceClient } from 'jito-ts/dist/gen/block-engine/auth'; 35 | import { authInterceptor, AuthProvider } from 'jito-ts/dist/sdk/block-engine/auth'; 36 | 37 | 38 | 39 | async function start() { 40 | 41 | await init(); 42 | 43 | streamNewTokens(); 44 | streamOpenbook(); 45 | 46 | } 47 | 48 | const searcherClient = ( 49 | url: string, 50 | authKeypair: Keypair, 51 | grpcOptions?: Partial 52 | ): SearcherServiceClient => { 53 | const authProvider = new AuthProvider( 54 | new AuthServiceClient(url, ChannelCredentials.createSsl()), 55 | authKeypair 56 | ); 57 | const client: SearcherServiceClient = new SearcherServiceClient( 58 | url, 59 | ChannelCredentials.createSsl(), 60 | { interceptors: [authInterceptor(authProvider)], ...grpcOptions } 61 | ); 62 | 63 | return client; 64 | } 65 | 66 | import { Transaction } from '@solana/web3.js'; 67 | import * as bs58 from 'bs58'; 68 | import { TokenInstructions } from '@project-serum/serum'; 69 | import { Liquidity, TOKEN_PROGRAM_ID } from '@raydium-io/raydium-sdk'; 70 | import { RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT } from './constants'; 71 | import { AccountLayout } from '@solana/spl-token'; 72 | const connection = new Connection(RPC_ENDPOINT, { 73 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, 74 | }); 75 | async function test() { 76 | const poolState = await connection.getProgramAccounts(new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8')); 77 | 78 | logger.info(poolState); 79 | 80 | } 81 | 82 | start(); 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | id-bundles.json 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | -------------------------------------------------------------------------------- /liquidity/liquidity.ts: -------------------------------------------------------------------------------- 1 | import { Commitment, Connection, PublicKey } from '@solana/web3.js'; 2 | import { 3 | Liquidity, 4 | LiquidityPoolKeys, 5 | Market, 6 | TokenAccount, 7 | SPL_ACCOUNT_LAYOUT, 8 | publicKey, 9 | struct, 10 | MAINNET_PROGRAM_ID, 11 | LiquidityStateV4, 12 | } from '@raydium-io/raydium-sdk'; 13 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; 14 | import { MinimalMarketLayoutV3 } from '../market'; 15 | 16 | export const RAYDIUM_LIQUIDITY_PROGRAM_ID_V4 = MAINNET_PROGRAM_ID.AmmV4; 17 | export const OPENBOOK_PROGRAM_ID = MAINNET_PROGRAM_ID.OPENBOOK_MARKET; 18 | 19 | export const MINIMAL_MARKET_STATE_LAYOUT_V3 = struct([ 20 | publicKey('eventQueue'), 21 | publicKey('bids'), 22 | publicKey('asks'), 23 | ]); 24 | 25 | export function createPoolKeys( 26 | id: PublicKey, 27 | accountData: LiquidityStateV4, 28 | minimalMarketLayoutV3: MinimalMarketLayoutV3, 29 | ): LiquidityPoolKeys { 30 | return { 31 | id, 32 | baseMint: accountData.baseMint, 33 | quoteMint: accountData.quoteMint, 34 | lpMint: accountData.lpMint, 35 | baseDecimals: accountData.baseDecimal.toNumber(), 36 | quoteDecimals: accountData.quoteDecimal.toNumber(), 37 | lpDecimals: 5, 38 | version: 4, 39 | programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, 40 | authority: Liquidity.getAssociatedAuthority({ 41 | programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, 42 | }).publicKey, 43 | openOrders: accountData.openOrders, 44 | targetOrders: accountData.targetOrders, 45 | baseVault: accountData.baseVault, 46 | quoteVault: accountData.quoteVault, 47 | marketVersion: 3, 48 | marketProgramId: accountData.marketProgramId, 49 | marketId: accountData.marketId, 50 | marketAuthority: Market.getAssociatedAuthority({ 51 | programId: accountData.marketProgramId, 52 | marketId: accountData.marketId, 53 | }).publicKey, 54 | marketBaseVault: accountData.baseVault, 55 | marketQuoteVault: accountData.quoteVault, 56 | marketBids: minimalMarketLayoutV3.bids, 57 | marketAsks: minimalMarketLayoutV3.asks, 58 | marketEventQueue: minimalMarketLayoutV3.eventQueue, 59 | withdrawQueue: accountData.withdrawQueue, 60 | lpVault: accountData.lpVault, 61 | lookupTableAccount: PublicKey.default, 62 | }; 63 | } 64 | 65 | export async function getTokenAccounts( 66 | connection: Connection, 67 | owner: PublicKey, 68 | commitment?: Commitment, 69 | ) { 70 | const tokenResp = await connection.getTokenAccountsByOwner( 71 | owner, 72 | { 73 | programId: TOKEN_PROGRAM_ID, 74 | }, 75 | commitment, 76 | ); 77 | 78 | const accounts: TokenAccount[] = []; 79 | for (const { pubkey, account } of tokenResp.value) { 80 | accounts.push({ 81 | pubkey, 82 | programId: account.owner, 83 | accountInfo: SPL_ACCOUNT_LAYOUT.decode(account.data), 84 | }); 85 | } 86 | 87 | return accounts; 88 | } 89 | -------------------------------------------------------------------------------- /streaming/openbook.ts: -------------------------------------------------------------------------------- 1 | import { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc"; 2 | import pino from "pino"; 3 | const transport = pino.transport({ 4 | target: 'pino-pretty', 5 | }); 6 | 7 | export const logger = pino( 8 | { 9 | level: 'info', 10 | serializers: { 11 | error: pino.stdSerializers.err, 12 | }, 13 | base: undefined, 14 | }, 15 | transport, 16 | ); 17 | 18 | 19 | import Client from "@triton-one/yellowstone-grpc"; 20 | import { MARKET_STATE_LAYOUT_V3 } from "@raydium-io/raydium-sdk"; 21 | import { PublicKey } from "@solana/web3.js"; 22 | import { BufferRingBuffer } from "../buffer/buffer"; 23 | 24 | const client = new Client("https://grpc.solanavibestation.com", undefined, undefined); 25 | 26 | //Initialize Ring Buffer 27 | 28 | // This portion of the code streams the Openbook data which is needed to make a buy request. 29 | // The data is stored in a buffer ring and then queried if the raydium stream gets a liquidity event. 30 | // We stream and store this data because a lot of the time the data we need from here is streamed before the raydium stream gets the liquidity event. 31 | // This way we can store the data and then query it when we need it instead of making a slow web request to get the data. 32 | // Many times it will not contain the data we need in which case the buy will be aborted. A trade-off for speed. 33 | 34 | // I know somebody can probably improve this a lot. I'm not a pro at this stuff. I'm just a guy who likes to code. 35 | 36 | export const bufferRing = new BufferRingBuffer(5000); 37 | 38 | export async function streamOpenbook() { 39 | const stream = await client.subscribe(); 40 | // Collecting all incoming events. 41 | stream.on("data", (data) => { 42 | if (data.account != undefined) { 43 | bufferRing.enqueue(data.account.account.data); 44 | } 45 | }); 46 | 47 | const openBookRequest: SubscribeRequest = { 48 | "slots": {}, 49 | "accounts": { 50 | "raydium": { 51 | "account": [], 52 | "filters": [ 53 | { 54 | "memcmp": { 55 | "offset": MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint').toString(), 56 | "base58": "So11111111111111111111111111111111111111112" 57 | } 58 | } 59 | ], 60 | "owner": ["srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"] //Openbook program ID 61 | } 62 | }, 63 | "transactions": {}, 64 | "blocks": {}, 65 | "blocksMeta": {}, 66 | "accountsDataSlice": [], 67 | "commitment": CommitmentLevel.PROCESSED, 68 | entry: {} 69 | } 70 | // Sending a subscription request. 71 | await new Promise((resolve, reject) => { 72 | stream.write(openBookRequest, (err: null | undefined) => { 73 | if (err === null || err === undefined) { 74 | resolve(); 75 | } else { 76 | reject(err); 77 | } 78 | }); 79 | }).catch((reason) => { 80 | console.error(reason); 81 | throw reason; 82 | }); 83 | } -------------------------------------------------------------------------------- /jito/bundle.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | VersionedTransaction, 6 | MessageV0 7 | } 8 | from '@solana/web3.js'; 9 | 10 | import { Bundle } from 'jito-ts/dist/sdk/block-engine/types'; 11 | 12 | import * as Fs from 'fs'; 13 | 14 | 15 | require('dotenv').config(); 16 | 17 | 18 | import { searcherClient } from 'jito-ts/dist/sdk/block-engine/searcher'; 19 | 20 | import { 21 | ChannelCredentials, 22 | ChannelOptions, 23 | ClientReadableStream, 24 | ServiceError, 25 | } from '@grpc/grpc-js'; 26 | 27 | 28 | 29 | 30 | import { SearcherServiceClient } from 'jito-ts/dist/gen/block-engine/searcher' 31 | import { AuthServiceClient } from 'jito-ts/dist/gen/block-engine/auth'; 32 | import { authInterceptor, AuthProvider } from 'jito-ts/dist/sdk/block-engine/auth'; 33 | 34 | 35 | import { 36 | PRIVATE_KEY, 37 | RPC_ENDPOINT, 38 | RPC_WEBSOCKET_ENDPOINT, 39 | } from '../constants'; 40 | 41 | import bs58 from 'bs58'; 42 | import { logger } from '../utils/logger'; 43 | import { bundle } from 'jito-ts'; 44 | 45 | function sleep(ms: number) { 46 | return new Promise((resolve) => setTimeout(resolve, ms)); 47 | } 48 | 49 | const SIGNER_WALLET = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)); 50 | 51 | 52 | const blockEngineUrl = process.env.BLOCK_ENGINE_URL || ''; 53 | console.log('BLOCK_ENGINE_URL:', blockEngineUrl); 54 | 55 | // const authKeypairPath = process.env.AUTH_KEYPAIR_PATH || ''; 56 | // console.log('AUTH_KEYPAIR_PATH:', authKeypairPath); 57 | // const decodedKey = new Uint8Array( 58 | // JSON.parse(Fs.readFileSync(authKeypairPath).toString()) as number[] 59 | // ); 60 | // const keypair = Keypair.fromSecretKey(decodedKey); 61 | 62 | // const c = searcherClient(blockEngineUrl, keypair); 63 | const keypair = Keypair.generate(); 64 | const c = searcherClient(blockEngineUrl, keypair); 65 | 66 | 67 | export const searcherClientAdv = ( 68 | url: string, 69 | authKeypair: Keypair, 70 | grpcOptions?: Partial 71 | ): SearcherServiceClient => { 72 | const authProvider = new AuthProvider( 73 | new AuthServiceClient(url, ChannelCredentials.createSsl()), 74 | authKeypair 75 | ); 76 | const client: SearcherServiceClient = new SearcherServiceClient( 77 | url, 78 | ChannelCredentials.createSsl(), 79 | { interceptors: [authInterceptor(authProvider)], ...grpcOptions } 80 | ); 81 | 82 | return client; 83 | } 84 | 85 | 86 | // Get Tip Accounts 87 | 88 | let tipAccounts: string[] = []; 89 | (async () => { 90 | try { 91 | tipAccounts = await c.getTipAccounts(); 92 | console.log('Result:', tipAccounts); 93 | } catch (error) { 94 | console.error('Error:', error); 95 | } 96 | })(); 97 | 98 | 99 | 100 | export async function sendBundle(latestBlockhash: string, message: MessageV0, mint: PublicKey) { 101 | 102 | try { 103 | 104 | const transaction = new VersionedTransaction(message); 105 | 106 | transaction.sign([SIGNER_WALLET]); 107 | 108 | 109 | 110 | 111 | logger.info(`Fetching and adding tip`); 112 | 113 | const _tipAccount = tipAccounts[Math.floor(Math.random() * 6)]; 114 | const tipAccount = new PublicKey(_tipAccount); 115 | 116 | 117 | 118 | const b = new Bundle([transaction], 2); 119 | b.addTipTx( 120 | SIGNER_WALLET, 121 | 150_000, // Adjust Jito tip amount here 122 | tipAccount, 123 | latestBlockhash 124 | ); 125 | 126 | 127 | logger.info(`Sending bundle`); 128 | const bundleResult = await c.sendBundle(b); 129 | logger.info(`Sent bundle! bundleResult = ${bundleResult}`); 130 | 131 | 132 | 133 | logger.info( 134 | { 135 | dex:`https://dexscreener.com/solana/${mint}?maker=${SIGNER_WALLET.publicKey}` 136 | }, 137 | ); 138 | 139 | 140 | } 141 | 142 | catch (error) { 143 | logger.error(error); 144 | 145 | } 146 | 147 | } 148 | 149 | // Get leader schedule 150 | 151 | // This was when I was experimenting with only sending the buy tx when a Jito leader was up or going to be up in the next slot so that I wouldn't 152 | // have to wait multiple slots for the tx to be processed. I ended up not using this feature as it couldn't get it working correctly before I moved on. 153 | 154 | export async function storeJitoLeaderSchedule() { 155 | 156 | const cs = searcherClientAdv(blockEngineUrl, keypair); 157 | 158 | 159 | const leaderSchedule = new Set(); 160 | 161 | cs.getConnectedLeadersRegioned({ regions: ["tokyo", "amsterdam", "ny", "frankfurt"] }, (error, response) => { 162 | 163 | 164 | for (let key in response) { 165 | if (key === 'connectedValidators') { 166 | let validators = response[key]; 167 | for (let validatorKey in validators) { 168 | // Each validator object 169 | let validator = validators[validatorKey]; 170 | // Assuming `slots` is an array inside each validator object 171 | Object.keys(validator.connectedValidators).forEach((key: string) => { 172 | const slotsArray: number[][] = Object.values(validator.connectedValidators[key]); // Assume SlotList is an array of arrays 173 | const flattenedSlotsArray: number[] = slotsArray.flat(); // Flatten the array 174 | flattenedSlotsArray.forEach((slot: number) => { 175 | leaderSchedule.add(slot); 176 | }); 177 | }); 178 | } 179 | } 180 | } 181 | 182 | //console.log(leaderSchedule); 183 | }); 184 | 185 | return leaderSchedule; 186 | } -------------------------------------------------------------------------------- /streaming/raydium.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CommitmentLevel, 3 | SubscribeRequest, 4 | } from "@triton-one/yellowstone-grpc"; 5 | import pino from "pino"; 6 | const transport = pino.transport({ 7 | target: "pino-pretty", 8 | }); 9 | 10 | export const logger = pino( 11 | { 12 | level: "info", 13 | serializers: { 14 | error: pino.stdSerializers.err, 15 | }, 16 | base: undefined, 17 | }, 18 | transport 19 | ); 20 | 21 | import Client from "@triton-one/yellowstone-grpc"; 22 | import { 23 | LIQUIDITY_STATE_LAYOUT_V4, 24 | MARKET_STATE_LAYOUT_V3, 25 | } from "@raydium-io/raydium-sdk"; 26 | import { PublicKey } from "@solana/web3.js"; 27 | import { bufferRing } from "./openbook"; 28 | import { buy } from "../transaction/transaction"; 29 | import { storeJitoLeaderSchedule } from "../jito/bundle"; 30 | 31 | // Array to store Jito leaders for current epoch 32 | let leaderSchedule = new Set(); 33 | 34 | // Function to populate the Jito leader array 35 | export async function populateJitoLeaderArray() { 36 | leaderSchedule = await storeJitoLeaderSchedule(); 37 | } 38 | 39 | // uncomment this line to enable Jito leader schedule check and delete the return line. 40 | function slotExists(slot: number): boolean { 41 | //return leaderSchedule.has(slot); 42 | return true; 43 | } 44 | 45 | const client = new Client( 46 | "https://grpc.solanavibestation.com", 47 | undefined, 48 | undefined 49 | ); //grpc endpoint from Solana Vibe Station obviously 50 | 51 | (async () => { 52 | const version = await client.getVersion(); // gets the version information 53 | console.log(version); 54 | })(); 55 | 56 | let latestBlockHash: string = ""; 57 | 58 | export async function streamNewTokens() { 59 | const stream = await client.subscribe(); 60 | // Collecting all incoming events. 61 | stream.on("data", (data) => { 62 | if (data.blockMeta) { 63 | latestBlockHash = data.blockMeta.blockhash; 64 | } 65 | 66 | if (data.account != undefined) { 67 | logger.info(`New token alert!`); 68 | let slotCheckResult = false; 69 | let slotCheck = Number(data.account.slot); 70 | for (let i = 0; i < 2; i++) { 71 | logger.info(`Start slot check. Attempt ${i}`); 72 | const exists = slotExists(slotCheck + i); 73 | logger.info(`End slot check`); 74 | if (exists === true) { 75 | slotCheckResult = true; 76 | break; 77 | } 78 | } 79 | 80 | if (slotCheckResult) { 81 | const poolstate = LIQUIDITY_STATE_LAYOUT_V4.decode( 82 | data.account.account.data 83 | ); 84 | const tokenAccount = new PublicKey(data.account.account.pubkey); 85 | logger.info(`Token Account: ${tokenAccount}`); 86 | 87 | let attempts = 0; 88 | const maxAttempts = 2; 89 | 90 | const intervalId = setInterval(async () => { 91 | const marketDetails = bufferRing.findPattern(poolstate.baseMint); 92 | if (Buffer.isBuffer(marketDetails)) { 93 | const fullMarketDetailsDecoded = 94 | MARKET_STATE_LAYOUT_V3.decode(marketDetails); 95 | const marketDetailsDecoded = { 96 | bids: fullMarketDetailsDecoded.bids, 97 | asks: fullMarketDetailsDecoded.asks, 98 | eventQueue: fullMarketDetailsDecoded.eventQueue, 99 | }; 100 | buy(latestBlockHash, tokenAccount, poolstate, marketDetailsDecoded); 101 | clearInterval(intervalId); // Stop retrying when a match is found 102 | } else if (attempts >= maxAttempts) { 103 | logger.error("Invalid market details"); 104 | clearInterval(intervalId); // Stop retrying after maxAttempts 105 | } 106 | attempts++; 107 | }, 10); // Retry every 10ms 108 | } else { 109 | logger.info(`No up coming Jito leaders. Slot: ${data.account.slot}`); 110 | } 111 | } 112 | }); 113 | 114 | // Create a subscription request. 115 | const request: SubscribeRequest = { 116 | slots: {}, 117 | accounts: { 118 | raydium: { 119 | account: [], 120 | filters: [ 121 | { 122 | memcmp: { 123 | offset: 124 | LIQUIDITY_STATE_LAYOUT_V4.offsetOf("quoteMint").toString(), // Filter for only tokens paired with SOL 125 | base58: "So11111111111111111111111111111111111111112", 126 | }, 127 | }, 128 | { 129 | memcmp: { 130 | offset: 131 | LIQUIDITY_STATE_LAYOUT_V4.offsetOf( 132 | "marketProgramId" 133 | ).toString(), // Filter for only Raydium markets that contain references to Serum 134 | base58: "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX", 135 | }, 136 | }, 137 | { 138 | memcmp: { 139 | offset: 140 | LIQUIDITY_STATE_LAYOUT_V4.offsetOf( 141 | "swapQuoteInAmount" 142 | ).toString(), // Hack to filter for only new tokens. There is probably a better way to do this 143 | bytes: Uint8Array.from([0]), 144 | }, 145 | }, 146 | { 147 | memcmp: { 148 | offset: 149 | LIQUIDITY_STATE_LAYOUT_V4.offsetOf( 150 | "swapBaseOutAmount" 151 | ).toString(), // Hack to filter for only new tokens. There is probably a better way to do this 152 | bytes: Uint8Array.from([0]), 153 | }, 154 | }, 155 | ], 156 | owner: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"], // raydium program id to subscribe to 157 | }, 158 | }, 159 | transactions: {}, 160 | blocks: {}, 161 | blocksMeta: { 162 | block: [], 163 | }, 164 | accountsDataSlice: [], 165 | commitment: CommitmentLevel.PROCESSED, // Subscribe to processed blocks for the fastest updates 166 | entry: {}, 167 | }; 168 | 169 | // Sending a subscription request. 170 | await new Promise((resolve, reject) => { 171 | stream.write(request, (err: null | undefined) => { 172 | if (err === null || err === undefined) { 173 | resolve(); 174 | } else { 175 | reject(err); 176 | } 177 | }); 178 | }).catch((reason) => { 179 | console.error(reason); 180 | throw reason; 181 | }); 182 | } 183 | -------------------------------------------------------------------------------- /transaction/transaction.ts: -------------------------------------------------------------------------------- 1 | import { Liquidity, LiquidityPoolKeys, LiquidityStateV4, Token, TokenAmount } from "@raydium-io/raydium-sdk"; 2 | import { Commitment, ComputeBudgetProgram, Connection, Keypair, PublicKey, SystemProgram, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; 3 | import bs58 from "bs58"; 4 | import { COMMITMENT_LEVEL, LOG_LEVEL, PRIVATE_KEY, QUOTE_AMOUNT, QUOTE_MINT, RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT } from "../constants"; 5 | import { 6 | AccountLayout, 7 | createAssociatedTokenAccountIdempotentInstruction, 8 | createCloseAccountInstruction, 9 | getAssociatedTokenAddressSync, 10 | TOKEN_PROGRAM_ID, 11 | } from '@solana/spl-token'; 12 | 13 | import { TokenInstructions } from '@project-serum/serum'; 14 | import { sendBundle } from "../jito/bundle"; 15 | import { logger } from "../utils/logger"; 16 | import { MinimalMarketLayoutV3, getMinimalMarketV3 } from "../market"; 17 | import { createPoolKeys, getTokenAccounts } from "../liquidity"; 18 | import { populateJitoLeaderArray } from "../streaming/raydium"; 19 | import { retrieveEnvVariable } from "../utils"; 20 | 21 | 22 | 23 | let wallet: Keypair; 24 | let quoteToken: Token; 25 | let quoteTokenAssociatedAddress: PublicKey; 26 | let quoteAmount: TokenAmount; 27 | 28 | wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)); 29 | quoteAmount = new TokenAmount(Token.WSOL, QUOTE_AMOUNT, false); 30 | 31 | 32 | 33 | export interface MinimalTokenAccountData { 34 | mint: PublicKey; 35 | address: PublicKey; 36 | poolKeys?: LiquidityPoolKeys; 37 | market?: MinimalMarketLayoutV3; 38 | }; 39 | 40 | const existingTokenAccounts: Map = new Map(); 41 | 42 | const solanaConnection = new Connection(RPC_ENDPOINT, { 43 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, 44 | }); 45 | 46 | // Init Function 47 | 48 | export async function init(): Promise { 49 | logger.level = LOG_LEVEL; 50 | // get wallet 51 | wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)); 52 | logger.info(`Wallet Address: ${wallet.publicKey}`); 53 | 54 | 55 | // get quote mint and amount 56 | switch (QUOTE_MINT) { 57 | case 'WSOL': { 58 | quoteToken = Token.WSOL; 59 | quoteAmount = new TokenAmount(Token.WSOL, QUOTE_AMOUNT, false); 60 | break; 61 | } 62 | case 'USDC': { 63 | quoteToken = new Token( 64 | TOKEN_PROGRAM_ID, 65 | new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 66 | 6, 67 | 'USDC', 68 | 'USDC', 69 | ); 70 | quoteAmount = new TokenAmount(quoteToken, QUOTE_AMOUNT, false); 71 | break; 72 | } 73 | default: { 74 | throw new Error(`Unsupported quote mint "${QUOTE_MINT}". Supported values are USDC and WSOL`); 75 | } 76 | } 77 | 78 | logger.info( 79 | `Script will buy all new tokens using ${QUOTE_MINT}. Amount that will be used to buy each token is: ${quoteAmount.toFixed().toString()}`, 80 | ); 81 | 82 | // check existing wallet for associated token account of quote mint 83 | const tokenAccounts = await getTokenAccounts(solanaConnection, wallet.publicKey, COMMITMENT_LEVEL); 84 | console.log("test"); 85 | 86 | for (const ta of tokenAccounts) { 87 | existingTokenAccounts.set(ta.accountInfo.mint.toString(), { 88 | mint: ta.accountInfo.mint, 89 | address: ta.pubkey, 90 | }); 91 | } 92 | 93 | const tokenAccount = tokenAccounts.find((acc) => acc.accountInfo.mint.toString() === quoteToken.mint.toString())!; 94 | 95 | if (!tokenAccount) { 96 | throw new Error(`No ${quoteToken.symbol} token account found in wallet: ${wallet.publicKey}`); 97 | } 98 | 99 | quoteTokenAssociatedAddress = tokenAccount.pubkey; 100 | 101 | await populateJitoLeaderArray(); 102 | 103 | } 104 | 105 | // Create transaction 106 | 107 | 108 | export async function buy(latestBlockhash: string, newTokenAccount: PublicKey, poolState: LiquidityStateV4, marketDetails: MinimalMarketLayoutV3): Promise { 109 | try { 110 | 111 | 112 | const ata = getAssociatedTokenAddressSync(poolState.baseMint, wallet.publicKey); 113 | const poolKeys = createPoolKeys(newTokenAccount, poolState, marketDetails!); 114 | const { innerTransaction } = Liquidity.makeSwapFixedInInstruction( 115 | { 116 | poolKeys: poolKeys, 117 | userKeys: { 118 | tokenAccountIn: quoteTokenAssociatedAddress, 119 | tokenAccountOut: ata, 120 | owner: wallet.publicKey, 121 | }, 122 | amountIn: quoteAmount.raw, 123 | minAmountOut: 0, 124 | }, 125 | poolKeys.version, 126 | ); 127 | 128 | 129 | const messageV0 = new TransactionMessage({ 130 | payerKey: wallet.publicKey, 131 | recentBlockhash: latestBlockhash, 132 | instructions: [ 133 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000 }), // Set this to super small value since it is not taken into account when sending as bundle. 134 | ComputeBudgetProgram.setComputeUnitLimit({ units: 80000 }), // Calculated amount of units typically used in our transaction is about 70848. Setting limit slightly above. 135 | createAssociatedTokenAccountIdempotentInstruction( 136 | wallet.publicKey, 137 | ata, 138 | wallet.publicKey, 139 | poolState.baseMint, 140 | ), 141 | ...innerTransaction.instructions, 142 | ], 143 | }).compileToV0Message(); 144 | 145 | 146 | let commitment: Commitment = retrieveEnvVariable('COMMITMENT_LEVEL', logger) as Commitment; 147 | 148 | const transaction = new VersionedTransaction(messageV0); 149 | 150 | transaction.sign([wallet, ...innerTransaction.signers]); 151 | 152 | //await sleep(30000); 153 | 154 | /*const signature = await solanaConnection.sendRawTransaction(transaction.serialize(), { 155 | preflightCommitment: commitment, 156 | }); 157 | */ 158 | //logger.info(`Sending bundle transaction with mint - ${signature}`); 159 | sendBundle(latestBlockhash, messageV0, poolState.baseMint); 160 | } 161 | catch (error) { 162 | logger.error(error); 163 | } 164 | 165 | } 166 | 167 | function sleep(ms: number) { 168 | return new Promise(resolve => setTimeout(resolve, ms)); 169 | } -------------------------------------------------------------------------------- /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": "es2017", /* 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 | --------------------------------------------------------------------------------