๐Ÿฆ€ BitClaw Dev Portal

Build a third-party app for 1bitclaw.com โ€” a decentralized platform on BSV where the UI itself lives on-chain. This page describes the runtime you will actually get, not an aspirational one.

โš ๏ธ If you found a different guide describing bc.ws, bc.store, bc.dns, bc.crypto, or geoAnchor/dnsAnchor as available to your app โ€” that guide is describing BitClaw's own first-party modules (built into the core client). Apps installed through Build App run in a locked-down sandbox with a much smaller bridge. This page is generated straight from the same spec text shown in Build App's own "Spec" tab, so it can't drift from what's actually enforced.

1.Quick start

  1. Write a module: an IIFE that registers window.BitClawV2Modules['my-app'] with { meta, mount(container, ctx), unmount() } โ€” see the Sandbox API v1 contract below for the exact skeleton.
  2. Test it locally in /local-test/ against a real shell before spending anything (see section 4).
  3. In Build App โ†’ Deploy Code: paste/upload your .js file. It's gzipped and chunked into CD| records automatically if it's over 90KB โ€” approve the resulting draft(s) in your Outbox.
  4. In Build App โ†’ Publish: fill in name/icon/description/category, point main at your deployed code, broadcast the ML| manifest.
  5. Your app appears in everyone's Browse tab once the publish TX confirms (~10 min on WhatsOnChain). Anyone who installs it gets it sandboxed, same as described here.

2.Funding โ€” getting your first satoshis

Every step that touches the chain (deploying code, publishing a manifest, registering a DNS name) is a real BSV transaction paid from your own wallet. You don't need much.

Get a wallet

Open BitClaw โ†’ Wallet page โ†’ "โœจ Generate new wallet (WIF)" creates a fresh key locally in your browser (never sent anywhere). Alternatively connect the Yours Wallet browser extension if you already use it.

Fund it

Buy a small amount of BSV on any exchange that lists it, or send some from an existing HandCash/Yours wallet, to the address shown on your Wallet page. A few thousand satoshis is enough to develop and publish several apps.

Roughly what things cost

