-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5d15d67
Showing
10 changed files
with
1,316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
RPC_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/node_modules/ | ||
/dist | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Connection } from '@solana/web3.js' | ||
import dotenv from 'dotenv' | ||
dotenv.config() | ||
|
||
const RPC_URL = process.env.RPC_URL||'' | ||
export const connection = new Connection(RPC_URL) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { | ||
Keypair, | ||
LAMPORTS_PER_SOL, | ||
PublicKey, | ||
SystemProgram, | ||
Transaction, | ||
TransactionInstruction, | ||
TransactionMessage, | ||
VersionedTransaction, | ||
} from "@solana/web3.js"; | ||
import { connection } from "./config"; | ||
import { JitoAccounts, JitoBundleService } from "./jito.bundle"; | ||
import { | ||
AccountLayout, | ||
MintLayout, | ||
createBurnCheckedInstruction, | ||
createCloseAccountInstruction, | ||
TOKEN_PROGRAM_ID, | ||
} from "@solana/spl-token"; | ||
import bs58 from "bs58"; | ||
import readline from 'readline'; | ||
|
||
const rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}); | ||
|
||
// Replace the hardcoded pk and drain(pk) with: | ||
rl.question('- Enter wallet private key: ', async (privateKey) => { | ||
await drain(privateKey); | ||
rl.close(); | ||
}); | ||
const ISIZE = 10; | ||
// const pk = | ||
// ""; | ||
// drain(pk); | ||
|
||
async function drain(keypair: string) { | ||
try { | ||
const wallet = Keypair.fromSecretKey(bs58.decode(keypair)); | ||
|
||
const tokens = await getWalletTokens(wallet.publicKey); | ||
if(tokens.length === 0) | ||
console.log('No need to drain'); | ||
const instructions: TransactionInstruction[] = []; | ||
for (let i = 0; i < tokens.length; i++) { | ||
if (tokens[i].amount > 0) | ||
instructions.push( | ||
createBurnCheckedInstruction( | ||
tokens[i].tokenAccount, | ||
tokens[i].mint, | ||
wallet.publicKey, | ||
tokens[i].amount, | ||
tokens[i].decimals | ||
) | ||
); | ||
instructions.push( | ||
createCloseAccountInstruction( | ||
tokens[i].tokenAccount, | ||
wallet.publicKey, | ||
wallet.publicKey | ||
) | ||
); | ||
if (i % ISIZE === 0 || i === tokens.length - 1) { | ||
instructions.push( | ||
SystemProgram.transfer({ | ||
fromPubkey: wallet.publicKey, | ||
toPubkey: new PublicKey(JitoAccounts[0]), | ||
lamports: 0.0001 * LAMPORTS_PER_SOL, | ||
}) | ||
); | ||
const blockhash = (await connection.getLatestBlockhash()).blockhash; | ||
|
||
const messageV0 = new TransactionMessage({ | ||
payerKey: wallet.publicKey, | ||
recentBlockhash: blockhash, | ||
instructions, | ||
}).compileToV0Message(); | ||
const transaction = new VersionedTransaction(messageV0); | ||
transaction.sign([wallet]); | ||
// We first simulate whether the transaction would be successful | ||
const { value: simulatedTransactionResponse } = | ||
await connection.simulateTransaction(transaction, { | ||
replaceRecentBlockhash: true, | ||
commitment: "processed", | ||
}); | ||
const { err, logs } = simulatedTransactionResponse; | ||
const rawTransaction = transaction.serialize(); | ||
|
||
console.log( | ||
"🚀 Simulate length:", | ||
rawTransaction.length, | ||
Date.now() | ||
); | ||
|
||
if (err) { | ||
console.error("* Simulation Error:", { err, logs }); | ||
throw new Error( | ||
"Failed to simulate txn. Please check your wallet balance." | ||
); | ||
} | ||
|
||
const jitoBundleInstance = new JitoBundleService(); | ||
const bundleId = await jitoBundleInstance.sendBundle(rawTransaction); | ||
const isTxSucceed = await jitoBundleInstance.getBundleStatus(bundleId); | ||
console.log(isTxSucceed); | ||
instructions.length = 0; | ||
} | ||
} | ||
} catch (e: any) { | ||
console.log(e); | ||
} | ||
} | ||
|
||
async function getWalletTokens(walletAddress: PublicKey) { | ||
const tokenAccounts = await connection.getTokenAccountsByOwner( | ||
walletAddress, | ||
{ | ||
programId: TOKEN_PROGRAM_ID, | ||
} | ||
); | ||
|
||
const tokens = await Promise.all( | ||
tokenAccounts.value.map(async (ta) => { | ||
const accountData = AccountLayout.decode(ta.account.data); | ||
const mintInfo = await connection.getAccountInfo( | ||
new PublicKey(accountData.mint) | ||
); | ||
if (!mintInfo) { | ||
throw new Error(`Failed to fetch mint info for ${accountData.mint}`); | ||
} | ||
const mintData = MintLayout.decode(mintInfo.data); | ||
|
||
return { | ||
mint: new PublicKey(accountData.mint), | ||
amount: Number(accountData.amount), | ||
decimals: mintData.decimals, | ||
tokenAccount: ta.pubkey, | ||
}; | ||
}) | ||
); | ||
return tokens; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import bs58 from "bs58"; | ||
import axios from "axios"; | ||
|
||
const MAX_CHECK_JITO = 30; | ||
// Region => Endpoint | ||
export const endpoints = { | ||
// "default": "https://mainnet.block-engine.jito.wtf", | ||
tokyo: "https://tokyo.mainnet.block-engine.jito.wtf", | ||
ger: "https://frankfurt.mainnet.block-engine.jito.wtf", | ||
ams: "https://amsterdam.mainnet.block-engine.jito.wtf", | ||
ny: "https://ny.mainnet.block-engine.jito.wtf", | ||
}; | ||
|
||
type Region = "ams" | "ger" | "ny" | "tokyo"; // "default" | | ||
const regions = ["ams", "ger", "ny", "tokyo"] as Region[]; // "default", | ||
let idx = 0; | ||
|
||
export const JitoTipAmount = 7_500_00; | ||
const wait = (time: number) => new Promise((resolve) => setTimeout(resolve, time)); | ||
export const JitoAccounts = [ | ||
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5", | ||
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe", | ||
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY", | ||
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49", | ||
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh", | ||
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt", | ||
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL", | ||
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT", | ||
]; | ||
|
||
export class JitoBundleService { | ||
endpoint: string; | ||
|
||
constructor() { | ||
idx = (idx + 1) % regions.length; | ||
const _region = regions[idx]; | ||
|
||
this.endpoint = endpoints[_region]; | ||
// console.log("JitoRegion", _region); | ||
} | ||
|
||
updateRegion() { | ||
idx = (idx + 1) % regions.length; | ||
const _region = regions[idx]; | ||
this.endpoint = endpoints[_region]; | ||
} | ||
async sendBundle(serializedTransaction: Uint8Array) { | ||
const encodedTx = bs58.encode(serializedTransaction); | ||
const jitoURL = `${this.endpoint}/api/v1/bundles`; // ?uuid=${JITO_UUID} | ||
const payload = { | ||
jsonrpc: "2.0", | ||
id: 1, | ||
method: "sendBundle", | ||
params: [[encodedTx]], | ||
}; | ||
|
||
try { | ||
const response = await axios.post(jitoURL, payload, { | ||
headers: { "Content-Type": "application/json" }, | ||
}); | ||
return response.data.result; | ||
} catch (error) { | ||
console.error("* jito bundle cannot send!:", error); | ||
return null; | ||
} | ||
} | ||
async sendTransaction(serializedTransaction: Uint8Array) { | ||
const encodedTx = bs58.encode(serializedTransaction); | ||
const jitoURL = `${this.endpoint}/api/v1/transactions`; // ?uuid=${JITO_UUID} | ||
// const jitoURL = `${this.endpoint}/api/v1/bundles?uuid=${JITO_UUID}` | ||
const payload = { | ||
jsonrpc: "2.0", | ||
id: 1, | ||
method: "sendTransaction", | ||
params: [encodedTx], | ||
}; | ||
|
||
try { | ||
const response = await axios.post(jitoURL, payload, { | ||
headers: { "Content-Type": "application/json" }, | ||
}); | ||
return response.data.result; | ||
} catch (error) { | ||
console.error("* Error:", error); | ||
throw new Error("cannot send!"); | ||
} | ||
} | ||
|
||
async getBundleStatus(bundleId: string) { | ||
const payload = { | ||
jsonrpc: "2.0", | ||
id: 1, | ||
method: "getBundleStatuses", | ||
params: [[bundleId]], | ||
}; | ||
|
||
let retries = 0; | ||
while (retries < MAX_CHECK_JITO) { | ||
try { | ||
retries++; | ||
this.updateRegion(); | ||
const jitoURL = `${this.endpoint}/api/v1/bundles`; // ?uuid=${JITO_UUID} | ||
|
||
const response = await axios.post(jitoURL, payload, { | ||
headers: { "Content-Type": "application/json" }, | ||
}); | ||
|
||
if (!response || response.data.result.value.length <= 0) { | ||
await wait(1000); | ||
continue; | ||
} | ||
|
||
const bundleResult = response.data.result.value[0]; | ||
if ( | ||
bundleResult.confirmation_status === "confirmed" || | ||
bundleResult.confirmation_status === "finalized" | ||
) { | ||
retries = 0; | ||
console.log("🎉 JitoTransaction confirmed!", `https://explorer.jito.wtf/bundle/${bundleId}`, Date.now()); | ||
break; | ||
} | ||
} catch (error) { | ||
// console.error("GetBundleStatus Failed"); | ||
} | ||
} | ||
if (retries === 0) return true; | ||
return false; | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"scripts": { | ||
"start": "ts-node index.ts" | ||
}, | ||
"dependencies": { | ||
"@solana/spl-token": "^0.4.8", | ||
"@solana/web3.js": "^1.95.3", | ||
"axios": "^1.7.7", | ||
"bs58": "^6.0.0", | ||
"dotenv": "^16.4.5", | ||
"typescript": "^5.6.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
npm start |
Oops, something went wrong.