The previous post laid out the vision: sovereign AI agents using Clojure + BSV as two primitives that collapse six layers of vendor dependencies into zero. That post ended at “steps 4 and 5 are the frontier — the glue layer that has not been built yet.”

Today, that frontier gets paved. This post walks through the live testnet implementation of the BSV Agent Coordination Layer — a Clojure monorepo that turns simulated stubs into real BSV testnet transactions. 100 tests, 242 assertions, zero failures.

The Gap: What Was Simulated

The original codebase had working coordination logic — identity protocols, atomic wallet reserves, policy engines, escrow workflows — but every interaction with the ledger was mocked:

Layer Simulated Live Replacement
Address derivation Hardcoded "mxx..." string secp256k1 pubkey -> RIPEMD160(SHA256) -> Base58Check
Transaction spending (str "tx-" (random-nonce)) UTXO selection, P2PKH build, DER signing, broadcast
OP_RETURN anchoring Local SHA-256 hash only Real OP_RETURN tx with change output
Balance In-memory atom Bitails API (get-balance)
UTXOs Manually set Bitails API (get-unspent)
Faucet No-op WhatsonChain API + poll-for-confirmation
Network calls No retry Exponential backoff (3 retries)

The architecture required six modules with acyclic dependencies:

Architecture Diagram

The goal: replace the dotted-line simulations with solid, broadcast-ready transactions without changing a single coordination-logic test.

Step 1: Address Derivation Into the Identity Protocol

The first fix was architectural. Address derivation (pubkey -> Base58Check address) lived in testnet.client as a utility. But every agent identity is an address — it should be a first-class protocol method.

Before (testnet/client.clj, leaky):

(defn agent->address [agent]
  (pubkey->address (identity/public-key (:identity agent))))

After (coordination/identity.clj, protocol):

(defprotocol IAgentIdentity
  (agent-id [_])
  (public-key [_])
  (sign [_ data])
  (verify [_ data signature])
  (address [_] [_ version-byte]))

Every identity now answers (address identity) returning "m..." or "n..." for testnet, "1..." for mainnet. The 25-byte Base58Check encoding is self-contained:

(defn pubkey->address
  ([pubkey-bytes] (pubkey->address pubkey-bytes 0x6f))
  ([pubkey-bytes version-byte]
   (let [hash160 (ripemd160 (sha256 pubkey-bytes))
         versioned (byte-array (inc (alength hash160)))]
     (aset versioned 0 (byte version-byte))
     ;; ... checksum, encode-base58 ...
     )))

The testnet client delegates to identity now, removing the cycle that forced forward-declarations.

Architecture Diagram

Step 2: Live Transaction Building

The core of the work. A valid BSV P2PKH transaction requires:

  1. UTXO selection from Bitails API
  2. Transaction serialization (version, inputs, outputs, locktime)
  3. SIGHASH preimage construction for each input
  4. ECDSA signing with SIGHASH_ALL appended
  5. Broadcast via Bitails /tx/broadcast

UTXO Selection

Coin selection with fee estimation. The simplest approach: sort UTXOs descending, grab until we cover amount + fee:

(defn select-utxos [utxos target-amount estimated-fee]
  (let [needed (+ target-amount estimated-fee)
        sorted (sort-by (comp - :satoshis) utxos)
        selected (reduce (fn [acc u]
                           (if (>= (:total acc) needed)
                             (reduced acc)
                             (update acc :selected conj u)
                             (update acc :total + (:satoshis u))))
                         {:selected [] :total 0}
                         sorted)]
    (if (>= (:total selected) needed)
      (assoc selected :change (- (:total selected) target-amount))
      (throw (ex-info "Insufficient UTXOs" {:needed needed})))))

Transaction Serialization

Every byte matters. The BSV wire format is little-endian, with varint prefixes for variable-length fields:

Architecture Diagram

The serialization is a straightforward byte-array concatenation:

(defn- serialize-tx [tx]
  (let [ver (le32 (:version tx))
        in-count (varint (count (:inputs tx)))
        in-bytes (apply concat (for [in (:inputs tx)]
                                 (concat (hex->bytes (reverse-hex (:txid-hex in)))
                                         (le32 (:vout in))
                                         (varint (count (:script-hex in)))
                                         (hex->bytes (:script-hex in))
                                         (le32 (:sequence in)))))
        out-count (varint (count (:outputs tx)))
        out-bytes (apply concat (for [out (:outputs tx)]
                                  (concat (le64 (:satoshis out))
                                          (varint (count (:script-hex out)))
                                          (hex->bytes (:script-hex out)))))
        locktime (le32 (:locktime tx))]
    (byte-array (concat ver in-count in-bytes
                        out-count out-bytes locktime))))

SIGHASH + Signing

The Bitcoin signing algorithm: double-SHA256 of the sighash preimage, ECDSA sign, append 0x01 (SIGHASH_ALL):

Architecture Diagram

The signing function uses the same Bouncy Castle SHA256withECDSA that the identity protocol uses — butnote the double-hash:

(defn- sign-input [tx input-index prevout-script-hex key-material]
  (let [preimage (sighash-preimage tx input-index prevout-script-hex)
        hash (sha256 (sha256 preimage))  ;; Bitcoin double-SHA256
        sig-der (ecdsa-sign key-material hash)
        sig-with-hash (byte-array (inc (alength sig-der)))]
    (System/arraycopy sig-der 0 sig-with-hash 0 (alength sig-der))
    (aset sig-with-hash (alength sig-der) (byte 0x01))  ;; SIGHASH_ALL
    ;; Build scriptSig: <varint length> <sig+hashbyte> <varint length> <pubkey>
    ...))