ActionApprox. cost
Deploy code, single TX (< 90KB gzip)~30โ€“60 sats network fee + 1โ€“2 sats to anchor addresses
Deploy code, chunked (each extra 90KB)+1 TX per chunk (~30โ€“60 sats fee each)
Publish ML manifest~30โ€“60 sats fee + 1โ€“2 sats anchors (#APPS + optional category)
Register a DNS namesmall fee, see the DNS tab for current pricing

These are order-of-magnitude estimates, not quotes โ€” actual miner fee rates can change.

3.BitClaw V3 โ€” Third-Party App Spec (Sandbox API v1)

โš ๏ธ This is the contract for apps installed the way YOUR users will install
yours: Build App โ†’ Publish โ†’ their Browse โ†’ Install. Your code then runs
inside an iframe with sandbox="allow-scripts" and NO allow-same-origin โ€” the
browser itself guarantees it can't read this page's localStorage/cookies
(that's where the wallet key lives), no matter what the code does. Everything
below is what crosses that boundary. If another guide you found promises more
(bc.ws, bc.store, bc.dns, bc.crypto, bc.mindmap...), that guide is describing
BitClaw's own first-party modules, not apps installed through this tool.
Full contract, FAQ, funding/onboarding: 1bitclaw.com/dev

1. Module registration (IIFE)

(function() {
  'use strict';
  let _ctx = null;

  window.BitClawV2Modules = window.BitClawV2Modules || {};
  window.BitClawV2Modules['my-module'] = {
    meta: {
      title: 'My App',
      icon: '๐Ÿš€',
      color: '#7c3aed',       // accent color
      version: '1.0.0',
      description: 'Short description (max 200 chars)',
    },
    mount(container, ctx) {
      _ctx = ctx;
      container.innerHTML = '<div>Hello BitClaw</div>';
      // ctx.bitclaw โ€” Sandbox API v1 bridge, see section 2 below
      // ctx.params  โ€” URL sub-path segments: /v3/my-module/id โ†’ ['id']
    },
    unmount() {
      _ctx = null;
      // clean up event listeners, timers
    },
  };

  // OPTIONAL โ€” MCP opt-in (infra/mcp/ui-capabilities.mjs). Two small literals
  // let an assistant type into your form and press your buttons the way a
  // person would: mcpFields maps a name to a CSS selector for a real input
  // already in your markup, mcpActions maps a name to the real function your
  // own onclick calls. Nothing new runs โ€” the module's own code still builds
  // the record. Only expose what stops at a reviewable Outbox draft; leave
  // out anything that spends, deletes, or executes code immediately.
  window.BitClawV2Modules['my-module'].mcpFields = {
    title: '#my-title',
    body: '#my-body',
  };
  window.BitClawV2Modules['my-module'].mcpActions = {
    publish: '_myPublish',
  };
})();

2. Sandbox API v1 โ€” what ctx.bitclaw actually gives you

Base bridge โ€” always on, no declaration needed (reads are proxied through the
parent page, promise-based; writes always go through the user's Outbox):

  ctx.bitclaw.v2data.fetchHistory(address)
  ctx.bitclaw.v2data.fetchHistoryWithData(address, opts)   // history + decoded OP_RETURN in one round trip
  ctx.bitclaw.v2data.fetchTx(txid)
  ctx.bitclaw.util.addressFromHashtag(tag)   // fixed tags ONLY โ€” see section 5
  ctx.bitclaw.util.geoAnchor(hash4)          // collision-safe geo anchor (async)
  ctx.bitclaw.util.dnsAnchor(name)           // collision-safe DNS anchor (async)
  ctx.bitclaw.util.appAnchor(tag)            // collision-safe anchor scoped to YOUR app only (async)
  // Always bound to your own installed appId โ€” there's no way to pass a
  // different scope, so you can't collide with (or write into) another app's
  // anchor space, and it can't collide with yours across different tags either.
  ctx.bitclaw.data.fetch(type, opts)
  ctx.bitclaw.identity.getAddress()
  ctx.bitclaw.wallet.getAddress()          // synchronous, cached at mount
  ctx.bitclaw.wallet.isConnected()         // synchronous, cached at mount

  ctx.bitclaw.outbox.addDraft({ label, record, sats, meta })
  // Queues a draft in the user's Outbox. NOTHING broadcasts until the user
  // reviews and approves it there โ€” your code never sees a private key.

Rate limit on reads: burst 30 requests, refills ~4/sec. Over the limit you get
back { error: "Rate limit exceeded" } โ€” back off, don't hammer it.

Permission-gated bridge โ€” ONLY available if your ML manifest declares them in
a top-level "permissions" array (see section 3). Calling these without the
matching permission either rejects with an error (store) or is silently
dropped (ws/navigate โ€” those were already fire-and-forget for first-party
modules too, there's no round trip to fail):

  "permissions": ["store"]
    ctx.bitclaw.store.get(key)              โ†’ Promise<string|null>
    ctx.bitclaw.store.set(key, value)       โ†’ Promise<true>
    ctx.bitclaw.store.getJSON(key, fallback) โ†’ Promise<any>
    ctx.bitclaw.store.setJSON(key, value)   โ†’ Promise<true>
    ctx.bitclaw.store.remove(key)           โ†’ Promise<true>
    // 128KB total per app, namespaced so no other app can read or clobber
    // your keys. NOTE: async here (unlike first-party bc.store, which is
    // synchronous) โ€” it goes over postMessage. Freed automatically on
    // uninstall.

  "permissions": ["ws"]
    ctx.bitclaw.ws.watchAnchor(bsvAddress, cb)   // cb(event) on 0-conf + confirmed
    ctx.bitclaw.ws.unwatchAnchor(bsvAddress)
    // Anchor events only โ€” NOT the full first-party bc.ws (no personal addr:
    // inbox, no board: channels, no sub/onMsg/send). Max 5 anchors watched at
    // once per mounted instance.

  "permissions": ["navigate"]
    ctx.navigate(page, params)   // send the user to another BitClaw page โ€”
                                  // validated against the real route list,
                                  // your app's iframe is torn down like any
                                  // other navigation
    ctx.setParams(params)        // update your OWN app's URL params (only
                                  // takes effect while your app is still the
                                  // active route)

Still no-op / unavailable, permission or not:
  ctx.eventBus                        no-ops (logs one console.warn)
  window.localStorage / cookies       throws SecurityError (opaque origin โ€”
                                      this is the whole point of the sandbox)

Declaring a permission you don't use is harmless but pointless โ€” it just
shows up in the Install confirmation dialog users see before installing,
so only ask for what you actually need.

3. ML Manifest + Publishing to app lists

ML|<appId>|1|1|<JSON>

Required:
  name         string   Display name (3-50 chars)
  icon         string   Single emoji
  main         txid     CD record with the main JS module

Optional:
  description  string   (max 200 chars)
  version      string   semver "1.2.0"
  color        hex      "#7c3aed"
  category     string   tools|finance|social|games|education|market|productivity|developer|other
  authorPfId   pfId     pf_<id> โ€” must match your profile
  dnsName      string   registered DNS name pointing to this app
  deepLinks    object   { base, params[], example }
  requiresWallet bool
  module       string   Name to register in window.BitClawV2Modules (used by shell dynamic loader)
  requires     string[] Builtin modules to preload: ['mindmap','taskboard','dns',...]
                        liquid_fast uses this for smart preload when /d/<name> opens your app
  permissions  string[] Subset of ["store","ws","navigate"] โ€” see section 2. Absence
                        (or an old manifest published before this existed) = base
                        bridge only. Shown to users in the Install confirmation
                        dialog before they install, so only declare what you use.

TX outputs to appear in Browse list:
  [0] OP_RETURN ML|<appId>|1|1|<JSON>   (0 sat)
  [1] addressFromHashtag('#APPS')        (1 sat)  โ† registration
  [2] addressFromHashtag('#cat_tools')   (1 sat)  โ† category (optional)
  [3] change

Build App scans #APPS history โ†’ parses ML| records โ†’ Browse tab.
WOC confirmation time: ~10 min. After confirming, appears in Browse.
(There is also a 0-conf fast path: Browse watches #APPS over UMS, so your app
usually shows a "๐Ÿ†• just published" card within seconds of broadcast.)

Identity & updates (anti-hijack):
- The FIRST record seen for an appId fixes that appId's authorAddr claim.
  Later ML| records for the same appId from a DIFFERENT authorAddr are
  rejected โ€” by the apps indexer and by clients' chain-scan fallback alike โ€”
  and surfaced as a "โš  contested" badge. Nobody can take over your appId,
  IF you declared authorAddr in your first publish. Declare it.
- To ship an update: re-publish the SAME appId from the SAME authorAddr with
  a new main/mainChunks + version. Users who installed your app get an
  "โฌ† Update available" badge in Build App โ†’ Installed and update in one
  click. There is NO auto-update โ€” users always confirm.

4. Sidebar menu โ€” user installs your app

After publishing ML manifest to #APPS:
1. Any user opens Build App โ†’ Browse โ†’ finds your app
2. Clicks Install โ†’ stored in bc.settings.installed_apps (localStorage + EK on-chain sync)
3. Sidebar shows: Apps section โ†’ [Tamagotchi][PixelPlot][MindMap] + YOUR APP ICON
4. User clicks โ†’ shell loads your code (single TX or reassembled from mainChunks)
   into a sandboxed iframe โ†’ mount(root, ctx) with the Sandbox API v1 ctx from
   section 2 above โ€” NOT full ctx.bitclaw. "Open App" never falls back to an
   unsandboxed page, no matter how big your app is.

app-{id} route โ†’ ctx.params available โ†’ deep links INTO your app work:
  /v3/app-my-module/itemId  โ†’ ctx.params = ['itemId']
(ctx.navigate/setParams need the "navigate" permission โ€” see section 2 โ€”
without it your app can only read its initial params on mount, not push the
user to a different route.)

The sidebar item shows: icon + name (first 14 chars).
Builtins (tamagotchi, pixel, mindmap) are always shown; yours appears below them.

5. Anchors you can actually use

ctx.bitclaw.util.addressFromHashtag(tag) is safe ONLY for the small set of
well-known, fixed public tags (#APPS, #cat_tools, #cat_finance, ...), the
same ones this Publish tab writes to. It uses one-level BIP32 derivation
(djb2 hash) which collides ~22% of the time on short/dynamic strings, so
NEVER invent your own dynamic tag with it (no '#geo_<hash>', no per-item
'#item_<id>' via this call).

For per-item / per-user / geo anchors, use the bridged two-level sha256
derivation instead โ€” no permission needed, these are part of the base bridge:
  await ctx.bitclaw.util.geoAnchor(geo.slice(0,4))
  await ctx.bitclaw.util.dnsAnchor('my-app')
  await ctx.bitclaw.util.appAnchor('item_' + itemId)   // scoped to YOUR app only

Prefer appAnchor for anything purely internal to your own app (per-item, per-
board, per-room feeds) โ€” it's automatically namespaced to your installed appId,
so you can reuse simple tags like 'item_42' without worrying about colliding
with another app's anchors for the same tag.

6. On-chain formats this tool writes for you

CD|<id>|1|1|<JS code>          โ€” your module code (Deploy Code tab,
                                    auto-chunked into multiple CD| TXs if >90KB,
                                    manifest.mainChunks: [txid, ...])
  ML|<id>|1|1|<JSON manifest>    โ€” your app manifest (Publish tab)

Records OTHER users may write about your app (all on the per-app anchor
#app_<first 12 chars of your ML txid> โ€” read them with fetchHistoryWithData):
  ATTEST|<appTxid>|<up/down>|<comment>|<ts>|<attesterAddr>
                                 โ€” community attestation, shown in your app's
                                    detail view. Social signal only, never a gate.
  APP_INSTALL|<appId>|<ts>       โ€” anonymous install ping (queued as an optional
                                    Outbox draft on install), counted into the
                                    "โฌ‡ N" installs figure in Browse.
  ATP|...                        โ€” paid boost records (see Boost button).

Your own runtime code can still write ANY OP_RETURN string via
ctx.bitclaw.outbox.addDraft({ record: '...' }) โ€” the bridge doesn't validate
record contents, only which read METHODS you can call. Without the "ws"
permission, other apps/services can't reach you over UMS to react to your
custom record types in realtime โ€” they'd need to poll chain history instead.
Most of BitClaw's other on-chain record types (WORKER_REG, DN|REG, MM_EDIT...)
aren't things your sandboxed code can usefully act on regardless โ€” they're
for first-party modules that have the matching
bc.* service to go with them.

7. Deep links

/v3/app-my-module/itemId   โ†’ ctx.params = ['itemId']   (works)
/v3/app-my-module/a/b      โ†’ ctx.params = ['a', 'b']    (works)
ctx.navigate(...) from inside your app       (needs "navigate" permission, section 2)

Handle incoming params in mount():
  mount(container, ctx) {
    const [itemId] = ctx.params || [];
    ...
  }

8. DNS registration for your app (optional)

This is something YOU do once, from the Publish tab, when you publish โ€”
Build App itself is a first-party module with real bc.dns, so it can call
bc.dns.register('my-app', 'manifest', appTxid, 365) on your behalf. It is
NOT something your deployed app's own sandboxed code can call for its users
at runtime (no bc.dns bridge there). Once registered, 1bitclaw.com/d/my-app
opens your app for anyone.

9. Notes

- Starter kit: 1bitclaw.com/dev/template.zip โ€” module skeleton + a local
  harness whose mock bridge mirrors THIS contract (same methods, same error
  strings, same permission gating). TypeScript types: 1bitclaw.com/dev/bitclaw.d.ts.
  Example apps with sources: 1bitclaw.com/dev (Tooling section).
- Uncaught errors in your app are reported to the host page (window.onerror โ†’
  BC_APP_ERROR) and shown to the user as an "app crashed" banner instead of a
  silently blank iframe โ€” still ship your own try/catch UX for anything you
  can handle gracefully.
- Author validation: authorPfId must be in bc.profiles.list()
- Large modules: auto-chunked at 90KB per TX (GZip + CD|id|total|index|payload)
  โ€” shell now assembles mainChunks in the sandboxed path, so size is not a
  reason to lose sandboxing
- Wallet modes (for the USER installing your app): WIF (local sign) or Yours
  Wallet (external sign) โ€” irrelevant to your sandboxed code, which never
  sees a key either way
- This spec, 1bitclaw.com/dev, and shell.js's BC_READ_ALLOWLIST should always
  agree โ€” if you find a mismatch, the allowlist in shell.js is the actual
  ground truth; please report the doc drift

4.Local testing

Two complementary harnesses โ€” use both before publishing:

Starter harness (contract-accurate)

Download the starter and run npx serve . โ€” it mounts your module against a mock of the real Sandbox API v1 bridge: same methods, same error strings (Method not allowed, Rate limit exceeded, permission/quota errors), same permission gating. If your code runs clean here, it runs after install. Build App's in-product "Test ZIP" tab uses the same mirror.

/local-test/ (full shell, live chain)

1bitclaw.com/local-test/ boots a REAL shell against the live chain โ€” good for testing look & feel, routing and real data. โš ๏ธ It gives your code full first-party ctx.bitclaw, which is MORE permissive than the sandbox your users get โ€” treat the starter harness (or Build App โ†’ Test ZIP) as the authority on what will actually work.

5.Tooling: starter, types, CLI, examples

ThingWhat it is
template.zipStarter package: module skeleton + local harness + mock bridge (exact Sandbox API v1 mirror) + README.
bitclaw.d.tsTypeScript definitions for ctx/ctx.bitclaw โ€” editor autocomplete without a build step (works via JSDoc in plain JS).
publish-app.mjsStandalone CLI publisher (dep: [email protected]): gzip โ†’ chunked CD| โ†’ ML| manifest โ†’ optional DN|REG, one unconfirmed chain, NO_BROADCAST=1 dry-run supported. Same records the Publish tab writes.
infra-kit.zipSelf-host operator kit (see ยง6): the whole infra/ tree โ€” indexer/relay/worker, Docker compose, register.mjs, validators, kind presets, full README. For running services, not publishing apps.
Examples hello-world.js โ€” read-only, no permissions ยท guestbook.js โ€” store + outbox drafts + dynamic anchor reads ยท price-ticker.js โ€” data.fetch snapshots + optional ws anchor watch. Each drops straight into the starter harness or Build App โ†’ Test ZIP.

6.Run a service โ€” earn sats

The client is only a viewer: every record lives on-chain. The infrastructure that serves that data โ€” snapshot indexers, chain-reader nodes, the realtime relay, app workers โ€” is open for anyone to run, and priority subscribers pay the operator. This is the operator mirror of the app path above: same "download a kit, follow the steps" shape, aimed at people who want to host, not publish an app.

๐Ÿ›ก๏ธ Accelerator, not authority. A service you run can only make reads faster โ€” it can't forge or hide data. Every snapshot item carries its txid and is re-checkable against the chain (SPV), signed snapshots (BC_SNAP1) turn a lie into cryptographic proof, and clients always keep a direct-chain fallback. So the free tier is never a paywall โ€” payment buys priority perks (on-demand rescans, push updates, premium anchors), not access.

The four roles

RoleServesDiscovery / earns
indexPre-indexed snapshots of a data kind (GET /public-index/snapshot/<kind>). One KIND_PRESET wires anchors + transform + state-tracking + cold-sync.INDEXER_REG โ†’ shows in every user's Network panel. Priority subscriptions.
nodeChain truth โ€” any WhatsOnChain-compatible ?path= proxy. Point clients at your own for full sovereignty.NODE_REG.
relayRealtime UMS (WebSocket) + federation โ€” the accelerator for messages / live collab; chain stays the source of truth.WORKER_REG.
workerAn app service by kind (svc:<kind>) โ€” mint quotes, content keys, an oracle, a contracts executor, the pixel canvas. Any HTTP server; add the paid tier with infra/lib/paid.mjs (~10 lines).WORKER_REG with svc:<kind> โ†’ Network panel โ†’ ๐Ÿงฉ Services. Priority subscriptions.

5-minute quickstart

๐Ÿ“ฆ Download infra-kit.zip the whole infra/ kit โ€” indexer/relay/worker, Docker compose, register.mjs, validators, presets, and the full README.md. No secrets inside; scanned on every build.

  1. Get the kit. Download and unzip the button above. Everything below runs from the infra/ folder.
  2. Provision keys โ€” in your browser. Open BitClaw โ†’ Run Agent โ†’ ๐Ÿ›ฐ Control Room โ†’ My Infrastructure โ†’ + Provision. It derives a scoped worker key (your master WIF never leaves the browser), asks the few business choices (kind / geo shard / tariff), and hands you a ready .env. Save it as infra/.env, then fund the shown worker address with a few thousand sats.
  3. Validate. node infra/validate-env.mjs โ€” catches missing/typo'd vars before you spend anything.
  4. Run. cd infra && docker compose up -d indexer (or plain node infra/indexer/server.mjs). Health: curl localhost:8080/health.
  5. Register on-chain. docker compose run --rm register โ†’ you appear in every user's Network panel under "Discover from chain", tariff shown on your ๐Ÿ’ณ button.
  6. Verify. OPERATOR_ADDR=<addr> SERVICE_ID=<id> node infra/verify-registration.mjs.
  7. Earn & monitor. Subscribers' SUB payments land at your worker address; the live tariff is served from /health, so repricing is just a restart. Watch subscribers + revenue in Control Room โ†’ My Infrastructure. Kill-switch: spend the liveness-coin from that panel and the box halts in <1s.

The Provision step also writes the exact .env for you โ€” the quickstart works end-to-end from a machine that has never seen this repo. The bundled README.md is the full server-side reference (federation, geo-sharding, premium anchors, self-hosted UMS).

Kind presets (KIND_PRESET=)

One env var wires a complex kind the way the production indexers do: market social taskboard apps profiles agents dns mindmap contracts ordinals pixel tamagotchi bookstore kb games map. Empty = fully custom (you list anchors yourself). The live list any indexer serves at GET /presets is the source of truth.