dApp Developer Guide for trac-peer
This guide explains how to build a dApp on trac-peer, how wallets/dApps discover what your app supports, and how clients execute app functions via the peer RPC.
For MSB with peer local setup (bootstraps, funding, subnet deployment), see DOCS.md.
In this document we will refer to two types of apps:
trac-peer app - contracts/protocol pair.
dApp - decentralized application that consumes contracts written in trac-peer.
1) Mental model (what you’re building)
MSB is the settlement layer. A transaction becomes “real” when MSB accepts it.
trac-peer runs a subnet (a smaller P2P network) that derives deterministic state from an ordered log.
Your Contract is executed locally on every subnet node from the same ordered operation, so every node derives the same state.
Your Protocol defines:
how user input is mapped into typed transaction operation:
{ type, value }optional read/query methods exposed to dApps (via
protocol.api)
Clients do not “call contract functions directly”. They submit a transaction (an operation), and the contract executes that operation during subnet apply method.
2) Example contract
The default runner is scripts/run-peer.mjs. It currently wires the demo Tuxemon app:
dev/tuxemonProtocol.jsdev/tuxemonContract.js
To run your own app locally, here is the simplest workflow:
Create
dev/myProtocol.jsanddev/myContract.js.Update
scripts/run-peer.mjsto import your protocol/contract instead of Tuxemon.
If you want to keep Tuxemon unchanged, add a second runner script (example: scripts/run-peer-myapp.mjs) that wires your app.
3) Contract (state machine)
Your contract class should extend the base contract in src/artifacts/contract.js and implement one method per supported operation type.
3.1 Registering “ABI-like” metadata (what dApps discover)
Wallets need a machine-readable description of what the contract supports. trac-peer exposes this at:
GET /v1/contract/schema
That response is constructed from contract metadata if present, and falls back to inference if you register nothing.
The base contract supports two common registration styles:
addFunction(name)- declares that an operation exists and value schema is treated as{}(untyped).addSchema(name, fastestSchema)- declares that an operation exists and provides an explicit schema forkey/value(preferred).
If you register schemas, wallets/dapps can render forms and validate inputs before signing.
3.2 Writing state (recommended key conventions)
Contracts write to subnet state via the storage methods provided by the base contract.
Recommended:
Put app state under
app/<appName>/...so it’s easy to query.Do not overwrite reserved system keys (admin/chat/tx indexing, etc).
Example keys:
app/tuxedex/<userPubKeyHex>app/counter/value
4) Protocol (command mapping + wallet API)
Your protocol class extends src/artifacts/protocol.js.
4.1 CLI mapping (/tx --command "...")
/tx --command "...")The CLI /tx starts from a string. Your protocol maps that string into a typed operation:
mapTxCommand(commandString) -> { type, value } | null
Examples:
"catch"→{ type: "catch", value: {} }"set foo bar"→{ type: "set", value: { key: "foo", value: "bar" } }
4.2 dApp-facing API (protocol.api)
protocol.api)dApps generally need:
Read/query methods (get state, derived data)
A single write path through transaction submission (prepare → sign → simulate → broadcast)
In this codebase the base protocol exposes a ProtocolApi instance at:
protocol.api
The base protocol also exposes a discovery schema at:
protocol.getApiSchema()(included inGET /v1/contract/schema)
If you add read/query methods to protocol.api (typically via the protocol’s extendApi() pattern), they can be reflected into the RPC schema so dApps know what’s available.
5) RPC endpoints (what wallets/dApps use)
Run a peer with RPC enabled:
Endpoints (all JSON, all under /v1):
GET /v1/healthGET /v1/statusGET /v1/contract/schemaGET /v1/contract/noncePOST /v1/contract/tx/preparePOST /v1/contract/txGET /v1/state?key=<urlencoded>&confirmed=true|false
Important notes:
--api-tx-exposedonly has effect if you started with--rpc.Operator/admin actions (deploy subnet, add/remove writers/indexers, chat moderation) are CLI-only and are not exposed by RPC.
6) Wallet → peer → contract flow (end-to-end)
This is the “Ethereum-style” flow: dApp discovers a peer URL, fetches a schema, prepares a transaction, wallet signs locally, then submits it.
Where the dApp fits
A dApp (web/mobile UI) talks to a peer’s RPC URL to fetch
GET /v1/contract/schemaand to read state viaGET /v1/state.For writes, the dApp asks the wallet to:
request
nonce+preparefrom the peer,sign the returned
txhash locally,submit
sim: truethensim: falseto the peer.
In other words: the dApp never needs the private key. It just passes data between the peer RPC and the wallet signer.
Step A - Discover contract schema
Wallet uses:
contract.txTypes(what transaction types exist)contract.ops[type](input structure for each type, when available)api.methods(optional read/query methods exposed by the protocol api)
Step B - Get a nonce
Step C - Prepare a tx hash to sign
The wallet constructs a typed command (this is app-specific):
Then it asks the peer to compute the tx hash:
The response contains:
tx(hex32): the exact 32-byte tx hash that must be signedcommand_hash(hex32): hash of the prepared command (used by MSB payload)
Step D - Sign locally in the wallet
Wallet signs the bytes of tx (32 bytes) with its private key to produce:
signature(hex64)
Step E - Simulate (recommended)
Simulation runs the same MSB-level validations the real transaction will face (fee balance, signature, bootstrap checks, etc.) and then executes the contract against an in-memory storage view.
Step F - Broadcast (the real transaction)
Step G - Read app state
Apps typically write under app/.... Read via:
The confirmed flag controls whether you read from:
the latest local view (
confirmed=false), orthe signed/confirmed view (
confirmed=true)
7) Minimal app skeleton (example)
Contract
Protocol
Last updated