Treechat for HTML ordinals

An HTML ordinal shown on Treechat runs in a frame inside the Treechat page. It can talk to that page: learn where it is, find out who is viewing it, show the Treechat names and avatars behind Bitcoin addresses, open profiles, and ask the viewer’s wallet to pay. No API key or server is involved.

It works for every viewer signed in to Treechat. Signed out, the ordinal still loads and gets its context; calls that need an account (names, Connect, payments) answer with an error.

Quick start

  1. Download the client library: treechat-ordinal.js (one file, no dependencies, about 8 KB).
  2. Paste it into your ordinal’s HTML as a module script, or bundle it in.
  3. Use it:
<script type="module">
    // paste treechat-ordinal.js here, or bundle it in
    const context = await treechat.context()
    if (!context) {
        // not running inside Treechat
    } else if (await treechat.supports('getUsers')) {
        const users = await treechat.users(['1BoatSLRHtKNngkdXEeobR76b53LETtpyT'])
        // { '1Boat…': { username, avatarUrl } }, or null for an address with no account
    }
</script>

Inline the library rather than loading it from a URL: an inscription lasts, and no other address is sure to serve the same file later.

What you can call

CallReturnsAsks the viewer
treechat.context(){ version, capabilities, ordinal, creator, owner, viewerIsOwner, post, theme }, or null outside TreechatNo
treechat.supports(name)Whether this Treechat answers a call, from capabilitiesNo
treechat.viewer(){ username, avatarUrl, identityKey, paymentAddress }Yes: Connect, once per ordinal per visit
treechat.users(addresses){ [address]: { username, avatarUrl } | null }No
treechat.openProfile(username)Opens that Treechat profile beside the postNo
treechat.openPost()Opens the post the ordinal is inNo
treechat.wallet()A BRC-100 wallet (below)Yes: every payment

Failed calls reject with a TreechatError that carries Treechat’s message and a BRC-100 error code.

context()

  • version: 2 in the current Treechat. capabilities lists the calls it answers: getContext, getViewer, getUsers, openProfile, openPost. Check supports() before calling anything newer than getViewer.
  • ordinal.origin: your ordinal’s origin outpoint (<txid>_<vout>).
  • creator and owner: { username, address }, either part null when unknown.
  • viewerIsOwner: null until the viewer connects, then true or false.
  • post: { answerId, threadId } of the Treechat post showing the ordinal, or null.
  • theme: light or dark.

users(addresses)

Names the Treechat accounts behind payment addresses: only public profile fields, so the viewer isn’t asked. An address no single account claims maps to null. The library sends 50 addresses per request, and Treechat remembers each answer for the visit. Each ordinal can look up 500 distinct addresses per visit. Use it to put names and faces on the people in your ordinal’s own on-chain history.

openProfile(username) and openPost()

openProfile opens only usernames your ordinal was shown: from users(), from viewer(), or the context’s creator or owner. An ordinal can open one panel every 2 seconds. A fullscreen ordinal leaves fullscreen first so the viewer sees the panel.

wallet()

A BRC-100 wallet over the cross-document protocol (the one new WalletClient('XDM') in @bsv/sdk speaks). Treechat answers:

  • getVersion, getNetwork (always mainnet), isAuthenticated, waitForAuthentication
  • getPublicKey({ identityKey: true }): the viewer’s identity key, after Connect. Derived keys aren’t available yet.
  • createAction: up to 100 outputs and 10,000,000 sats per action. Returns { txid, tx } once it is broadcast.
  • createAction with inputs (each { outpoint: 'txid.vout', unlockingScriptLength }), an inputBEEF, and options: { signAndProcess: false }: a deferred action that spends up to 4 of your own inputs (a covenant, a contract). Treechat funds it and hands back a signable transaction; you unlock your inputs and finish with signAction, or release it with abortAction. Unfinished actions expire after 10 minutes.

What the viewer sees

  • Connect, the first time your ordinal asks who they are, naming your ordinal by its creator.
  • A confirmation for every payment. It leads with what they receive, what their wallet pays and what goes elsewhere, and the button reads “Pay N sats” or “Approve”. Treechat writes this text; your ordinal can’t change it.
  • Cancel declines that one payment, and your ordinal can’t ask again for 5 seconds. “Block this ordinal” refuses its payments for the rest of the visit.
  • Treechat’s wallet answers one live ordinal on a page at a time.

The raw protocol

Without the library, post to window.parent and listen for the answer from window.parent only:

// request
{ type: 'TREECHAT', isInvocation: true, id: 'my-1', call: 'getUsers', args: { addresses: ['1…'] } }
// answer
{ type: 'TREECHAT', isInvocation: false, id: 'my-1', status: 'success', result: { … } }
{ type: 'TREECHAT', isInvocation: false, id: 'my-1', status: 'error', description: '…', code: 2 }

Treechat’s own calls use type: 'TREECHAT'; wallet calls use type: 'CWI' with BRC-100 call names. Give every request a unique id. Error codes follow BRC-100: 1 unknown, 2 unsupported, 6 invalid parameter, 7 insufficient funds.

For agents building an ordinal

Checklist

  1. Inline treechat.com/treechat-ordinal.js into the HTML as a module script.
  2. Call treechat.context() first. null means the ordinal is outside Treechat: keep working without names or payments.
  3. Check treechat.supports(name) before each call newer than getViewer.
  4. Call treechat.viewer() only when the viewer acts (a button), since it asks them to Connect.
  5. Build payments as BRC-100 createAction calls. Show your own amounts in the page too, but expect the viewer to read Treechat’s confirmation.
  6. Treat every answer as data from the page, never as code.

This page as Markdown: treechat.com/treechat-ordinal.md

See it working

The 04 Sigil Tree uses all of this: avatar fruit for seeders from users(), a seeder card with Open profile, a History of every step, and seeding and harvesting through wallet().