# Custom Script Transactions

Execute arbitrary Bitcoin scripts using `CustomScriptTransaction`.

## Overview

`CustomScriptTransaction` allows you to embed and execute arbitrary Bitcoin scripts within a Taproot or P2MR transaction. It uses a two-transaction model similar to interactions: a funding transaction creates a UTXO at a derived script address (P2TR or P2MR), and the custom script transaction spends that UTXO by satisfying the embedded script.

```mermaid
flowchart LR
    subgraph TX1["Transaction 1: Funding"]
        U["User UTXOs"] --> SA["Script Address<br/>(P2TR / P2MR)"]
        U --> Change["Change Output"]
    end

    subgraph TX2["Transaction 2: Custom Script"]
        SA2["Script UTXO"] --> Out["Script Output"]
        SA2 --> RF["Refund"]
    end

    SA -->|"creates UTXO"| SA2

    subgraph Witness["Witness Stack"]
        W1["Custom witnesses"]
        W2["Signature(s)"]
        W3["Script"]
        W4["Control block"]
    end

    Witness --> TX2
```

## Factory Method

Custom script transactions are created through `TransactionFactory.createCustomScriptTransaction()`:

```typescript
import { TransactionFactory } from '@btc-vision/transaction';

const factory = new TransactionFactory();
const [fundingTx, customTx, nextUTXOs, inputUtxos] =
    await factory.createCustomScriptTransaction(parameters);
```

The return type is a tuple: `[string, string, UTXO[], UTXO[]]`.

| Index | Type | Description |
|-------|------|-------------|
| `[0]` | `string` | Funding transaction hex |
| `[1]` | `string` | Custom script transaction hex |
| `[2]` | `UTXO[]` | Change UTXOs for subsequent transactions |
| `[3]` | `UTXO[]` | Original input UTXOs that were consumed |

## Parameters

`ICustomTransactionParameters` extends `SharedInteractionParameters` (with `challenge` omitted):

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `signer` | `Signer \| UniversalSigner` | Yes | - | Key pair used to sign inputs |
| `network` | `Network` | Yes | - | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | - | Available UTXOs to fund the transaction |
| `from` | `string` | Yes | - | Sender address |
| `to` | `string` | Yes | - | Target address for the script output |
| `feeRate` | `number` | Yes | - | Fee rate in sat/vB |
| `priorityFee` | `bigint` | Yes | - | OPNet priority fee in satoshis |
| `gasSatFee` | `bigint` | Yes | - | OPNet gas fee in satoshis |
| `script` | `(Uint8Array \| Stack)[]` | Yes | - | Array of script elements (data pushes and opcodes) |
| `witnesses` | `Uint8Array[]` | Yes | - | Witness data to satisfy the script |
| `randomBytes` | `Uint8Array` | No | Auto-generated | 32-byte random salt |
| `annex` | `Uint8Array` | No | - | Optional Taproot annex data (without `0x50` prefix) |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | No | - | ML-DSA signer |
| `useP2MR` | `boolean` | No | `false` | Use P2MR (BIP 360) instead of P2TR. Eliminates the quantum-vulnerable key-path spend. |

## Script and Witness Structure

The `script` parameter is an array of Bitcoin script elements compiled into the Taproot leaf. The `witnesses` parameter provides the data that satisfies the script at spending time.

```mermaid
flowchart TB
    subgraph ScriptDef["Script Definition"]
        S1["Data push (Uint8Array)"]
        S2["Opcode (e.g., OP_DROP)"]
        S3["Opcode (e.g., OP_CHECKSIG)"]
    end

    subgraph Compiled["Compiled Script"]
        CS["CustomGenerator.compile(script)"]
    end

    subgraph WitnessStack["Witness Stack (at spend time)"]
        W1["witnesses[0]"]
        W2["witnesses[1]"]
        W3["..."]
        Sig["Signature(s)"]
        Scr["Compiled script"]
        CB["Control block"]
    end

    ScriptDef --> CS
    CS --> WitnessStack
```

The witness stack is assembled in the custom finalizer:

1. All custom `witnesses` elements
2. Tap script signatures (from signing)
3. The compiled script (leaf script)
4. The Taproot control block
5. Optional annex data (if provided)

## Annex Data

The optional `annex` parameter allows embedding arbitrary data in the Taproot annex field. If the provided data does not start with the `0x50` prefix, it is automatically prepended:

```typescript
// Annex will be prefixed with 0x50 automatically
const parameters = {
    // ... other params
    annex: new Uint8Array([0x01, 0x02, 0x03]),
};

// Or provide the prefix yourself
const parameters2 = {
    // ... other params
    annex: new Uint8Array([0x50, 0x01, 0x02, 0x03]),
};
```

## Script Address Derivation

The script address is derived from a seed generated by hashing the random bytes:

```mermaid
flowchart LR
    RB["randomBytes"] --> Hash["hash256(randomBytes)"]
    Hash --> Seed["scriptSeed"]
    Seed --> Signer["EcKeyPair.fromSeedKeyPair()"]
    Seed --> Addr["AddressGenerator.generatePKSH()"]
```

