Last active
January 4, 2025 16:28
-
-
Save YogaSakti/d800776fe28414cb2a07c82adf070d8e to your computer and use it in GitHub Desktop.
auto check pengu using curl impersonate
This file contains hidden or 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
/* eslint-disable max-lines-per-function */ | |
/* eslint-disable no-sync */ | |
const solanaWeb3 = require('@solana/web3.js'); | |
const { Connection, Keypair, PublicKey, sendAndConfirmTransaction, LAMPORTS_PER_SOL, SystemProgram, Transaction, VersionedMessage, VersionedTransaction } = solanaWeb3 | |
const { sign } = require('tweetnacl'); | |
const { decodeUTF8 } = require('tweetnacl-util'); | |
const bs58 = require('bs58'); | |
const bip39 = require('bip39'); | |
const { derivePath } = require('ed25519-hd-key') | |
const fs = require('fs'); | |
const delay = require('delay'); | |
const { fetcher } = require('./fetcher') | |
const key = '' | |
const rpcUrl = `https://rpc.helius.xyz?api-key=${key}` | |
const connection = new Connection(rpcUrl, 'confirmed'); | |
// get message using fetcher | |
const getMessage = () => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/auth/message?', 'GET', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}) | |
const doAuth = (signature, signingDate, wallet) => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/auth/token?', 'POST', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'content-type': 'application/json', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}, { | |
signature, | |
signingDate, | |
'type': 'solana', | |
wallet | |
}) | |
const cekElig = (address) => fetcher(`https://api.clusters.xyz/v0.1/airdrops/pengu/eligibility/${address}?`, 'GET', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}).then((res) => { | |
if (res.total == 0) return false | |
return true | |
}) | |
// sign message example | |
// claim.pudgypenguins.com wants you to | |
// sign in with your Solana account: | |
// drnTmZNNyGocHnmkLKGIEk4Qv5aF8RC | |
// mPGk3U9Dh8tQy | |
// getClaim Data | |
const getClaim = (address) => fetcher(`https://api.clusters.xyz/v0.1/airdrops/pengu/claim/${address}?`, 'GET', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}) | |
// bulk eligibility check | |
const cekEligBulk = (addresses) => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/eligibility', 'POST', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'content-type': 'application/json', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}, addresses) | |
// generate account from private key or mnemonic | |
const generateAccount = (accountData, isPk = false) => { | |
let keypair = null | |
if (isPk) { | |
keypair = Keypair.fromSecretKey(bs58.decode(accountData)) | |
} else { | |
const seed = bip39.mnemonicToSeedSync(accountData); | |
const path = 'm/44\'/501\'/0\'/0\''; | |
const derivedSeed = derivePath(path, seed.toString('hex')).key; | |
keypair = Keypair.fromSeed(derivedSeed) | |
} | |
if (!keypair) throw new Error('Invalid secret key') | |
return { | |
publicKey: keypair.publicKey, | |
secretKey: keypair.secretKey, | |
// aditional data below | |
address: keypair.publicKey.toBase58(), | |
privateKey: bs58.encode(keypair.secretKey), | |
} | |
}; | |
const signMessage = (message, account) => { | |
const { address } = account | |
const bufSecretKey = account.secretKey | |
const messageBytes = decodeUTF8(message); | |
const signatureRaw = sign.detached(messageBytes, bufSecretKey); | |
const buffer = Buffer.from(signatureRaw); | |
const hexString = buffer.toString('hex'); | |
const signature = `0x${hexString}` | |
return { address, signature, message } | |
}; | |
const saveGroupToFile = (fileName, groupData) => { | |
const existingData = fs.existsSync(fileName) ? JSON.parse(fs.readFileSync(fileName, 'utf8')) : []; | |
existingData.push(groupData); | |
fs.writeFileSync(fileName, JSON.stringify(existingData, null, 2)); | |
}; | |
// Save data to file ensuring uniqueness | |
const saveGroupToFileUniqe = (fileName, groupData) => { | |
const existingData = fs.existsSync(fileName) | |
? JSON.parse(fs.readFileSync(fileName, 'utf8')) | |
: []; | |
if (existingData.find((item) => item.address === groupData.address)) return; | |
existingData.push(groupData); | |
fs.writeFileSync(fileName, JSON.stringify(existingData, null, 2)); | |
}; | |
// Authenticate wallet and retrieve token | |
const getToken = async (wallet) => { | |
try { | |
const { message: messageContent, signingDate } = await getMessage(); | |
const account = | |
bip39.validateMnemonic(wallet) | |
? generateAccount(wallet, false) | |
: generateAccount(wallet, true); | |
const { address, signature } = signMessage(messageContent, account); | |
const authResult = await doAuth(signature, signingDate, address); | |
return { token: authResult.token, address }; | |
} catch (error) { | |
console.error('Error in getToken:', error); | |
return null; | |
} | |
}; | |
const isMnemonicOrPk = (data) => { | |
try { | |
// Check if data contains spaces (likely a mnemonic) | |
if (typeof data === 'string' && data.includes(' ')) { | |
try { | |
bip39.validateMnemonic(data); | |
return 'mnemonic'; | |
} catch { | |
// Invalid mnemonic | |
console.log(`Invalid mnemonic: ${data}`); | |
} | |
} | |
// Check if data is Base58 PrivateKey | |
if (typeof data === 'string' && !data.includes(' ') && !data.includes(',') && bs58.decode(data).length > 0) { | |
try { | |
Keypair.fromSecretKey(bs58.decode(data)); | |
return 'privateKey'; | |
} catch { | |
// Invalid Base58 private key | |
console.log(`Invalid Base58 private key: ${data}`); | |
} | |
} | |
// Check if data is a stringified array (secret key) | |
if (typeof data === 'string' && data.startsWith('[') && data.endsWith(']')) { | |
try { | |
const parsedArray = JSON.parse(data); | |
Keypair.fromSecretKey(Uint8Array.from(parsedArray)); | |
return 'secretKey'; | |
} catch { | |
// Invalid secret key | |
console.log(`Invalid secret key: ${data}`); | |
} | |
} | |
// If none match, return null | |
return null; | |
} catch (error) { | |
console.error('Error in isMnemonicOrPk:', error); | |
return null; | |
} | |
}; | |
const createGroupedData = async (wallets, groupFile, totalPerGroup) => { | |
let currentGroup = []; | |
let groupIndex = 1; | |
// for with index | |
for (const wallet of wallets) { | |
// check every key | |
const keys = Object.keys(wallet); | |
for (const key of keys) { | |
let data = wallet[key]; | |
// data value must longer than 50 | |
if (data.length < 50) continue; | |
const type = isMnemonicOrPk(data); | |
if (type === 'mnemonic' || type === 'privateKey' || type === 'secretKey') { | |
wallet[type] = data; | |
break; | |
} | |
} | |
const { token, address } = await getToken(wallet.mnemonic || wallet.privateKey); | |
await delay(1000); | |
let isEligible = await cekEligBulk([token]); | |
isEligible = isEligible.totalUnclaimed > 0; | |
await delay(1000); | |
if (isEligible) { | |
currentGroup.push(wallet.mnemonic || wallet.privateKey); | |
console.log(`Wallet ${address || address} is eligible.`); | |
saveGroupToFileUniqe('claimPinguin-eligible.json', { ...wallet, token }); | |
} else { | |
console.log(`Wallet ${address || address} is not eligible.`); | |
continue | |
// saveGroupToFile('claimPinguin-shadow-auth.json', { ...wallet, token }); | |
} | |
// if current group reaches totalPerGroup, finalize and reset the group | |
if (currentGroup.length === totalPerGroup) { | |
const [parent] = currentGroup; // first wallet as parent | |
const children = currentGroup.slice(1); // remaining as children | |
const groupData = { parent, children }; | |
// save current group to JSON file | |
saveGroupToFile(groupFile, groupData); | |
console.log(`Group ${groupIndex} saved.`); | |
groupIndex += 1; | |
currentGroup = []; // reset group | |
} | |
await delay(500); | |
} | |
// add remaining wallets if any, even if the group is incomplete | |
if (currentGroup.length > 0) { | |
const [parent] = currentGroup; | |
const children = currentGroup.slice(1); | |
const groupData = { parent, children }; | |
saveGroupToFile(groupFile, groupData); | |
// const parentAddress = wallets.find((obj) => obj.mnemonic === parent || obj.Mnemonic === parent); | |
// saveParentToFile(parentFile, parentAddress.privateKey); | |
console.log(`Group ${groupIndex} saved.`); | |
} | |
}; | |
(async () => { | |
let wallets = JSON.parse(fs.readFileSync('noMnemonic.json', 'utf8')); | |
const totalPerGroup = 10; // total mnemonic per grup contoh: 50 = 1 parent + 49 child | |
// INI NAMA FILE HASILNYA | |
const groupFile = 'claimPinguin-walletGroup.json'; | |
// create grouped data and save to files incrementally | |
await createGroupedData(wallets, groupFile, totalPerGroup); | |
})(); |
This file contains hidden or 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
/* eslint-disable max-lines-per-function */ | |
/* eslint-disable no-sync */ | |
/* eslint-disable no-continue */ | |
/* eslint-disable no-undefined */ | |
/* eslint-disable max-lines */ | |
const solanaWeb3 = require('@solana/web3.js'); | |
// eslint-disable-next-line no-unused-vars | |
const { Connection, ComputeBudgetProgram, Keypair, PublicKey, Transaction, LAMPORTS_PER_SOL, SystemProgram, TransactionMessage, VersionedMessage, VersionedTransaction } = solanaWeb3 | |
const { sign } = require('tweetnacl'); | |
const { decodeUTF8 } = require('tweetnacl-util'); | |
const bs58 = require('bs58'); | |
const bip39 = require('bip39'); | |
const { derivePath } = require('ed25519-hd-key') | |
const fs = require('fs'); | |
const delay = require('delay'); | |
const { fetcher } = require('./utils/index') | |
const readline = require('readline'); | |
const SplToken = require('@solana/spl-token'); | |
const { getOrCreateAssociatedTokenAccount } = SplToken | |
const PENGU_TOKEN_ADDRESS = new PublicKey('2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv'); | |
const askQuestion = (query) => { | |
const rl = readline.createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
return new Promise((resolve) => { | |
rl.question(query, (answer) => { | |
rl.close(); | |
resolve(answer); | |
}); | |
}); | |
} | |
// const key = '' | |
// const rpcUrl = `https://rpc.helius.xyz?api-key=${key}` | |
const rpcUrl = `https://cold-hanni-fast-mainnet.helius-rpc.com/` | |
const connection = new Connection(rpcUrl, 'confirmed'); | |
// get message using fetcher | |
const getMessageApi = () => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/auth/message?', 'GET', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}) | |
function getMessage() { | |
const signingDate = new Date().toISOString(); | |
return { | |
message: `$PENGU Claim\n\nThis is a gasless signature to prove ownership of your wallet address.\n\nAlways ensure you are on the correct website URL: claim.pudgypenguins.com.\n\n${signingDate}`, | |
signingDate: signingDate | |
}; | |
} | |
const doAuth = (signature, signingDate, wallet) => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/auth/token?', 'POST', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'content-type': 'application/json', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}, { | |
signature, | |
signingDate, | |
'type': 'solana', | |
wallet | |
}) | |
// ENDPOINTS DEAD | |
const cekElig = (address) => fetcher(`https://api.clusters.xyz/v0.1/airdrops/pengu/eligibility/${address}?`, 'GET', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}) | |
// bulk eligibility check using token? so dumb | |
const cekEligBulk = (addresses) => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/eligibility', 'POST', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'content-type': 'application/json', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}, addresses) | |
// get txData by server | |
const getClaim = (address) => fetcher(`https://api.clusters.xyz/v0.1/airdrops/pengu/claim/${address}?`, 'GET', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}) | |
// link child wallet to parent wallet | |
const linkChildToParent = (authorization, addressParent) => fetcher('https://api.clusters.xyz/v0.1/airdrops/pengu/link?', 'POST', { | |
'accept': '*/*', | |
'accept-language': 'en-US,en;q=0.9', | |
'authorization': `Bearer ${authorization}`, | |
'content-type': 'application/json', | |
'priority': 'u=1, i', | |
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', | |
'sec-ch-ua-mobile': '?0', | |
'sec-ch-ua-platform': '"Windows"', | |
'sec-fetch-dest': 'empty', | |
'sec-fetch-mode': 'cors', | |
'sec-fetch-site': 'cross-site', | |
'Referer': 'https://claim.pudgypenguins.com/', | |
'Referrer-Policy': 'strict-origin-when-cross-origin' | |
}, { | |
'address': addressParent, | |
'type': 'solana', | |
'isPrivate': false | |
}) | |
const isMnemonicOrPk = (data) => { | |
try { | |
// Check if data contains spaces (likely a mnemonic) | |
if (typeof data === 'string' && data.includes(' ')) { | |
try { | |
bip39.validateMnemonic(data); | |
return 'mnemonic'; | |
} catch { | |
// Invalid mnemonic | |
console.log(`Invalid mnemonic: ${data}`); | |
} | |
} | |
// Check if data is Base58 PrivateKey | |
if (typeof data === 'string' && !data.includes(' ') && !data.includes(',') && bs58.decode(data).length > 0) { | |
try { | |
Keypair.fromSecretKey(bs58.decode(data)); | |
return 'privateKey'; | |
} catch { | |
// Invalid Base58 private key | |
console.log(`Invalid Base58 private key: ${data}`); | |
} | |
} | |
// Check if data is a stringified array (secret key) | |
if (typeof data === 'string' && data.startsWith('[') && data.endsWith(']')) { | |
try { | |
const parsedArray = JSON.parse(data); | |
Keypair.fromSecretKey(Uint8Array.from(parsedArray)); | |
return 'secretKey'; | |
} catch { | |
// Invalid secret key | |
console.log(`Invalid secret key: ${data}`); | |
} | |
} | |
// If none match, return null | |
return null; | |
} catch (error) { | |
console.error('Error in isMnemonicOrPk:', error); | |
return null; | |
} | |
}; | |
const generateAccount = (accountData) => { | |
let keypair = null; | |
// Determine the type of the input data | |
const type = isMnemonicOrPk(accountData); | |
// Generate Keypair based on the type | |
if (type === 'mnemonic') { | |
const seed = bip39.mnemonicToSeedSync(accountData); | |
const path = 'm/44\'/501\'/0\'/0\''; | |
const derivedSeed = derivePath(path, seed.toString('hex')).key; | |
keypair = Keypair.fromSeed(derivedSeed); | |
} else if (type === 'privateKey') { | |
keypair = Keypair.fromSecretKey(bs58.decode(accountData)); | |
} else if (type === 'secretKey') { | |
const parsedArray = JSON.parse(accountData); | |
keypair = Keypair.fromSecretKey(Uint8Array.from(parsedArray)); | |
} else { | |
throw new Error('Invalid account data'); | |
} | |
if (!keypair) { | |
throw new Error('Failed to generate keypair'); | |
} | |
// Construct account object | |
const account = { | |
publicKey: keypair.publicKey, | |
secretKey: keypair.secretKey, | |
// Additional data | |
address: keypair.publicKey.toBase58(), | |
privateKey: bs58.encode(keypair.secretKey), | |
}; | |
return account; | |
}; | |
// sign message | |
const signMessage = (message, account) => { | |
const { address } = account | |
const bufSecretKey = account.secretKey | |
const messageBytes = decodeUTF8(message); | |
const signatureRaw = sign.detached(messageBytes, bufSecretKey); | |
const buffer = Buffer.from(signatureRaw); | |
const hexString = buffer.toString('hex'); | |
const signature = `0x${hexString}` | |
return { address, signature, message } | |
}; | |
// get token from wallet | |
const getToken = async (wallet) => { | |
const message = await getMessage(); | |
const { message: messageContent, signingDate } = message; | |
const account = generateAccount(wallet); | |
const { address } = account; | |
const { signature } = signMessage(messageContent, account); | |
const authResult = await doAuth(signature, signingDate, address); | |
return { token: authResult.token, address }; | |
}; | |
// get SPL token balance | |
const getTokenBalance = async (address) => { | |
const parsedTokenAccounts = await connection.getParsedTokenAccountsByOwner(new PublicKey(address), { mint: new PublicKey(PENGU_TOKEN_ADDRESS) }); | |
if (parsedTokenAccounts.value.length === 0) { | |
return 0; | |
} | |
return parsedTokenAccounts.value[0].account.data.parsed.info.tokenAmount.uiAmount; | |
}; | |
// send SPL token | |
const sendToken = async (from, to, amount) => { | |
const TokenAddress = PENGU_TOKEN_ADDRESS | |
const recipientAddress = new PublicKey(to); | |
const senderKeypair = Keypair.fromSecretKey(from.secretKey); | |
const recipient = await getOrCreateAssociatedTokenAccount( | |
connection, | |
senderKeypair, | |
TokenAddress, | |
recipientAddress | |
); | |
const sender = await getOrCreateAssociatedTokenAccount( | |
connection, | |
senderKeypair, | |
TokenAddress, | |
senderKeypair.publicKey | |
); | |
const instructions = SplToken.createTransferInstruction( | |
sender.address, | |
recipient.address, | |
senderKeypair.publicKey, | |
amount, | |
[], | |
SplToken.TOKEN_PROGRAM_ID | |
) | |
// add compute budget instruction for priority fee | |
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitPrice({ | |
microLamports: 0.00015 * LAMPORTS_PER_SOL | |
}); | |
await delay(500) | |
const blockhash = await connection.getLatestBlockhash('confirmed').then(res => res.blockhash); | |
await delay(500) | |
const messageV0 = new TransactionMessage({ | |
payerKey: senderKeypair.publicKey, | |
recentBlockhash: blockhash, | |
instructions: [computeBudgetIx, instructions] | |
}).compileToV0Message(); | |
const transaction = new VersionedTransaction(messageV0); | |
transaction.sign([senderKeypair]); | |
const txid = await connection.sendRawTransaction(transaction.serialize(), { | |
skipPreflight: !1, | |
preflightCommitment: 'confirmed' | |
}) | |
await connection.getLatestBlockhash('confirmed').then(({ blockhash, lastValidBlockHeight }) => connection.confirmTransaction({ | |
blockhash, | |
lastValidBlockHeight, | |
signature: txid | |
}, 'confirmed')) // wait for confirmation | |
return txid; | |
}; | |
// send SOL | |
const sendSol = async (from, to, amount) => { | |
try { | |
const blockhash = await connection.getLatestBlockhash('confirmed').then(res => res.blockhash); | |
const messageV0 = new TransactionMessage({ | |
payerKey: from.publicKey, | |
recentBlockhash: blockhash, | |
instructions: [SystemProgram.transfer({ | |
fromPubkey: from.publicKey, | |
toPubkey: new PublicKey(to), | |
lamports: BigInt(Math.round(amount * LAMPORTS_PER_SOL)) | |
})], | |
}).compileToV0Message(); | |
const transaction = new VersionedTransaction(messageV0); | |
transaction.sign([from]); | |
const signature = await connection.sendRawTransaction(transaction.serialize(), { | |
skipPreflight: !1, | |
maxRetries: 5, | |
preflightCommitment: 'confirmed' | |
}); | |
await connection.getLatestBlockhash('confirmed').then(({ blockhash, lastValidBlockHeight }) => connection.confirmTransaction({ | |
blockhash, | |
lastValidBlockHeight, | |
signature: signature | |
}, 'confirmed')) // wait for confirmation | |
return signature; | |
} catch (error) { | |
console.error('Error sending SOL:', error); | |
return null; | |
} | |
}; | |
// get transaction fee | |
const getTransactionFee = async (pkFee) => { | |
try { | |
const feeAccountChecker = generateAccount(pkFee); | |
const recentBlockhash = (await connection.getLatestBlockhash()).blockhash; | |
const transaction = new Transaction(); | |
transaction.recentBlockhash = recentBlockhash; | |
transaction.feePayer = feeAccountChecker.publicKey; | |
transaction.add(SystemProgram.transfer({ | |
fromPubkey: feeAccountChecker.publicKey, | |
toPubkey: new PublicKey('MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa'), // transferring to itself just to simulate | |
lamports: 10 | |
})); | |
const message = transaction.compileMessage(); | |
const response = await connection.getFeeForMessage( | |
message, | |
'confirmed' | |
); | |
const feeInLamports = response.value; | |
return feeInLamports; | |
} catch (error) { | |
console.error(error); | |
} | |
}; | |
// collect token and sol | |
const collectTokenAndSol = async (accountFrom, addressTo, amountToken = null) => { | |
try { | |
const tokenBalance = await getTokenBalance(accountFrom.address); | |
if (tokenBalance !== 0) { | |
// eslint-disable-next-line prefer-exponentiation-operator | |
let amountToSend = amountToken == tokenBalance * 1000000 ? amountToken : tokenBalance * 1000000; // $PENGU 6 decimal | |
const sendTokenResult = await sendToken(accountFrom, addressTo, amountToSend); | |
if (sendTokenResult) console.log(`[>] ${amountToSend / 1000000} $PENGU Has Been Sent To Banker! https://solscan.io/tx/${sendTokenResult}`); | |
} else { | |
console.log(`[!] ${accountFrom.address} | No token to collect!`); | |
} | |
const transferFee = await getTransactionFee(accountFrom.privateKey); | |
const remainingBalance = await connection.getBalance(new PublicKey(accountFrom.address)); | |
const minimumBalanceLamports = transferFee; | |
if (remainingBalance > minimumBalanceLamports) { | |
let amountToSend = (remainingBalance - minimumBalanceLamports) / LAMPORTS_PER_SOL; | |
console.log(`[>] Sending remaining ${amountToSend.toFixed(4)} SOL back to main wallet...`); | |
const returnSig = await sendSol(accountFrom, addressTo, amountToSend); | |
if (returnSig) console.log(`[>] Success! TxHash: https://solscan.io/tx/${returnSig}`); | |
} else { | |
console.log(`[!] ${accountFrom.address} | No SOL to collect!`); | |
} | |
} catch (error) { | |
console.error('Error collecting token and SOL:', error.message); | |
} | |
} | |
const sendTokenAndSolUsingFeePayer = async (source, target, feePayer, amountToken = 0) => { | |
try { | |
const TokenAddress = PENGU_TOKEN_ADDRESS; | |
let recipientAddress = new PublicKey(target); | |
const sourceKeypair = Keypair.fromSecretKey(source.secretKey); | |
const feePayerKeypair = Keypair.fromSecretKey(feePayer.secretKey); | |
// Initialize instructions | |
const instructions = []; | |
const computeUnitPrice = 0.005 * LAMPORTS_PER_SOL; | |
// Add token transfer instruction if token balance exists | |
const tokenBalance = await getTokenBalance(source.publicKey); | |
let amountToSend = amountToken == tokenBalance * 1000000 ? amountToken : tokenBalance * 1000000; // $PENGU 6 decimal | |
if (tokenBalance > 0) { | |
const recipientTokenAccount = await getOrCreateAssociatedTokenAccount( | |
connection, | |
sourceKeypair, | |
TokenAddress, | |
recipientAddress | |
); | |
const senderTokenAccount = await getOrCreateAssociatedTokenAccount( | |
connection, | |
sourceKeypair, | |
TokenAddress, | |
sourceKeypair.publicKey | |
); | |
instructions.push( | |
SplToken.createTransferInstruction( | |
senderTokenAccount.address, | |
recipientTokenAccount.address, | |
sourceKeypair.publicKey, | |
Math.floor(tokenBalance * 10 ** 6), // Assuming token has 6 decimals | |
[], | |
SplToken.TOKEN_PROGRAM_ID | |
) | |
); | |
// Add Compute Budget Program instruction (for priority fee) | |
instructions.push( | |
ComputeBudgetProgram.setComputeUnitPrice({ | |
microLamports: computeUnitPrice, | |
}) | |
); | |
console.log(`[>] ${tokenBalance} $PENGU prepared for transfer...`); | |
} | |
// Add SOL transfer instruction if balance exists | |
const senderBalance = await connection.getBalance(sourceKeypair.publicKey, 'confirmed'); | |
if (senderBalance > 0) { | |
instructions.push( | |
SystemProgram.transfer({ | |
fromPubkey: sourceKeypair.publicKey, | |
toPubkey: recipientAddress, | |
lamports: senderBalance, // Send all available SOL | |
}) | |
); | |
console.log(`[>] ${(senderBalance / LAMPORTS_PER_SOL).toFixed(4)} $SOL prepared for transfer...`); | |
} | |
if (instructions.length === 0) { | |
console.log('[!] No tokens or SOL to transfer.'); | |
return null; | |
} | |
// Get latest blockhash | |
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed'); | |
// Create transaction message | |
const message = new TransactionMessage({ | |
payerKey: feePayerKeypair.publicKey, // Fee payer handles fees | |
recentBlockhash: blockhash, | |
instructions, | |
}).compileToV0Message(); | |
// Create and sign transaction | |
const transaction = new VersionedTransaction(message); | |
transaction.sign([sourceKeypair, feePayerKeypair]); // Both source and fee-payer must sign | |
const txid = await connection.sendTransaction(transaction, { | |
skipPreflight: false, | |
maxRetries: 3, | |
preflightCommitment: 'confirmed', | |
}); | |
// console.log(`[>] Transaction Sent! TxHash: https://solscan.io/tx/${txid}`); | |
// Confirm the transaction | |
const confirmation = await connection.confirmTransaction( | |
{ | |
blockhash, | |
lastValidBlockHeight, | |
signature: txid, | |
}, | |
'confirmed' | |
); | |
if (confirmation.value.err) { | |
console.error(`[!] Transaction failed:`); | |
console.log(confirmation.value.err) | |
} else { | |
console.log( | |
`[>] Transaction Confirmed! TxHash: https://solscan.io/tx/${txid}` | |
); | |
} | |
return txid; | |
} catch (error) { | |
console.error('Transaction failed:'); | |
console.error(error.message); | |
if (error.logs) { | |
console.error('Logs:', error.logs); | |
} else { | |
console.error('No logs available.'); | |
} | |
return null; | |
} | |
}; | |
// CONFIG =========================================================================== | |
// PK utama untuk mengirim SOL // INI BOLEH SAMA DENGAN pkFeePayer | |
const pkBank = 'ini pk' | |
const bankAccount = generateAccount(pkBank); | |
// Fee Payer untuk mengirim token dan SOL (biaya pengiriman) // INI BOLEH SAMA DENGAN pkBank | |
const pkFeePayer = 'ini pk' | |
const feePayer = generateAccount(pkFeePayer); | |
const SEND_AMOUNT_SOL = 0.01; | |
// wallet list | |
let wallets = JSON.parse(fs.readFileSync('./claimPinguin-mnemonic.json', 'utf8')); | |
// first 50 only | |
// wallets = wallets.slice(50); | |
// END CONFIG =========================================================================== | |
(async () => { | |
for (let i = 0; i < wallets.length; i++) { | |
console.log('==================================================================================================================') | |
const walletParent = wallets[i].parent; | |
let walletChild = wallets[i].children; | |
// load parent wallet | |
const accountParent = generateAccount(walletParent); | |
const addressParent = accountParent.address; | |
// login to parent wallet | |
let loginParent; | |
try { | |
loginParent = await getToken(walletParent); | |
} catch (err) { | |
console.error(`[${i + 1}] Error logging in to parent wallet: ${err.message}`); | |
continue; | |
} | |
if (!loginParent.token) { | |
console.error(`[${i + 1}] Error logging in to parent wallet!`); | |
continue; | |
} | |
console.log(`[${i + 1}] ${addressParent} | Logged in to parent wallet!`); | |
let eligibilityCheckParent; | |
try { | |
eligibilityCheckParent = await cekEligBulk([loginParent.token]); | |
} catch (err) { | |
console.error(`[${i + 1}] Error checking eligibility for parent wallet: ${err.message}`); | |
continue; | |
} | |
if (eligibilityCheckParent.total === 0) { | |
console.log(`[${i + 1}] Not eligible!`); | |
await delay(2000); | |
continue; | |
} else if (eligibilityCheckParent.totalUnclaimed === 0) { | |
console.log(`[${i + 1}] ${addressParent} | Already claimed ${eligibilityCheckParent.total} $PENGU!`); | |
await sendTokenAndSolUsingFeePayer(accountParent, bankAccount.address, bankAccount); | |
await delay(1000); | |
continue; | |
} else { | |
console.log(`[${i + 1}] Parent Eligible! Total: ${eligibilityCheckParent.total} $PENGU | Unclaimed: ${eligibilityCheckParent.totalUnclaimed} $PENGU`); | |
} | |
await delay(1000); | |
// get token for children | |
const childrenTokens = []; | |
for (const [index, childMnemonic] of walletChild.entries()) { | |
// get address from child mnemonic | |
const childAccount = generateAccount(childMnemonic); | |
const childAddress = childAccount.address; | |
// if child address is already linked to parent, skip | |
if (eligibilityCheckParent.addresses.includes(childAddress)) { | |
console.log(`[${i + 1} | ${index + 1}] ${childAddress} | Already linked to parent!`); | |
continue; | |
} | |
let tokenData = {}; | |
try { | |
tokenData = await getToken(childMnemonic); | |
} catch (err) { | |
console.error(`[${i + 1} | ${index + 1}] Error getting token for child ${index + 1}: ${err.message}`); | |
} | |
await delay(250); | |
if (tokenData.token) { | |
const linkResult = await linkChildToParent(tokenData.token, addressParent).catch((err) => { | |
console.error(`[${i + 1} | ${index + 1}] Error linking child ${index + 1} to parent: ${err.message}`); | |
return null; | |
}); | |
if (linkResult === 201) { | |
console.log(`[${i + 1} | ${index + 1}] Success Load & Link Child ${index + 1} to Parent!`); | |
} | |
} | |
childrenTokens.push(tokenData); | |
await delay(250); | |
} | |
await delay(1000); | |
try { | |
eligibilityCheckParent = await cekEligBulk([loginParent.token]); | |
} catch (err) { | |
console.error(`[${i + 1}] Error re-checking eligibility for parent wallet: ${err.message}`); | |
} | |
// Claim transaction | |
try { | |
const claimParent = await getClaim(addressParent); | |
if (claimParent.error) { | |
console.error(`[${i + 1}] Error getting claim data for parent: ${claimParent.error}`); | |
if (claimParent.error === 'no claims left') { | |
await sendTokenAndSolUsingFeePayer(accountParent, bankAccount.address, bankAccount); | |
} | |
continue; | |
} | |
// Balance and transaction handling for parent wallet | |
try { | |
const balance = await connection.getBalance(new PublicKey(addressParent)); | |
if (balance < SEND_AMOUNT_SOL * LAMPORTS_PER_SOL) { | |
console.log(`[!] Address ${addressParent} doesn't have enough balance! | Sending SOL to parent wallet...`); | |
const sendSolResult = await sendSol(bankAccount, addressParent, SEND_AMOUNT_SOL); | |
if (!sendSolResult) { | |
console.error(`[${i + 1}] Failed to send SOL to parent wallet`); | |
continue; | |
} | |
console.log(`[>] SOL sent! Tx: https://solscan.io/tx/${sendSolResult}`); | |
let parentBalance = 0; | |
do { | |
parentBalance = await connection.getBalance(new PublicKey(addressParent)); | |
if (parentBalance >= SEND_AMOUNT_SOL * LAMPORTS_PER_SOL) { | |
break; | |
} | |
await delay(500); | |
} while (parentBalance <= SEND_AMOUNT_SOL * LAMPORTS_PER_SOL); | |
await delay(500); | |
} | |
} catch (error) { | |
console.error('Error sending SOL:', error.message); | |
} | |
// Send claim transaction | |
const accountParentKeypair = Keypair.fromSecretKey(accountParent.secretKey); | |
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed'); | |
for (let x = 0; x < claimParent.length; x++) { | |
const { data, signatures } = claimParent[x]; | |
const txMessage = VersionedMessage.deserialize(Buffer.from(data, 'base64')); | |
const transaction = new VersionedTransaction(txMessage); | |
transaction.signatures = signatures.map((y) => Buffer.from(y, 'base64')); | |
transaction.sign([accountParentKeypair]); | |
const sendTx = await connection.sendRawTransaction(transaction.serialize(), { | |
skipPreflight: false, | |
preflightCommitment: 'confirmed' | |
}); | |
const confirmation = await connection.confirmTransaction( | |
{ | |
blockhash, | |
lastValidBlockHeight, | |
signature: sendTx, | |
}, | |
'confirmed' | |
); | |
if (confirmation.value.err) { | |
console.error(`[${i + 1}] Transaction ${x + 1} failed: ${confirmation.value.err}`); | |
} else { | |
console.log(`[${i + 1}] Transaction ${x + 1} confirmed: https://solscan.io/tx/${sendTx}`); | |
} | |
await delay(1000); | |
} | |
} catch (error) { | |
console.error('Error sending claim transaction:', error.message); | |
} | |
// Collect token and SOL | |
try { | |
let unclaim = parseInt(eligibilityCheckParent.totalUnclaimed); | |
let tokenBalance = 0; | |
do { | |
tokenBalance = await getTokenBalance(addressParent); | |
if (tokenBalance >= unclaim) break; | |
await delay(1000); | |
} while (tokenBalance < unclaim); | |
const amountToSend = tokenBalance * 1000000; | |
await sendTokenAndSolUsingFeePayer(accountParent, bankAccount.address, feePayer, amountToSend); | |
} catch (error) { | |
console.error('Error sending token:', error.message); | |
} | |
await delay(3000); | |
console.log(`[${i + 1}] Done!`); | |
} | |
})(); |
This file contains hidden or 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
/* eslint-disable no-prototype-builtins */ | |
/* eslint-disable init-declarations */ | |
/* eslint-disable new-cap */ | |
/* eslint-disable max-params */ | |
/* eslint-disable no-param-reassign */ | |
const fetch = require('node-fetch'); | |
const { CurlGenerator } = require('curl-generator') | |
const shell = require('shelljs'); | |
const baseProgram = `${process.cwd()}/utils/curl_impersonate -sS` | |
const Uas = require('user-agents'); | |
const userAgent = new Uas({ deviceCategory: 'desktop' }); | |
const isJsonString = (str) => { | |
try { | |
JSON.parse(str); | |
} catch (e) { | |
return false; | |
} | |
return true; | |
} | |
const fetcher = (url, method, headers, body) => new Promise((resolve, reject) => { | |
const params = { | |
url, | |
method, | |
headers, | |
redirect: 'follow' | |
} | |
if (body) params.body = body | |
const generatedCommand = CurlGenerator(params, { silent: true, compressed: true }) | |
const result = shell.exec(`${baseProgram} ${generatedCommand}`, { async: false, silent: true }).stdout; | |
if (isJsonString(result)) { | |
resolve(JSON.parse(result)) | |
} else { | |
console.log(params); | |
console.log(result); | |
reject(result) | |
} | |
}) | |
module.exports = { | |
fetcher, | |
isJsonString | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment