There's two ways to do this.
- Pack instructions into a single transaction
If you are not bound by the transaction limitations(1232 bytes maximum, ~30 instructions max, ~18 publickeys max, 1.4m compute max), you can pack instructions into a single transaction and have any failure fail the transaction as a whole.
Example:
const Transaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: MINT_SIZE,
lamports: lamports,
programId: TOKEN_PROGRAM_ID,
}),
createInitializeMintInstruction(
mintKeypair.publicKey,
form.decimals,
publicKey,
publicKey,
TOKEN_PROGRAM_ID)
);
If the above, createInitializeMintInstruction depends on the createAccount first. Packed both in a transaction, if either of the instructions fail, the entire transaction fails.
- Manage Transaction retry on the UI
You could have each transaction separate due to limitations, but ultimately depending on each other.
const transaction1 = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: MINT_SIZE,
lamports: lamports,
programId: TOKEN_PROGRAM_ID,
})
);
const transaction2 = new Transaction().add(
createInitializeMintInstruction(
mintKeypair.publicKey,
form.decimals,
publicKey,
publicKey,
TOKEN_PROGRAM_ID)
);
const signature1 = await connection.sendTransaction(transaction1, [mintKeypair, payerKeypair])
const signature2 = await connection.sendTransaction(transaction2, [mintKeyPair, payerKeypair])
Using the above, you can use connection.confirmTransaction with the signatures to verify which signatures are actually confirmed vs which failed. This will allow you to toggle the UI at will depending on which transaction is confirmed on the network.