The `CustomScriptTransaction` instance exposes:

- `scriptAddress` -- the derived PKSH address
- `p2trAddress` -- the P2TR address (either explicit `to` or computed)
- `getRndBytes()` -- the random bytes used

## Two-Transaction Flow

```mermaid
sequenceDiagram
    participant App
    participant Factory as TransactionFactory
    participant FT as FundingTransaction
    participant CST as CustomScriptTransaction

    App->>Factory: createCustomScriptTransaction(params)
    Factory->>Factory: Iterate funding amount estimation
    Factory->>FT: Create & sign funding tx
    FT-->>Factory: Signed funding tx + script UTXO
    Factory->>CST: Create custom script tx<br/>(funded by FT output)
    Factory->>CST: Sign custom script tx
    CST-->>Factory: Signed custom script tx
    Factory-->>App: [fundingHex, customHex, nextUTXOs, inputUtxos]
```

## Complete Example

```typescript
import {
    TransactionFactory,
    EcKeyPair,
    UTXO,
} from '@btc-vision/transaction';
import { networks, opcodes } from '@btc-vision/bitcoin';

async function executeCustomScript() {
    const network = networks.bitcoin;
    const factory = new TransactionFactory();

    // Create signer
    const signer = EcKeyPair.fromWIF(process.env.PRIVATE_KEY!, network);
    const address = EcKeyPair.getTaprootAddress(signer, network);

    // Fetch UTXOs
    const utxos: UTXO[] = await fetchUTXOs(address);

    // Define a custom script:
    // This script expects a preimage on the witness stack,
    // drops it, then requires a valid signature.
    const preimage = new TextEncoder().encode('secret-preimage');
    const customScript = [
        preimage,
        opcodes.OP_DROP,
        // The signer's pubkey is embedded by the generator
    ];

    // Witnesses must satisfy the script
    const witnesses = [
        preimage,    // Matches the data push + OP_DROP
    ];

    // Create and sign
    const [fundingTx, customTx, nextUTXOs, inputUtxos] =
        await factory.createCustomScriptTransaction({
            signer,
            mldsaSigner: null,
            network,
            utxos,
            from: address,
            to: address,
            feeRate: 10,
            priorityFee: 1000n,
            gasSatFee: 500n,
            script: customScript,
            witnesses,
            // useP2MR: true,  // Uncomment for quantum-safe P2MR output (bc1z...)
        });

    // Broadcast both transactions in order
    await broadcastTransaction(fundingTx);
    await broadcastTransaction(customTx);

    console.log('Custom script executed!');
    console.log('Change UTXOs:', nextUTXOs);
}
```

## Signing Process

The custom script transaction uses a dual-signing process for input 0:

```mermaid
flowchart TB
    subgraph Input0["Input 0 (Script Path)"]
        CS["Contract Signer<br/>(derived from seed)"]
        WS["Wallet Signer<br/>(user's key)"]
        CS --> CF["Custom Finalizer<br/>[witnesses, sigs,<br/>script, controlBlock, annex?]"]
        WS --> CF
    end

    subgraph InputN["Inputs 1+ (Key Path)"]
        WS2["Wallet Signer"] --> SF["Standard Finalizer"]
    end
```

Parallel signing is used for inputs 1+ when `isUniversalSigner` is available, falling back to sequential signing otherwise.

## Error Handling

```typescript
try {
    const result = await factory.createCustomScriptTransaction(params);
} catch (error) {
    const message = (error as Error).message;

    if (message.includes('Bitcoin script is required')) {
        // The script parameter is missing
    } else if (message.includes('Witness(es) are required')) {
        // The witnesses parameter is missing
    } else if (message.includes('Field "to" not provided')) {
        // The to address is missing
    } else if (message.includes('Field "from" not provided')) {
        // The from address is missing
    } else if (message.includes('Could not sign funding transaction')) {
        // Funding step failed
    }
}
```

## Best Practices

1. **Match witnesses to script.** The witness stack must satisfy the script exactly. An incorrect witness will cause the transaction to be invalid.
2. **Test scripts on regtest first.** Custom scripts can lock funds permanently if they contain errors. Always test on regtest before mainnet.
3. **Keep scripts minimal.** Larger scripts increase transaction weight and fees. Only include the opcodes you need.
4. **Use annex sparingly.** The Taproot annex is non-standard and may cause issues with some nodes or services.
5. **Broadcast in order.** The funding transaction must be accepted before the custom script transaction can spend its output.
6. **Track change UTXOs.** The third element of the return tuple (`nextUTXOs`) contains your new spendable outputs.
7. **Consider P2MR for quantum safety.** Set `useP2MR: true` to use P2MR outputs (BIP 360) instead of P2TR. P2MR commits directly to a Merkle root without a key-path spend, eliminating quantum-vulnerable internal pubkey exposure.

---

[< MultiSig Transactions](./multisig-transactions.md) | [Cancel Transactions >](./cancel-transactions.md)
