Runar covenant design

Non-custodial dividend distribution path · Version 1 · 2026-04-14

Issuer The Bitcoin Corporation Ltd, a private company limited by shares registered in England & Wales · Company number 16735102 · Registered office Flat 6, 315 Barking Road, London, E13 8EE, United Kingdom · online.bmovies@gmail.com
The goal. Eliminate the brief window during which the bMovies Platform custodies ticket revenue on behalf of royalty shareholders. A Runar-compiled Bitcoin SV smart contract holds ticket fees in a UTXO that can only be spent by constructing a fan-out transaction matching the canonical shareholder list at that block height. The Platform never touches the money.

1. Why a covenant, not just a fan-out

bMovies already does fan-out transactions — the streaming engine in src/agents/piece-payment.ts broadcasts ~1.73M per 24-hour window, each one a P2PKH spend with N outputs paying holders pro-rata. That code works. But it is custodially trusted: the Platform holds the UTXO, the Platform chooses when to broadcast it, and the Platform could (in principle) spend it elsewhere.

A covenant contract changes the trust model. The ticket fee goes straight into a UTXO locked by a script that enforces three rules:

Any attempt to spend the UTXO to a different destination fails the script check. The Platform cannot move the money to a wrong address, and neither can anyone else. The only valid spend is the one that distributes the revenue correctly.

2. Why not a stateful contract

Runar supports stateful smart contracts (see the stateful examples in /examples/ts/auction and /examples/ts/message-board), but they are not the right tool here. A stateful contract requires every interaction to update an on-chain state variable, which adds complexity and broadcast overhead. For dividend distribution we do not need persistent state — each ticket-sale batch creates a fresh UTXO, the covenant spends it once, and the UTXO is gone. It's a one-shot pay-out-or-die pattern, which is a stateless contract.

3. Contract shape (Runar TypeScript pseudocode)

This is the intended shape of the contract as it would be written in Runar's TypeScript DSL. It has not been compiled or deployed yet — this document is a design sketch, not a shipped artefact.

// examples/bmovies/DividendCovenant.runar.ts
import { SmartContract, assert, Addr, Sats, Sig, PubKey, hash256 } from 'runar-lang';

/**
 * bMovies dividend distribution covenant.
 *
 * Holds ticket-sale revenue for a single production and enforces
 * that it may only be spent by a transaction that fans out the
 * total value pro-rata to the committed shareholder list.
 *
 * Constructor commitments (baked into the locking script at deploy):
 *   - snapshotRoot: merkle root of the holder list at snapshot time
 *   - snapshotBlockHeight: block at which the snapshot was taken
 *   - totalShares: total outstanding shares at snapshot time
 *   - platformFeeAddr: address of the 1% platform fee
 *
 * Spending inputs (provided at spend time):
 *   - The ordered list of (holderAddr, shareCount) pairs
 *   - A merkle proof tying that list to snapshotRoot
 */
class DividendCovenant extends SmartContract {
  readonly snapshotRoot: bytes32;
  readonly snapshotBlockHeight: u32;
  readonly totalShares: u64;
  readonly platformFeeAddr: Addr;

  constructor(
    snapshotRoot: bytes32,
    snapshotBlockHeight: u32,
    totalShares: u64,
    platformFeeAddr: Addr,
  ) {
    super(snapshotRoot, snapshotBlockHeight, totalShares, platformFeeAddr);
    this.snapshotRoot = snapshotRoot;
    this.snapshotBlockHeight = snapshotBlockHeight;
    this.totalShares = totalShares;
    this.platformFeeAddr = platformFeeAddr;
  }

