JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 29
  • Score
    100M100P100Q52029F
  • License MIT

Trust through time — Nostr-native state anchoring on Bitcoin

Package Exports

  • blocktrails
  • blocktrails/browser

Readme

blocktrails

Reference implementation of Blocktrails — Nostr-native state anchoring on Bitcoin.

Live Demo · Specification · Profiles (MRC20) · NATEOS Viewer

What is this?

Blocktrails lets you anchor off-chain state to Bitcoin using Taproot. Each state produces a unique P2TR address. Spending from that address proves a state transition. The chain of spends is the state history — immutably ordered by Bitcoin.

base_key + sha256(state) = derived_key → P2TR address

Use cases: token ledgers, audit trails, Nostr identity anchoring, verifiable logs.

Install

npm install blocktrails

Quick Start

import { Blocktrail } from 'blocktrails';

// Create a trail with your Nostr-compatible private key
const trail = new Blocktrail(privkey);

// Genesis — commit first state to a P2TR address
const genesis = trail.genesis(JSON.stringify({ balance: 1000 }));
console.log(genesis.p2trAddress); // bc1p...

// Advance — each state gets a new address
const next = trail.advance(JSON.stringify({ balance: 900 }));
console.log(next.newP2trAddress); // bc1p... (different!)

// Verify — anyone with pubkey + states can verify
const { valid } = trail.verify(witnessPrograms);

How It Works

┌─────────────┐    scalar()    ┌─────────────┐    P2TR    ┌─────────────┐
│   State 0   │ ────────────▶  │  Address 0  │ ─────────▶ │   UTXO 0    │
└─────────────┘                └─────────────┘    fund    └──────┬──────┘
                                                                 │ spend
┌─────────────┐    scalar()    ┌─────────────┐    P2TR    ┌──────▼──────┐
│   State 1   │ ────────────▶  │  Address 1  │ ◀───────── │   UTXO 1    │
└─────────────┘                └─────────────┘            └──────┬──────┘
                                                                 │ spend
                                    ...                          ▼

Each state transition is a Bitcoin transaction. No special opcodes, just key tweaking.

API

Blocktrail Class

import { Blocktrail } from 'blocktrails';

const trail = new Blocktrail(privkey);

trail.genesis('state 0');           // Initialize
trail.advance('state 1');           // Transition
trail.advance('state 2');           // Transition

trail.currentState();               // 'state 2'
trail.currentWitnessProgram();      // Uint8Array (32 bytes)
trail.export();                     // { pubkeyBase, states, witnessPrograms }

Standalone Functions

import { genesis, transition, verify } from 'blocktrails';

// Create genesis
const g = genesis(privkey, 'initial state');
// → { witnessProgram, p2trAddress, derivedPrivkey, derivedPubkey }

// Create transition
const t = transition(privkey, 'state 0', 'state 1');
// → { signingPrivkey, prevWitnessProgram, newWitnessProgram, newP2trAddress }

// Verify chain
const result = verify(pubkeyBase, states, witnessPrograms);
// → { valid: true } or { valid: false, error: '...' }

Low-Level Primitives

import {
  scalar,              // sha256(state) mod n — the core tweak function
  derivePrivkey,       // d + t (derive privkey for state)
  derivePubkey,        // P + t·G (derive pubkey for state)
  p2trXonly,           // Compress to 32-byte x-only
  adjustPrivkeyForSigning, // BIP-340 parity adjustment
} from 'blocktrails';

// Example: derive address from state
const t = scalar(JSON.stringify({ counter: 42 }));
const P = derivePubkey(pubkeyBase, state);
const witnessProgram = p2trXonly(P);

CLI

# Install globally
npm install -g blocktrails

# Initialize a new trail
blocktrails init

# Mark state (unified command - broadcasts by default)
blocktrails mark '{"balance": 1000}'     # genesis + fund
blocktrails mark '{"balance": 900}'      # advance + spend
blocktrails mark '{"balance": 800}' --dry  # dry run (no broadcast)

# Or use separate commands
blocktrails genesis '{"balance": 1000}'  # off-chain only
blocktrails advance '{"balance": 900}'   # off-chain only
blocktrails fund --broadcast             # base → GENESIS
blocktrails spend --broadcast            # GENESIS → State 1

# Exit trail - send funds to external address
blocktrails exodus tb1p... --broadcast

# Publish to Nostr (NATEOS)
blocktrails publish --relay wss://relay.damus.io

# View trail with on-chain status
blocktrails show --online

CLI Commands

Command Description
init Create new trail (generates key or uses git config nostr.privkey)
mark <state> Unified command — add state and broadcast (use --dry for dry run)
genesis <state> Create genesis state (off-chain only)
advance <state> Advance to new state (off-chain only)
fund Move funds from base address to GENESIS (on-chain)
spend [state] Advance on-chain (to next state, or new state if provided)
exodus <address> Send funds to external address (exit trail)
publish Publish trail to Nostr relay (NATEOS)
show Show trail status (add --online for on-chain status)
export Export trail with witness programs
verify [file] Verify a trail
cache [clear|path] Show cache stats, clear cache, or show path

Supported Networks

Use --network or -n to specify: btc, tbtc3, tbtc4 (default), ltc

Transaction API

import {
  buildTransaction,
  signTransaction,
  serializeTransaction,
  broadcast,
  getUtxos,
  getFeeRates
} from 'blocktrails';

// Fetch UTXOs and fee rate
const utxos = await getUtxos(address, 'tbtc4');
const { halfHour } = await getFeeRates('tbtc4');

// Build and sign P2TR transaction
const tx = buildTransaction({
  inputs: utxos.map(u => ({ ...u, witnessProgram })),
  outputs: [{ witnessProgram: newWP, value: amount }]
});
const signed = signTransaction(tx, [signingKey], utxos);
const txHex = bytesToHex(serializeTransaction(signed));

// Broadcast
const txid = await broadcast(txHex, 'tbtc4');

Local Cache

Transaction data is cached locally in ~/.spv/{network}/tx/ to reduce API calls and enable offline access to previously fetched data.

# View cache stats
blocktrails cache

# Clear cache
blocktrails cache clear

# Show cache directory
blocktrails cache path
import {
  getCachedTx,
  cacheTx,
  getCacheStats,
  clearCache
} from 'blocktrails';

// getTransaction() automatically uses cache
const tx = await getTransaction(txid, 'tbtc4');

// Manual cache access
const cached = getCachedTx(txid, 'tbtc4');
const stats = getCacheStats('tbtc4');  // { count, size, sizeHuman }

Run Demo

npm run demo

Or try the live interactive demo on testnet4.

Run Tests

npm test  # 107 tests

Specification

  • Core Spec — The primitive: key tweaking, state commitment, verification
  • Profiles — Application schemas: Monochrome (generic), MRC20 (fungible tokens)

License

MIT