Last active
May 21, 2024 22:34
-
-
Save NotoriousPyro/0082720b892ca8af0d9f1580e0118c83 to your computer and use it in GitHub Desktop.
Burn your unwanted ATAs on Solana with this script
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
import { GetProgramAccountsFilter, PublicKey, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; | |
import { connection } from "../src/connection"; | |
import config from "../src/lib/Config"; | |
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, createBurnCheckedInstruction, createCloseAccountInstruction, getAssociatedTokenAddressSync, unpackAccount, unpackMint } from "@solana/spl-token"; | |
// Not really tested more than two but you can probably fit more in. | |
const maxMintsPerTx = 2; | |
// Replace this with your own keypair | |
const owningAccount = config.keypair; | |
// This creates a tx like this: https://solscan.io/tx/5PegaAnFzaEdVWYfzHFVTzJyZr8wHKRhijgtXyGhNzaQj4HfW5U8BqaaHNhGkCbZRDePns5cnFJAb18RCx4cuLqB | |
const mints = [ | |
"9UVjMeTLsXq4Nb9CdpaWgJ4D79Vpmcpx6Bks1vZ9UU6P", | |
"5qY9aMeoqLXdv6CCfGnURz7WUjvKK3UQdAMsEcAXPTos" | |
] | |
const createComputeBudgetInstruction = (computeUnitLimit: number = 600000, computeUnitPrice: number = 1) => [ | |
ComputeBudgetProgram.setComputeUnitLimit({ | |
units: computeUnitLimit // compute units | |
}), | |
ComputeBudgetProgram.setComputeUnitPrice({ | |
microLamports: computeUnitPrice // priority fee | |
}) | |
]; | |
export async function* generateAccountInfos( | |
publicKeys: PublicKey[], | |
chunkSize: number = 100, | |
commitment: Commitment = "confirmed" | |
): AsyncGenerator<{ publicKey: PublicKey, accountInfo: AccountInfo<Buffer>, slot: number }> { | |
for (let i = 0; i < publicKeys.length; i += chunkSize) { | |
const chunk = publicKeys.slice(i, i + chunkSize); | |
const accountInfos = await connection.getMultipleAccountsInfoAndContext(chunk, { commitment }); | |
for (const [index, accountInfo] of accountInfos.value.entries()) { | |
yield { publicKey: chunk[index], accountInfo, slot: accountInfos.context.slot }; | |
} | |
} | |
} | |
export const getAccountInfos = async ( | |
publicKeys: PublicKey[], | |
chunkSize: number = 100, | |
commitment: Commitment = "confirmed" | |
): Promise<{ publicKey: PublicKey, accountInfo: AccountInfo<Buffer>, slot: number }[]> => { | |
const accountInfos = []; | |
for await (const accountInfo of generateAccountInfos(publicKeys, chunkSize, commitment)) { | |
accountInfos.push(accountInfo); | |
} | |
return accountInfos; | |
} | |
/** | |
* Finds the ATAs for the given mints and burns them, closes the account and returns the tokens to the owner. | |
*/ | |
const burnTokens = async (mints: string[]) => { | |
const allTokenAccountsFilter: GetProgramAccountsFilter[] = [ | |
{ | |
dataSize: 165 | |
}, { | |
memcmp: { | |
offset: 32, | |
bytes: owningAccount.toString(), | |
} | |
} | |
]; | |
const allTokenAccounts = await connection.getProgramAccounts( | |
TOKEN_PROGRAM_ID, { filters: allTokenAccountsFilter } | |
); | |
const allToken2022Accounts = await connection.getProgramAccounts( | |
TOKEN_2022_PROGRAM_ID, { filters: allTokenAccountsFilter } | |
); | |
const unpackedATAs = allTokenAccounts.map( | |
tokenAccount => unpackAccount(tokenAccount.pubkey, tokenAccount.account) | |
); | |
const unpacked2022ATAs = allToken2022Accounts.map( | |
tokenAccount => unpackAccount(tokenAccount.pubkey, tokenAccount.account) | |
); | |
const mintAccountInfo = await getAccountInfos(mints.map(mint => new PublicKey(mint))); | |
const closeInstructions = mintAccountInfo.map(mint => { | |
const ata = getAssociatedTokenAddressSync(mint.publicKey, owningAccount.publicKey, false, mint.accountInfo.owner); | |
const ataInfo = mint.accountInfo.owner.equals(TOKEN_2022_PROGRAM_ID) | |
? unpacked2022ATAs.find(pa => pa.address.equals(ata)) | |
: unpackedATAs.find(pa => pa.address.equals(ata)) | |
; | |
const unpackedMint = unpackMint(mint.publicKey, mint.accountInfo) | |
const burn = createBurnCheckedInstruction( | |
ata, | |
mint.publicKey, | |
owningAccount.publicKey, | |
ataInfo.amount, | |
unpackedMint.decimals, | |
undefined, | |
mint.accountInfo.owner | |
) | |
const close = createCloseAccountInstruction( | |
ata, | |
owningAccount.publicKey, | |
owningAccount.publicKey, | |
undefined, | |
mint.accountInfo.owner | |
); | |
return [burn, close]; | |
}).filter(instruction => instruction).flat(); | |
const bhInfo = await connection.getLatestBlockhashAndContext({ commitment: "confirmed" }); | |
const messageV0 = new TransactionMessage({ | |
payerKey: owningAccount.publicKey, | |
recentBlockhash: bhInfo.value.blockhash, | |
instructions: [ | |
...createComputeBudgetInstruction( | |
100_000, 100 | |
), | |
...closeInstructions, | |
], | |
}).compileToV0Message(); | |
const tx = new VersionedTransaction(messageV0); | |
tx.sign([owningAccount]); | |
const simulation = await connection.simulateTransaction(tx, { commitment: "confirmed" }); | |
if (simulation.value.err) { | |
throw simulation.value.err; | |
} | |
console.log("Simulation: ", simulation.value); | |
try { | |
const signature = await connection.sendTransaction(tx, { | |
maxRetries: 20, | |
skipPreflight: true, | |
}); | |
const confirmation = await connection.confirmTransaction({ | |
signature, | |
blockhash: bhInfo.value.blockhash, | |
lastValidBlockHeight: bhInfo.value.lastValidBlockHeight, | |
}, "confirmed"); | |
if (confirmation.value.err) { | |
throw new Error(`Transaction not confirmed: ${confirmation.value.err.toString()}`); | |
} | |
console.log("Confirmed: ", signature); | |
} catch (error) { | |
console.error("Failed to burn accounts", error); | |
throw error; | |
} | |
} | |
const main = async () => { | |
for (let i = 0; i < mints.length; i += maxMintsPerTx) { | |
const chunk = mints.slice(i, i + maxMintsPerTx); | |
await burnTokens(chunk); | |
} | |
console.log("Done"); | |
process.exit(0); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment