# bChat Open Feed Protocol v2

Version 2, 6 October 2026. Contact: support@bwalletx.com

## 1. Why an open BSV feed

Posts on Bitcoin SV are already public: anyone can read them from the chain. What keeps social
apps apart is their indexers and interfaces, not the data. This spec describes one shared format
for posts, replies, likes, follows and payments, so that every app can show everyone's posts,
label where each came from, and compete on its own experience.

bChat writes this format, reads it, and runs a free public indexer for it. Any app can join.

## 2. Envelope

Each action is one `OP_FALSE OP_RETURN` output, using Bitcom pipes (`|`) between protocols:

```
OP_FALSE OP_RETURN
  [B <content> <mime> <encoding> [filename]]          (posts, replies, quotes; repeatable)
| MAP SET app <your app> type <type> v 2 [keys...]
| AIP BITCOIN_ECDSA <signing address> <compact sig>   (or SIGMA, see §5)
```

Protocol prefixes:

| Protocol | Prefix |
|---|---|
| B | `19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut` |
| MAP | `1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5` |
| AIP | `15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva` |

Rules:

- `app` is the name of the app that wrote the action (bChat writes `bChat`).
- `v=2` marks this version. Readers treat a missing `v` as v1 (the same layout, minus the
  v2 additions below).
- Readers ignore keys they do not know.
- Txids are lowercase hex.
- Post text goes in B as `text/markdown`, `UTF-8`, at most 2000 characters. Media goes in
  further B parts as `binary` with a mime type and filename.

## 3. Message types

| type | Extra MAP keys | B? | Payment outputs |
|---|---|---|---|
| `post` | optional `media_<n>` | yes | none |
| reply (`post`) | `context tx tx <parent txid>` or `context url url <link>` | yes | optional tip (§6) |
| quote (`post`) | `quote <txid>` (no `context`, so not a reply) | yes | none |
| `like` / `unlike` | `context tx tx <txid>` | no | none |
| paid `like` | as `like`, plus `amount <sats>` `paid 1` | no | one to the author (§6) |
| `repost` | `context tx tx <txid>` | no | none |
| `follow` / `unfollow` | `bapID <id>` (or `idKey` / `address` without a BAP identity) | no | none |
| `tip` | `context tx tx <txid>` `amount <sats>` [`currency BSV`] | no | one to the author (§6) |
| `lock` | `context tx tx <txid>` | no | a locked output |
| `hide` / `unhide` | `context tx tx <txid>` (an author's own post, §7.1) | no | none |

v1 likes omitted `context` and wrote only `tx <txid>`. Readers accept both.

Optional source hint for posts that originated in another app: `source <app>`, plus that app's
own post id where its API is not keyed by txid (for example `twetch_post_id <n>`).

## 4. Cross-app referencing

1. If you have the on-chain txid, use `context tx tx <txid>`. This is Bitcoin Schema, and it is
   what most BSV social indexers read.
2. Replying into a Treechat thread: also write `treechat_thread_id <uuid>`, and point `tx` at the
   thread's root post.
3. If you only have a URL (no txid), use `context url url <link>`. Other indexers will not be
   able to attach it to the original.
4. Never write another app's `app=` value and never reuse its private or server-only keys.
   That is impersonation.

## 5. Signing

- Default: AIP `BITCOIN_ECDSA`, signed by the user's posting key. The signature covers
  `OP_RETURN`, every push before the AIP prefix, and a trailing `|`, hashed as a Bitcoin Signed
  Message. The signature is the 65-byte compact form.
- Optional: SIGMA (`SIGMA BSM <address> <sig> <vin>`). SIGMA binds the signature to a
  transaction input, so the OP_RETURN cannot be replayed into another transaction. That matters
  for paid likes, where the claim is "I paid". Readers accept either.
- The user's own wallet signs. A server never signs on a user's behalf.

## 6. Payments: tips and paid likes

Status: **specified; shipping in bChat soon.** Not live yet.

The action and its payment are in the same transaction, so they are atomic and an indexer can
match them:

```
out 0: OP_RETURN (MAP type like|tip ... amount <A> [paid 1] [fee <F> feeTo <app address>]
                  [home <H> homeTo <home app address>] | AIP)
out 1: P2PKH -> author address    A sats
out 2: P2PKH -> app address       F sats   (only with an optional client fee, §6.1)
out 3: P2PKH -> home app address  H sats   (only with a home app share, §6.2; out 2 when no fee)
out n: change
```

(`out 0` stands for the action output wherever it sits; the payment is always the output right
after it, a fee output the one after that, and a home share output the one after the fee, or
right after the payment when there is no fee.)

- **The author is paid directly.** Nothing passes through bChat or any relay. bChat's own client
  fee is currently 0.
- **Author address**, in this order: (a) for Bitcoin Schema posts, the post's AIP signing
  address; (b) for apps that relay posts through a shared signer, the author's own paymail or
  published payment address, never the relay's address. If no author address can be resolved,
  do not offer a paid like or tip; fall back to a free like.
- `amount` must equal output 1's value. Indexers must check that the output exists and pays the
  resolved author; otherwise the tip counts as 0.
- Defaults in bChat: paid like 1,000 sats; tip chosen by the user, minimum 546 sats.
- Network fee at the wallet's standard rate. A like transaction is about 300 bytes.
- Unlike does not refund. A paid like followed by an unlike keeps the payment and removes the like.

### 6.1 Optional client fee

The app where the reader taps tip or paid like MAY add a fee to itself, under these rules:

- **Separate and visible.** The fee is its own P2PKH output, immediately after the author's
  output, and is declared in MAP as `fee <F>` and `feeTo <address>`. The wallet's approval prompt
  must show it as a separate line ("App fee").
- **Never from the author's share.** The author's output is still exactly `amount`. The fee is
  paid on top.
- **Capped.** `F` is a whole number of sats, at least 1, at most **5% of `amount`**, and at most
  **10,000 sats**. `feeTo` must not be the author's address.
- **Readers** check the author output exactly as before; a fee output never invalidates a tip. A
  fee is valid only when the next output pays `feeTo` exactly `F` within the caps; readers ignore
  (and may flag) an invalid fee, and never count any fee towards the author's total.
- A transaction with no `fee` key has no fee. bChat currently sets none.

### 6.2 Home app share

When a reader tips or paid-likes a post that was written in **another** app (its home app, e.g.
Treechat, Twetch, Peck, Fwetch), the app where the reader taps MAY add a share for the home app,
on top, in the same transaction. bChat does: **5% of `amount`**.

- **Separate and visible.** Its own P2PKH output, right after the fee output (or right after the
  author's output when there is no fee), declared in MAP as `home <H>` and `homeTo <address>`
  (after `fee`/`feeTo` when both are present). The wallet's approval prompt shows it as its own
  line ("Home app"), and the tip sheet says so ("Treechat gets $0.01 on top").
- **Never from the author's share.** The author's output is still exactly `amount`.
- **Capped.** `H` is a whole number of sats, at least 1, at most **5% of `amount`**, and at most
  **10,000 sats**. `homeTo` must not be the author's address or `feeTo`.
- **Only to an address the home app published for itself** (see "Publishing a home payment
  address" below). Never inferred from the app's posts: a relay or shared posting address is not
  a payment address unless the app itself says it is. If no address is known, the share is skipped
  silently; the tip goes ahead without it.
- **Optional per client.** A client that does not pay home shares writes no `home` keys. Posts
  written in the paying app itself get no home share.
- **Readers** check the author output exactly as before; a home output never invalidates a tip.
  A home share is valid only when its output pays `homeTo` exactly `H` within the caps; readers
  ignore (and may flag) an invalid one and never count it towards the author's total.
- A transaction with no `home` key has no home share.

**Publishing a home payment address.** For now, the simplest thing: the Open Feed keeps a
registry of home payment addresses in bChat's source (`homePayTo` per app in
`src/lib/feed/sources.ts` of bit-sign), and an app gets listed by asking us at
support@bwalletx.com from an address we can tie to the app. The registry is empty today: bChat
will start paying each app's share as soon as that app confirms an address. A signed on-chain
declaration (`MAP SET app <app> type payto homeTo <address>`, AIP-signed by a key the app
publishes on its own domain) may replace the registry in a later version.

## 7. Open Feed member rules

### Authors first

Posts on BSV are public, and they belong to the people who wrote them, not to the app they were
written in. So the Open Feed is built around authors:

- **Every author's post can be shown by any app**, with credit and a link home (rules 1–2).
- **Authors get paid** wherever their post is read (rule 3).
- **An author's own request to hide a post is honoured** by every member app.
- **App-level visibility flags** (for example a post marked "local" or "this app only" by the app
  that wrote it, rather than chosen by its author) are noted, but they are not binding on other
  readers. An app that wants its content off the open feed can keep it off-chain or encrypt it.
- A post its home app has taken down for moderation reasons is treated as hidden.

### 7.1 Author hide requests

An author hides their own post by writing:

```
OP_FALSE OP_RETURN
  MAP SET app <your app> type hide v 2 context tx tx <txid>
| AIP BITCOIN_ECDSA <address> <compact sig>   (or SIGMA)
```

and reverses it with `type unhide` (same keys).

- **Only the author's own key counts.** A hide is valid only when its signature verifies and the
  signing address is the address that signed the original post (for Bitcoin Schema posts, the
  post's AIP address; for apps that sign with their own identity keys, that key's address). A
  hide signed by anyone else, including the app that relayed the post, is ignored.
- **Latest wins.** For each post, readers take the latest valid `hide` or `unhide` from its
  author (block order, then first-seen time). If it is `hide`, the post is hidden.
- **Any app can write it.** `app` is the writing app; a hide is honoured whichever app wrote it.
- **Readers hide, they do not delete.** The post stays on chain. Member apps drop it from feeds,
  threads, profiles and embeds, and may show "hidden by its author" in its place.
- Posts relayed through a shared signing key (where the author's key is unknown, e.g. Treechat)
  cannot be hidden this way; ask the home app instead.
- The public indexer (§9) verifies hides and serves them at `/social/hides`.

An app that joins the Open Feed agrees to five things:

1. **Attribution.** Show a source label on every post from another app ("from bChat",
   "from <app>"). Never present another app's post as your own.
2. **Link home.** Every syndicated post links back to the original on its home app.
3. **Pay the author.** Tips and paid likes go to the author's own address or paymail (§6),
   never to the app that relayed the post. A home app share (§6.2) goes to the home app on top
   of that, never out of the author's amount. Each app publishes a stable payment destination
   per author.
4. **Own moderation.** Each app applies its own filters, mutes and takedown list, and is never
   obliged to show anything. Hiding happens when reading; on-chain data is untouched. Authors'
   own hide requests are honoured across apps.
5. **Write as yourself.** Your actions carry your own `app=` value and the user's own
   signature (§5).

## 8. Reader rules

- Count likes per post per signer (BAP id, or address). The last of like/unlike wins.
- When merging counts from another app's API, show them separately ("N on <app> + M on bChat").
  Never claim your likes appear in another app.
- Check payment outputs (§6) before showing tip totals. Never count a client fee (§6.1) or a home
  app share (§6.2) as a tip.
- Hide a post when the latest valid hide/unhide from its own author is a hide (§7.1).
- Filtering is each reader's choice and changes only what that reader sees. Content filters
  (language, sensitive media, mutes) are display-only: never treat a filtered post as deleted,
  and never stop serving, counting or linking to it because some reader filters it. Author hides
  (§7.1) and an app's own takedowns (§7, rule 4) are separate from filtering.
- Ignore unknown keys and unknown types rather than failing.

## 9. Free public indexer

bChat runs a public indexer of this format at **https://push.bwalletx.com/feed**. No key, open
CORS, rate-limited per IP, cached. Responses are JSON.

| Method | Path | Returns |
|---|---|---|
| GET | `/health` | Indexer status: last indexed block, chain tip, post count |
| GET | `/social/feed?page=&limit=` | Latest posts, newest first |
| GET | `/social/post/<txid>` | One post |
| GET | `/social/post/<txid>/reply?page=&limit=` | Replies to a post |
| GET | `/social/post/<txid>/like?limit=` | Likes on a post |
| GET | `/social/post/address/<address>?page=&limit=` | Posts by one signing address |
| GET | `/social/hides?tx=<txid>,<txid>…` | Author hides (§7.1) for up to 100 posts: `{hidden:[{tx, address, by, ts}]}`, latest action per signer, hides only. Check `address` is the post's author key |
| POST | `/ingest` with `{"rawTx": "<hex>"}` | Indexes a just-broadcast transaction immediately, so it shows before the block indexer sees it |

Lists from this indexer already leave out posts hidden by their own author.

Example:

```
curl https://push.bwalletx.com/feed/social/feed?limit=20
```

## 10. How an app joins

1. **Read.** Pull posts from the public indexer (or run your own indexer of this format) and show
   them with a source label and a link home.
2. **Expose payment destinations.** Publish a stable per-author paymail or address, so tips from
   any member app reach the right person. This matters most if your app relays posts through a
   shared signer.
   **Optionally, publish your app's own payment address** to receive the home app share (§6.2)
   when readers in other apps tip your posts.
3. **Optionally, count others' actions.** Index likes, replies and reposts written by other
   member apps against your posts' txids, at least those that carry a verified payment to the
   author (a paid like is spam-resistant).
4. **Confirm.** Exchange a test txid each way with us to check that posts and replies display.

Your data stays yours, your users stay yours, and you can leave at any time. In return your
posts reach every member app's users, those users can pay your authors, and every syndicated
post links back to you.

Write to support@bwalletx.com to join or to propose changes.

## 11. Join the Open Feed: embed it in minutes

Show the Open Feed on your site or in your app with one tag:

```html
<script src="https://www.bitcoinchat.online/embed/feed.js" data-source="bchat" data-limit="20"></script>
```

It renders a small feed right after the tag: each post with its author, a "from <app>" source
badge linking to the original, and a link to the post on bChat.

| Attribute | Values | Default |
|---|---|---|
| `data-source` | `all`, `bchat`, `treechat`, `twetch`, `peck`, `fwetch` | `all` |
| `data-limit` | 1 to 50 | 20 |
| `data-theme` | `auto` (follows the reader's light/dark setting), `light`, `dark` | `auto` |
| `data-target` | id of an element to render into | right after the tag |

Or use an iframe:

```html
<iframe src="https://www.bitcoinchat.online/embed/feed?source=bchat&limit=20&theme=auto"
        width="100%" height="600" style="border:0" loading="lazy" title="bChat Open Feed"></iframe>
```

Or read the JSON yourself (public, CORS open, no key):

```
GET https://www.bitcoinchat.online/api/feed/open?source=bchat&limit=20
→ { "feed", "home", "spec", "source",
    "posts": [{ "txid", "url", "text", "mediaCount", "author": {"name","handle"},
                "source", "sourceLabel", "sourceUrl", "at", "replyTo", "likes", "replies" }] }
```

- **Moderated.** Posts hidden by their authors (§7.1), bChat's takedown list, and posts our
  filters flag as adult are left out. Replies are left out; the feed shows top-level posts.
- **No tracking.** No cookies, no storage, no analytics. The widget makes one request, to
  bitcoinchat.online, with no credentials and no referrer. It shows text only, so it loads
  nothing from third parties; media is a link away.
- **Attributed.** Keep the source badge and the link to each post. That is what rules 1 and 2
  (§7) ask of every member app.
- Rate-limited per IP and cached for about a minute.

## 12. Test vectors

These were produced by bChat's own script builder and signer. The test key is the private key
`0x01` (a well-known public test key; never use it for funds), address
`1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH`. AIP signatures are deterministic (RFC 6979), so you should
reproduce them byte for byte. They show the layout bChat writes today, which has no `v` key
(readers treat it as v1).

Referenced txid: `b6006fca35b647f5a186f60987811871fbfda6eeffeee52be4fde287d8852cae`

**Post "gm from bChat", signed** (locking script hex):

```
006a2231394878696756345179427633744870515663554551797131707a5a56646f4175740d676d2066726f6d2062436861740d746578742f6d61726b646f776e055554462d38017c223150755161374b36324d694b43747373534c4b79316b683536575755374d745552350353455403617070056243686174047479706504706f7374017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d4841209c8e195d58f5e0d3182971a6c65f8034592e21fe221b0fe1d4466219fb74b88f21006b7a6bae1e65547939c4b2a58f7a8031743c56dd1df00bbdcc1ea37d1f96
```

**Reply "agreed" to the referenced txid, unsigned:**

```
006a2231394878696756345179427633744870515663554551797131707a5a56646f417574066167726565640d746578742f6d61726b646f776e055554462d38017c223150755161374b36324d694b43747373534c4b79316b683536575755374d745552350353455403617070056243686174047479706504706f737407636f6e746578740274780274784062363030366663613335623634376635613138366636303938373831313837316662666461366565666665656535326265346664653238376438383532636165
```

Its AIP signature (compact, hex):
`1fda2347e7afe4b887e1c5612abc9bb400fc5c7f850cf7e3146fb53de4b6d1b07d273e81e346eda0121d12d2d455e23a0aa9f2bf3a36eda741f2033b70b7f8be5e`

**Like on the referenced txid (v1 layout, no `context`), signed:**

```
006a223150755161374b36324d694b43747373534c4b79316b683536575755374d7455523503534554036170700562436861740474797065046c696b650274784062363030366663613335623634376635613138366636303938373831313837316662666461366565666665656535326265346664653238376438383532636165017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d484120c16616413e890c25e26cb7f2cb09b58c03d61af62dbc695b17ce32680c62bafc5a168d97b3362ac25e4fcf6e4f5b1b7baca4d6c1bcef518647986ce3dafa13d5
```

**Author hide of the referenced txid (§7.1), signed:**

```
006a223150755161374b36324d694b43747373534c4b79316b683536575755374d745552350353455403617070056243686174047479706504686964650176013207636f6e746578740274780274784062363030366663613335623634376635613138366636303938373831313837316662666461366565666665656535326265346664653238376438383532636165017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d48411f5c5320a70b066b16bb5b82e84ba7cd8a45c0e66cb37f7f436bd8a4148035afbe6f6ac33daf9fbb99714e238a7fb44b73aba63f152b174e927c816ccfcd1b0602
```

**Tip of 10,000 sats with a 500-sat client fee (§6.1), signed.** The fee goes to the test address
here only to keep the vector self-contained; a real fee never goes to the payer or the author.

```
006a223150755161374b36324d694b43747373534c4b79316b683536575755374d7455523503534554036170700562436861740474797065037469700176013207636f6e74657874027478027478406236303036666361333562363437663561313836663630393837383131383731666266646136656566666565653532626534666465323837643838353263616506616d6f756e74053130303030036665650335303005666565546f22314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d48017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d48411f1523bd266b772b62d15a834f978168e3992a50f391834eb7ce63aeb8278e73f426758beb519b29278bfc0f046fe6257fad32eebad6523f7d4a904f7f96ec4b01
```

**Tip of 10,000 sats with a 500-sat home app share (§6.2), signed.** As above, the share goes to
the test address only to keep the vector self-contained. The transaction carries the author's
output (10,000 sats) and then the home output (500 sats to `homeTo`).

```
006a223150755161374b36324d694b43747373534c4b79316b683536575755374d7455523503534554036170700562436861740474797065037469700176013207636f6e74657874027478027478406236303036666361333562363437663561313836663630393837383131383731666266646136656566666565653532626534666465323837643838353263616506616d6f756e7405313030303004686f6d650335303006686f6d65546f22314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d48017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314267475a3974634e34726d394b427a446e374b7072517a3837535a323653414d48412049264c2ec5dd5abe1376096c1b022b8b949cf61f8da666034d2b9583dcacc4e73332a18448cd64b392f64241f51e80609833fbe72ff3deba6c3d29b4e5be94c9
```

## 13. Versioning

- The `v` key in MAP names the version. Missing `v` means v1.
- New keys and types may be added within a version; readers ignore what they do not know.
- A change that would make existing readers misread data gets a new `v`.
- This document is published at https://www.bitcoinchat.online/docs/feed-protocol, with the raw
  text at https://www.bitcoinchat.online/docs/feed-protocol.md.

Questions, corrections and join requests: support@bwalletx.com
