Skip to main content

VTXO Transaction Flow

After a vault is funded, transfers between vault UTXOs are expressed as PSBTs spending the cooperative leaf, then wrapped in a Tachi wire envelope and submitted to the Tachi mempool.

Pipeline

1. Build

import { buildVtxoPsbt } from "@tachibtc/taurus-vault-core";

const built = buildVtxoPsbt({
vault,
inputs: [{ txid, vout, valueSats, scriptPubKey: vault.p2tr.output.toString("hex") }],
outputs: [
{ address: receiverAddr, valueSats: 40_00000000n },
{ address: vault.p2tr.address, valueSats: 59_00000000n }, // change
],
feeSats: 1_00000000n,
});

Creates a PSBT with the vault's NUMS internal key and cooperative tap leaf attached to each input.

2. Verify

import { verifyVtxoPsbt } from "@tachibtc/taurus-vault-core";

verifyVtxoPsbt(built.psbt, vault, { maxFeeSats: 2_00000000n });

Re-checks structure, witnessUtxo.script, internal key, leaf scripts, and enforces a fee ceiling.

warning

Always pass maxFeeSats to prevent a malformed PSBT from silently draining value to miners. If you explicitly want no cap, pass allowUnboundedFee: true.

3. Sign

import { signVtxoPsbtAsUser } from "@tachibtc/taurus-vault-core";

await signVtxoPsbtAsUser(built.psbt, userSigner, vault, { maxFeeSats: 2_00000000n });

Attaches the user's Schnorr tapScriptSig. The 5-of-7 KDHT quorum contributes their signatures out-of-band.

4. Finalize

import { finalizeVtxoPsbt } from "@tachibtc/taurus-vault-core";

finalizeVtxoPsbt(built.psbt, vault, { maxFeeSats: 2_00000000n });

Assembles the witness from user + node signatures and the cooperative leaf control block.

5. Wrap in Tachi Envelope

import { buildTachiTxTransfer, signTachiTx } from "@tachibtc/taurus-vault-core";

const draft = buildTachiTxTransfer({
vault,
inputs: built.inputs,
outputs: built.outputs,
feeSats: 1_00000000n,
nonce: 0n,
psbt: built.psbt,
});

const tachi = await signTachiTx(draft, userSigner);

6. Broadcast

import { broadcastTachiTx, waitForVtxoCommit } from "@tachibtc/taurus-vault-core";

await broadcastTachiTx(tachi, {
url: "https://tachi.example.com/tx/broadcast/sync",
});

// Optionally wait for inclusion
await waitForVtxoCommit(/* ... */);
caution

In production, always use https://. For local regtest, pass { allowInsecureHttp: true } to opt into plaintext HTTP — the library refuses to leak Schnorr signatures in the clear by default.

Deposits

For initial deposits (P2PKH → vault), use buildTachiTxDeposit instead of buildTachiTxTransfer:

import { buildTachiTxDeposit, vtxoIdFromDeposit } from "@tachibtc/taurus-vault-core";

const depositTx = buildTachiTxDeposit({ vault, /* ... */ });
const vtxoId = vtxoIdFromDeposit(depositTx);

The 32-byte vtxoId is derived from the deposit envelope and used as input for subsequent transfers.

Tachi Transaction Helpers

FunctionDescription
buildTachiTxDepositBuild a deposit envelope (P2PKH → vault)
buildTachiTxTransferBuild a transfer envelope (vault → vault)
encodeTachiTxEncode a TachiTx to bytes
encodeTachiTxBase64Encode a TachiTx to base64
signTachiTxBIP-340 sign over tachiTxSigHash
tachiTxSigHashCompute the sighash for a TachiTx
computeVtxoIdCompute a vtxoId from inputs
vtxoIdFromDepositDerive vtxoId from a deposit envelope
xOnlyFromAddressExtract x-only pubkey from a P2TR address