I must admit I am quite new to Web3 programming in general and Solana is my first.
What the program I'm writing is supposed to do: (1) Read wallets with tokens already there (2) Read public keys of recipient wallet and create Associated Token Accounts for them. (3) Send tokens to said recipient wallets using createTransferCheckedInstruction
What the program does: (1) - Does successfully (2) - Does successfully (3) - Fails
The program is currently in a quite messy state and many variables names don't make any sense
const { getAccount, getMint } = require('@solana/spl-token');
import { getLogs } from "@solana-developers/helpers";
import { createAssociatedTokenAccount, createTransferCheckedInstruction, type Account } from "@solana/spl-token";
import { Connection, Keypair, PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js";
import { encode, decode } from 'bs58';
const RPC_URL: string = "https://devnet.helius-rpc.com/?api-key=...";
const connection = new Connection(RPC_URL);
const MINT = "..."
const PAYEE = Keypair.fromSecretKey(decode("..."))
function* chunks(arr, n) {
for (let i = 0; i < arr.length; i += n) {
yield arr.slice(i, i + n);
}
}
// Read the file which has the SOURCE wallets
let wallets = (await Bun.file("s_wallets_privkey.txt").text()).split("\n");
// super inefficient way to get balance of Token Account
let balPromises = [];
for (let i = 0; i < wallets.length; i++) {
let wallet = wallets[i];
let keypair = Keypair.fromSecretKey(decode(wallet));
let response = connection.getParsedTokenAccountsByOwner(keypair.publicKey, {
mint: new PublicKey(MINT),
});
balPromises.push(response);
}
let tokenAccounts = await Promise.all(balPromises);
type AccountWbalance = {
pubkey: string,
balance: number
}
type AccountWithBalanceKey = {
pubkey: string,
privkey: string,
balance: number
}
let acswbals: AccountWbalance[] = [];
for (let i = 0; i < tokenAccounts.length; i++) {
let val = tokenAccounts[i].value[0];
let owner = (await getAccount(connection, val.pubkey)).owner.toBase58();
let acbal = val.account.data.parsed.info.tokenAmount.uiAmount;
acswbals.push({ pubkey: owner, balance: acbal });
}
let pubwallets = (await Bun.file("s_wallets_pubkey.txt").text()).split("\n");
let accounts: AccountWithBalanceKey[] = [];
acswbals.forEach(accountWithBalances => {
let lnForPrivateKey = pubwallets.indexOf(accountWithBalances.pubkey)
let privkey = wallets[lnForPrivateKey]
let accountWithBalanceKey: AccountWithBalanceKey = {
pubkey: accountWithBalances.pubkey,
privkey: privkey,
balance: accountWithBalances.balance
}
let kp = Keypair.fromSecretKey(decode(privkey))
kp.publicKey.toBase58() === accountWithBalances.pubkey ? accounts.push(accountWithBalanceKey) : null
});
accounts = accounts.sort((a, b) => { return b.balance - a.balance })
// Read file that contains public keys for DESTINATION wallets
let recipientPublicKeys = (await Bun.file("send_wallets_pubkey.txt").text()).split("\n");
// Create Token Accounts
let CreatedTokenAccountsWithPublicKey = []
for (let index = 0; index < recipientPublicKeys.length; index++) {
try {
let response = await connection.getParsedTokenAccountsByOwner(new PublicKey(recipientPublicKeys[index]), {
mint: new PublicKey(MINT),
});
// Store the Public Key, along with the corresponding Token account into an object
CreatedTokenAccountsWithPublicKey.push({ pubkey: recipientPublicKeys[index], tkac: response.value[0].pubkey.toBase58() })
if (response.value[0] === undefined) {
throw new Error("No token account found for: " + recipientPublicKeys[index])
}
} catch {
console.log("Creating token account for: " + recipientPublicKeys[index])
let tkac = await createAssociatedTokenAccount(connection, PAYEE, new PublicKey(MINT), new PublicKey(recipientPublicKeys[index]))
let pubkey = recipientPublicKeys[index]
//@ts-ignore
tkac = tkac.toBase58()
// Store the Public Key, along with the corresponding Token account into an object
CreatedTokenAccountsWithPublicKey.push({ pubkey, tkac })
console.log(CreatedTokenAccountsWithPublicKey)
}
}
console.log(accounts)
let instructions = []
let j = 0
// Create Instructions
for (let i = 0; i < accounts.length; i++) {
let account = accounts[i];
if (j < CreatedTokenAccountsWithPublicKey.length) {
let recipient = CreatedTokenAccountsWithPublicKey[i].tkac;
let instruction = createTransferCheckedInstruction(
new PublicKey(account.pubkey),
new PublicKey(MINT),
new PublicKey(recipient),
new PublicKey(account.pubkey),
10000e9,
9
)
instructions.push(instruction)
} else break;
j++
}
// Chunk the Instructions into multiple TXNs to avoid TXN too large errors
instructions = [...chunks(instructions, 5)]
let txns = []
instructions.forEach(chunk => {
txns.push(new Transaction().add(...chunk))
});
let txnsPromises = []
txns.forEach(txn => {
// Calculate signers
let signersPrivKeys: Keypair[] = []
txn.instructions.forEach((instruction: TransactionInstruction) => {
instruction.keys.forEach(key => {
let keyIndex = accounts.findIndex((el) => el.pubkey === key.pubkey.toBase58())
if (keyIndex !== -1) {
signersPrivKeys.push(Keypair.fromSecretKey(decode(accounts[keyIndex].privkey)))
}
})
})
txnsPromises.push(new Promise(async (resolve, reject) => {
try {
let signedTxn = await connection.sendTransaction(txn, [PAYEE, ...signersPrivKeys])
resolve(signedTxn)
} catch (error) {
console.log(await getLogs(connection, error))
reject(error)
}
}))
});
let signedTxns = await Promise.all(txnsPromises)
console.log(signedTxns)
Complete error trace:
6428 | }
6429 | let decodedSignature;
6430 | try {
6431 | decodedSignature = bs58__default.default.decode(rawSignature);
6432 | } catch (err) {
6433 | throw new Error('signature must be base58 encoded: ' + rawSignature);
^
error: signature must be base58 encoded: Error: Simulation failed.
Message: Transaction simulation failed: Error processing Instruction 0: invalid account data for instruction.
Logs:
[
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]",
"Program log: Instruction: TransferChecked",
"Program log: Error: InvalidAccountData",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1357 of 1000000 compute units",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA failed: invalid account data for instruction"
].
Catch the SendTransactionError and call `getLogs()` on it for full details.