Bitcoin Transactions
Complete guide to Bitcoin transaction structure for agents. Inputs, outputs, fees, signing, and broadcast.
Bitcoin Transactions
A Bitcoin transaction is a signed data structure that transfers value from inputs (existing UTXOs) to outputs (new UTXOs). Understanding transaction structure is essential for agents that need to verify, create, or analyze payments.
Transaction Structure
┌─────────────────────────────────────────────────────────┐
│ Version (4 bytes) │
├─────────────────────────────────────────────────────────┤
│ Input Count (varint) │
├─────────────────────────────────────────────────────────┤
│ Inputs[] │
│ ├── Previous TXID (32 bytes) │
│ ├── Previous Output Index (4 bytes) │
│ ├── ScriptSig Length (varint) │
│ ├── ScriptSig (variable) │
│ └── Sequence (4 bytes) │
├─────────────────────────────────────────────────────────┤
│ Output Count (varint) │
├─────────────────────────────────────────────────────────┤
│ Outputs[] │
│ ├── Value (8 bytes, satoshis) │
│ ├── ScriptPubKey Length (varint) │
│ └── ScriptPubKey (variable) │
├─────────────────────────────────────────────────────────┤
│ Locktime (4 bytes) │
└─────────────────────────────────────────────────────────┘
JSON Representation
{
"txid": "def456789...",
"version": 2,
"locktime": 0,
"vin": [
{
"txid": "abc123...",
"vout": 0,
"prevout": {
"value": 100000,
"scriptpubkey": "0014...",
"scriptpubkey_address": "bc1q..."
},
"scriptsig": "",
"witness": ["304402...", "02a1b2..."],
"sequence": 4294967293
}
],
"vout": [
{
"value": 50000,
"scriptpubkey": "0014...",
"scriptpubkey_address": "bc1q..."
},
{
"value": 49000,
"scriptpubkey": "0014...",
"scriptpubkey_address": "bc1q..."
}
],
"size": 222,
"vsize": 141,
"weight": 561,
"fee": 1000
}
Inputs (vin)
Each input references a previous unspent output and provides proof of authorization to spend it.
Input Fields
| Field | Size | Description |
|---|---|---|
txid | 32 bytes | Transaction containing the UTXO |
vout | 4 bytes | Output index within that transaction |
scriptSig | variable | Unlock script (legacy) |
witness | variable | Witness data (SegWit) |
sequence | 4 bytes | Enable RBF, relative timelocks |
Sequence Values
| Value | Meaning |
|---|---|
0xffffffff | Final, no RBF |
0xfffffffe | Final, locktime enabled |
< 0xfffffffe | RBF enabled (replaceable) |
Outputs (vout)
Each output specifies an amount and spending conditions.
Output Fields
| Field | Size | Description |
|---|---|---|
value | 8 bytes | Amount in satoshis |
scriptPubKey | variable | Lock script (spending conditions) |
Common ScriptPubKey Types
P2PKH: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
P2SH: OP_HASH160 <scriptHash> OP_EQUAL
P2WPKH: OP_0 <pubKeyHash>
P2TR: OP_1 <tweakedPubKey>
Fee Calculation
Fees are implicit—the difference between input and output values:
fee = sum(input_values) - sum(output_values)
Example
Inputs: 100,000 sats
Outputs: 50,000 + 49,000 = 99,000 sats
Fee: 100,000 - 99,000 = 1,000 sats
Fee Rate
Fees are priced per virtual byte (vB):
fee_rate = fee / vsize (sat/vB)
Current fee estimates via API:
curl https://mempool.space/api/v1/fees/recommended
{
"fastestFee": 25,
"halfHourFee": 20,
"hourFee": 15,
"economyFee": 10,
"minimumFee": 5
}
Transaction Sizes
Virtual Size (vsize)
SegWit introduced “virtual bytes” for fee calculation:
vsize = (weight + 3) / 4
weight = base_size * 3 + total_size
Size Estimates by Type
| Transaction Type | vsize (approx) |
|---|---|
| P2WPKH → P2WPKH (1-in, 2-out) | 141 vB |
| P2TR → P2TR (1-in, 2-out) | 111 vB |
| P2PKH → P2PKH (1-in, 2-out) | 226 vB |
Transaction States
Created → Signed → Broadcast → Mempool → Confirmed
Verification Checklist
- Unconfirmed (0 conf): In mempool, not in a block
- 1 confirmation: Included in most recent block
- 6 confirmations: Generally considered final
- Deep confirmation: 100+ blocks (coinbase maturity)
Query Transaction Status
# Get transaction details
curl https://mempool.space/api/tx/{txid}
# Check confirmation status
curl https://mempool.space/api/tx/{txid}/status
{
"confirmed": true,
"block_height": 830000,
"block_hash": "000000000...",
"block_time": 1706745600
}
Creating Transactions
Step-by-Step Process
- Select UTXOs to spend (inputs)
- Calculate total input value
- Define outputs (recipient + change)
- Estimate fee based on size and fee rate
- Set change output = inputs - payment - fee
- Sign inputs with private keys
- Broadcast to network
Pseudocode
def create_transaction(utxos, recipient, amount, fee_rate):
# Select inputs
selected = select_utxos(utxos, amount)
total_input = sum(u.value for u in selected)
# Estimate size
estimated_size = estimate_vsize(len(selected), 2) # 2 outputs
fee = estimated_size * fee_rate
# Calculate change
change = total_input - amount - fee
if change < 546: # Dust threshold
fee += change
change = 0
# Build transaction
tx = Transaction()
for utxo in selected:
tx.add_input(utxo)
tx.add_output(recipient, amount)
if change > 0:
tx.add_output(change_address, change)
return tx
Replace-By-Fee (RBF)
Transactions can be replaced with higher-fee versions if:
- Sequence number < 0xfffffffe
- Replacement has higher fee AND higher fee rate
Signaling RBF
{
"vin": [{
"sequence": 4294967293 // 0xfffffffd - RBF enabled
}]
}
Broadcasting
Via API
# Broadcast raw transaction hex
curl -X POST https://mempool.space/api/tx \
-H "Content-Type: text/plain" \
-d "0200000001..."
Response
Success returns the txid:
"abc123def456..."
Error returns description:
{
"error": "bad-txns-inputs-missingorspent"
}
Agent Best Practices
- Always verify UTXOs exist before building transactions
- Use replace-by-fee for flexibility (sequence < max)
- Include change output unless dust
- Wait for confirmations based on value:
- < $100: 1 confirmation
- < $10,000: 3 confirmations
-
$10,000: 6 confirmations
- Monitor fee rates to avoid overpaying
Machine-Readable Summary
{
"topic": "bitcoin-transactions",
"components": ["inputs", "outputs", "locktime", "witness"],
"fee_calculation": "sum(inputs) - sum(outputs)",
"confirmation_thresholds": {
"unconfirmed": 0,
"tentative": 1,
"standard": 3,
"final": 6
},
"apis": {
"broadcast": "POST https://mempool.space/api/tx",
"status": "GET https://mempool.space/api/tx/{txid}/status"
}
}