3

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.

1 Answer 1

3

This is a shot in the dark, but since your script is trying to do multiple things at once, and the third step depends on the second step having been confirmed, you might just need to change the default commitment on your connection, ie:

const connection = new Connection(RPC_URL, 'confirmed');

More info at https://solana.com/docs/rpc#configuring-state-commitment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.