Full Spend Flow

The live-spend function ties it all together:

Architecture Diagram

Step 3: Real OP_RETURN Anchoring

The audit module’s simulate-anchor was a SHA-256 hash with a fake txid. The replacement live-anchor builds a transaction where:

(defn live-anchor [identity wallet data-hex & {:keys [fee-satoshis]}]
  (let [utxos (get-unspent-with-retry (identity/address identity))
        selection (select-utxos utxos 0 fee)
        inputs (mapv utxo->input (:selected selection))
        anchor-script (str "6a" (format "%02x" (quot (count data-hex) 2)) data-hex)
        outputs [{:satoshis 0 :script-hex anchor-script}]
        outputs (if (pos? (:change selection))
                  (conj outputs {:satoshis (:change selection)
                                 :script-hex (p2pkh-script (pubkey->hash160 pubkey))})
                  outputs)
        signed (sign-all-inputs {:version 1 :inputs inputs :outputs outputs} ...)
        raw-hex (bytes->hex (serialize-tx signed))
        result (broadcast-tx-with-retry raw-hex)]
    ...))

The bundle-and-anchor function now takes an optional :wallet parameter. When present, it broadcasts to testnet. When absent, it falls back to simulation — making all existing tests pass without modification.

Step 4: BRC-105 Ring Middleware

HTTP 402 Payment Required is the mechanism. The BRCC-105 spec says: server returns 402 with x-bsv-payment-* headers, client pays and retries with x-bsv-payment header.

Architecture Diagram

The Ring middleware wraps any handler and intercepts configured routes:

(defn wrap-brc105-challenge [handler & {:keys [challenge-routes price-satoshis identity-key]}]
  (fn [request]
    (let [payment-header (get-in request [:headers "x-bsv-payment"])]
      (cond
        payment-header
        (if (verify-payment-header payment-header price-satoshis identity-key)
          (handler request)
          {:status 402 :body "invalid-payment"})

        ((set challenge-routes) (:uri request))
        (let [challenge-headers (service-response->challenge price-satoshis identity-key)]
          {:status 402 :headers challenge-headers :body "payment-required"})

        :else (handler request)))))

Step 5: Retry + Resilience

Every external API call (Bitails, WhatsonChain) is wrapped with exponential backoff:

(defn with-retry [f & {:keys [max-retries base-delay-ms factor]
                        :or {max-retries 3, base-delay-ms 1000, factor 2}}]
  (loop [attempt 1]
    (let [result (try {:value (f) :error nil}
                      (catch Exception e {:value nil :error e}))]
      (if (:error result)
        (if (< attempt max-retries)
          (do (Thread/sleep (* base-delay-ms (long (Math/pow factor (dec attempt)))))
              (recur (inc attempt)))
          (throw (:error result)))
        (:value result)))))

The refresh-utxos function provides a recovery path after failed broadcasts:

(defn refresh-utxos [identity]
  (let [addr (identity/address identity)
        utxos (get-unspent-with-retry addr)]
    {:address addr :utxos utxos}))

The Test Suite: 100 Tests, 242 Assertions

Every layer has test coverage. The new code added tests for:

Test Group Tests What It Proves
Address derivation 6 Protocol works, testnet/mainnet bytes, consistency
Varint/hex/LE encoding 5 Wire format helpers are correct
UTXO selection 2 Coin selection for sufficient/insufficient funds
Transaction structure 2 Build P2PKH tx, serialize roundtrip
Signing flow 1 ScriptSig is non-empty after signing
Retry logic 2 Returns value, throws after exhaustion
BRC-105 middleware 5 402 issued, verified, rejected
Faucet/hash160/refresh 4 Offline handling, byte lengths
Architecture Diagram
;; From the REPL:
;; Ran 100 tests containing 242 assertions.
;; 0 failures, 0 errors.

The Architecture Decoupling

The critical design constraint: coordination.audit needed to call coordination.testnet.client/live-anchor, but testnet.client already depended on audit for bytes->hex. This created a circular dependency.

The solution: pull hex->bytes and bytes->hex into testnet.client as local utilities (they are one-liners), then make audit depend on testnet.client only optionally through a :wallet key in bundle-and-anchor.

Architecture Diagram

No cycles. No shared mutable state. Each module is independently testable.

The Agile Angle

This implementation followed the Scrum framework from the course: one-week sprints, daily self-inspection, Friday freezes. The product backlog was ordered by dependency (addresses before transactions, transactions before anchoring, anchoring before middleware).

The empirical pillars — transparency (open PRs), inspection (test suite), adaptation (breaking the audit cycle) — are not theoretical. They directly shaped the implementation.

What This Enables

An agent with a BSV keypair and this coordination layer can now:

  1. Derive its own testnet address
  2. Fetch live UTXO balances from Bitails
  3. Build and sign real P2PKH transactions
  4. Anchor OP_RETURN audit trails to testnet
  5. Request tBSV from the WhatsonChain faucet with confirmation polling
  6. Serve and verify BRC-105 HTTP 402 payments via Ring middleware
  7. Retry failed network calls with exponential backoff

The next step is the frontier the previous post described: two agents where one hires the other using these primitives. The orchestrator writes a task contract to OP_RETURN, the worker reads it, bonds a micropayment, executes, and settles — all on BSV testnet, all in Clojure, all verifiable by a third party.

The glue is laid. The transactions are real. The test suite says green.