How to connect your app to an Anvil-Mesh node.
If you want the shortest path instead of the full protocol walkthrough, start with Add Your App To The Mesh.
- Your app signs an envelope (JSON with topic, payload, signature)
- Your app POSTs it to
POST /dataon any Anvil-Mesh node (with bearer token or x402 payment) - The mesh gossips it to all nodes interested in that topic
- Consumers query via
GET /data?topic=...or subscribe viaGET /data/subscribe?topic=... - Your app can send messages to specific identities via
POST /sendMessage - Your app can describe its topics by publishing to
meta:<topic> - Your app can declare its identity by publishing to
identity:<pubkey>
That's it. Your app is a publisher. Anvil-Mesh is the distribution layer.
You need:
- Node URL — e.g.
http://your-node:9333 - Auth token — the node operator gives you a bearer token, OR you pay per-request via x402
Test connectivity:
curl http://NODE_URL/status
# → {"node":"anvil","version":"0.1.0","headers":{"height":940965,"work":"..."}}An envelope is a signed JSON object:
{
"type": "data",
"topic": "your-app:your-topic",
"payload": "{\"rate\":42.50,\"pair\":\"BSV/USD\"}",
"signature": "3045...",
"pubkey": "02ab...",
"ttl": 60,
"timestamp": 1710000000
}Fields:
| Field | Required | Description |
|---|---|---|
type |
Yes | Always "data" |
topic |
Yes | Routing key — nodes subscribe by prefix (e.g. oracle: matches oracle:rates:bsv) |
payload |
Yes | Your data (opaque string — Anvil doesn't parse it) |
signature |
Yes | DER hex signature over the signing digest |
pubkey |
Yes | Compressed pubkey hex of the signer |
ttl |
Yes | Seconds until expiry. Use 0 + durable: true for persistent data |
durable |
No | true = persist forever (requires ttl: 0) |
timestamp |
Yes | Unix timestamp |
Signing digest (SHA-256 of this concatenation):
type + "\n" + topic + "\n" + payload + "\n" + ttl + "\n" + durable + "\n" + timestamp
import { PrivateKey } from '@bsv/sdk'
const key = PrivateKey.fromWif('your-wif')
const envelope = {
type: 'data',
topic: 'oracle:rates:bsv',
payload: JSON.stringify({ rate: 42.50, pair: 'BSV/USD', ts: Date.now() }),
ttl: 60,
durable: false,
timestamp: Math.floor(Date.now() / 1000),
}
// Build the signing preimage
const durableStr = envelope.durable ? 'true' : 'false'
const preimage = [envelope.type, envelope.topic, envelope.payload,
envelope.ttl, durableStr, envelope.timestamp].join('\n')
// IMPORTANT: pass raw preimage bytes to sign() — it SHA-256s internally.
// Do NOT pre-hash. Pre-hashing causes double-SHA256 and signature mismatch.
const sig = key.sign(Array.from(Buffer.from(preimage, 'utf8')))
envelope.signature = sig.toDER('hex')
envelope.pubkey = key.toPublicKey().toString()env := &envelope.Envelope{
Type: "data",
Topic: "oracle:rates:bsv",
Payload: `{"rate":42.50}`,
TTL: 60,
Timestamp: time.Now().Unix(),
}
env.Sign(privateKey) // sets Signature + Pubkeycurl -X POST http://NODE_URL/data \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"data","topic":"oracle:rates:bsv","payload":"{...}","signature":"...","pubkey":"...","ttl":60,"timestamp":1710000000}'Response:
{"accepted": true, "topic": "oracle:rates:bsv", "durable": false, "key": "oracle:rates:bsv:02ab...:a1b2c3d4"}The envelope is now stored on this node and gossiped to all mesh peers interested in the oracle: prefix.
From any Anvil node:
curl "http://NODE_URL/data?topic=oracle:rates:bsv&limit=10"Response:
{
"topic": "oracle:rates:bsv",
"count": 1,
"envelopes": [
{
"type": "data",
"topic": "oracle:rates:bsv",
"payload": "{\"rate\":42.50}",
"signature": "3045...",
"pubkey": "02ab...",
"ttl": 60,
"timestamp": 1710000000
}
]
}Consumers can verify the signature themselves — all fields are signed.
Use a hierarchical prefix:
your-app:category:specifics
Examples:
oracle:rates:bsv— BSV rate feedoracle:rates:btc— BTC rate feedsession:auth:tokens— session dataattestation:identity— identity attestations
Anvil routes by prefix — a node subscribing to oracle: receives all oracle:* envelopes.
If your app wants to charge consumers for data, see Non-Custodial Payment Policy.
The short version: add a monetization field to your envelope. The node enforces payment on your behalf without ever touching your funds.
{
"type": "data",
"topic": "oracle:rates:bsv",
"payload": "...",
"monetization": {
"model": "split",
"payee_locking_script_hex": "76a914<your-address>88ac",
"price_sats": 50
}
}Models: passthrough (app only), split (app + node), token (credential-based), free.
The monetization field is included in the signing digest — it can't be altered in transit.
| Action | Endpoint | Auth |
|---|---|---|
| Publish envelope | POST /data |
Bearer token or x402 |
| Query envelopes | GET /data?topic=... |
None (or x402 if gated) |
| Check node health | GET /status |
None |
| Discover peers | GET /overlay/lookup?topic=... |
None |
| Payment models | GET /.well-known/x402 |
None |