I wrote the following function to build and sign Taproot (P2TR) transactions using @cmdcode/tapscript. My intention is to support both key-path and script-path spends, and optionally both together.
The problem is that it doesn’t work as expected:
Script-path spends often fail to validate (e.g., control block errors, invalid witness, or failed script execution).
Can someone review my code and point out what’s wrong with my logic or implementation?
I’d especially appreciate advice on how to fix script-path failures and any performance improvements for the key-path case.
import { Address, Signer, Tap, Tx } from '@cmdcode/tapscript';
protected buildTaprootTx(
senderKey: { publicKey: Uint8Array; privateKey: Uint8Array },
utxos: Array<{ txid: string; vout: number; value: number }>,
recipient: string,
amountSat: number,
feeSat: number,
mode: 'key' | 'script' | 'both',
scriptLeaves: Array = [],
opReturnData?: Uint8Array | string,
changeAddr?: string
): string {
// ... (full code as in my gist, see link below)
}
full code
Questions:
-
What am I doing wrong, especially regarding script-path spending?
-
Is there a better way to structure or optimize the function for
performance and correctness? -
If you spot any obvious bugs or misunderstandings in how I use
Taproot key/script path logic, please point them out.
Any code review, suggestions, or working example references are highly appreciated. Thank you!