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.mdarrow-up-right.

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.js

  • dev/tuxemonContract.js

To run your own app locally, here is the simplest workflow:

  1. Create dev/myProtocol.js and dev/myContract.js.

  2. Update scripts/run-peer.mjs to 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 for key/value (preferred).

If you register schemas, wallets/dapps can render forms and validate inputs before signing.

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 "...")

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)

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 in GET /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/health

  • GET /v1/status

  • GET /v1/contract/schema

  • GET /v1/contract/nonce

  • POST /v1/contract/tx/prepare

  • POST /v1/contract/tx

  • GET /v1/state?key=<urlencoded>&confirmed=true|false

Important notes:

  • --api-tx-exposed only 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/schema and to read state via GET /v1/state.

  • For writes, the dApp asks the wallet to:

    1. request nonce + prepare from the peer,

    2. sign the returned tx hash locally,

    3. submit sim: true then sim: false to 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 signed

  • command_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)

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), or

  • the signed/confirmed view (confirmed=true)


7) Minimal app skeleton (example)

Contract

Protocol

Last updated