  /**
   * Distribute the UTXO's full value to the committed holder list.
   * The spending transaction's outputs must exactly match the list.
   */
  public distribute(
    holderAddrs: Addr[],
    holderShares: u64[],
    merkleProof: bytes[][],
    spentValue: Sats,
  ) {
    // 1. Verify the holder list matches the committed snapshot root
    const computedRoot = merkleRoot(holderAddrs, holderShares);
    assert(computedRoot == this.snapshotRoot);

    // 2. Verify shares sum to totalShares (no phantom holders)
    const shareSum = sum(holderShares);
    assert(shareSum == this.totalShares);

    // 3. Verify the spending tx has exactly N + 1 outputs
    //    (N for holders + 1 for the 1% platform fee)
    assert(ctx.tx.outputs.length == holderAddrs.length + 1);

    // 4. Verify each output pays the correct address + amount
    const distributable = spentValue - (spentValue / 100); // 99%
    for (let i = 0; i < holderAddrs.length; i++) {
      const expected = (distributable * holderShares[i]) / this.totalShares;
      assert(ctx.tx.outputs[i].address == holderAddrs[i]);
      assert(ctx.tx.outputs[i].value == expected);
    }

    // 5. Verify the last output is the platform fee
    const platformFee = spentValue / 100;
    assert(ctx.tx.outputs[holderAddrs.length].address == this.platformFeeAddr);
    assert(ctx.tx.outputs[holderAddrs.length].value == platformFee);
  }
}

Key design notes:

4. End-to-end flow

Phase A · Ticket sale

A viewer pays $2.99 to stream a film. The Platform's Stripe integration receives the payment, converts the 99% distributable share into $MNEE (a USD-stable BSV-21 token), and sends those sats to the covenant UTXO. The 1% platform fee is collected separately in the same checkout.

Phase B · Batch snapshot

Once a batch threshold is reached (by time — daily/weekly — or by value — e.g. $100 accumulated), the Platform fetches the current holder list from the BSV-21 indexer. Each holder's shares are counted at a specific block height. A merkle root is computed over (holderAddr, shareCount) pairs and committed as the covenant's snapshotRoot.

Phase C · Covenant deploy

The contract is compiled by Runar and deployed to a locking script on Bitcoin SV. The batch's accumulated $MNEE UTXO is sent to this locking script. From that moment on, the Platform cannot spend the UTXO elsewhere — the covenant controls it.

Phase D · Distribution spend

The Platform (or any agent, including shareholders themselves) constructs a spending transaction with N+1 outputs: one for each holder paying the pro-rata share, plus one for the 1% platform fee. The covenant script verifies the transaction structure and unlocks the UTXO. The spend is broadcast. Every holder's wallet receives a $MNEE transfer directly from the covenant, with no intermediate Platform touch.

Phase E · Audit

The covenant deploy txid and the distribution spend txid are logged to bct_distributions in the Platform database for reporting. Holders see the distribution in their app.bmovies.online/account dividend history with a link to the on-chain transaction. The audit trail is: (a) committed snapshot root, (b) covenant deploy, (c) distribution spend, (d) per-holder $MNEE receipt — all four on chain.

5. What this buys us legally

The Platform's non-custodial disclosure currently acknowledges two brief windows during which the Platform IS custodial: the commission-checkout service-fee window, and the ticket-revenue accumulation window between distribution batches. The Runar covenant eliminates the second window entirely. Once deployed:

The Platform still needs FCA regulatory review for the first window (commission checkout fee-in-advance), but that is a much smaller surface to defend than "we accept deposits on your behalf."

6. Scope, status, and estimates

Not yet built. This document is a design sketch, not a shipped contract. The elements that need to be built:

  1. The Runar contract itself — 5–8 days of contract development + testing.
  2. A snapshot builder that queries the BSV-21 indexer, produces the holder list, and computes the merkle root — 1–2 days.
  3. A deploy script that compiles the contract, funds it from ticket revenue, and broadcasts the deploy — 1 day.
  4. A spend constructor that reads the snapshot, builds the fan-out tx, and broadcasts it — 1 day.
  5. A $MNEE integration (exchange rate + conversion path) — 2–3 days depending on what $MNEE's official on-ramp looks like.
  6. FCA-aware legal review of the contract structure — budget £3k–£10k and 2–4 weeks of lawyer calendar.

Total: 2–3 weeks of engineering + a parallel legal review thread. Not a hackathon deliverable. It is a post-submission Phase 2 goal that finalises the non-custodial claim made in the prospectus.

7. References

Press Cmd+P or Ctrl+P to save this design as a PDF.
Non-custodial disclosure · $bMovies prospectus · Terms of Service