Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 38 additions & 28 deletions APP_DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Endpoints (all JSON, all under `/v1`):
- `GET /v1/status`
- `GET /v1/contract/schema`
- `GET /v1/contract/nonce`
- `POST /v1/contract/tx/prepare`
- `GET /v1/contract/tx/context` (returns MSB tx context)
- `POST /v1/contract/tx`
- `GET /v1/state?key=<urlencoded>&confirmed=true|false`

Expand All @@ -128,62 +128,65 @@ Important notes:

---

## 6) Wallet → peer → contract flow (end-to-end)
## 6) Client → peer → contract flow (end-to-end)

This is the “Ethereum-style” flow: wallet discovers a peer URL, fetches a schema, prepares a tx, signs locally, then submits it.
This is the “Ethereum-style” flow: a client (typically a dapp/backend) discovers a peer URL, fetches a schema, prepares a tx, requests a wallet signature, then submits it.

### Where the dapp fits
### Where the dapp fits (dapp constructs, wallet signs)

- 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.
- A **dapp** (web/mobile UI) can read: `GET /v1/contract/schema` and `GET /v1/state`.
- For **writes**, the dapp (or a backend the dapp calls) typically:
1) fetches `nonce` + `tx/context` from the peer,
2) constructs the tx hash (`tx`) locally,
3) asks the wallet to **sign** the tx hash,
4) submits `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.
In other words: the wallet only needs to sign; it does not need to talk to the peer RPC.

### Step A — Discover contract schema

```sh
curl -s http://127.0.0.1:5001/v1/contract/schema | jq
```

Wallet uses:
Client uses:
- `contract.txTypes` (what tx 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 B — Get a nonce (client)

```sh
curl -s http://127.0.0.1:5001/v1/contract/nonce | jq
```

### Step C — Prepare a tx hash to sign
### Step C — Get tx context + build tx hash (client)

The wallet constructs a typed command (this is app-specific):
The client constructs a typed command (this is app-specific):

```json
{ "type": "catch", "value": {} }
```

Then it asks the peer to compute the `tx` hash:
Then the client asks the peer for the MSB tx context (no computation):

```sh
curl -s -X POST http://127.0.0.1:5001/v1/contract/tx/prepare \
-H 'Content-Type: application/json' \
-d '{
"prepared_command": { "type": "catch", "value": {} },
"address": "<wallet-pubkey-hex32>",
"nonce": "<nonce-hex32>"
}' | jq
curl -s http://127.0.0.1:5001/v1/contract/tx/context | jq
```

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)
The response contains an `msb` object with the fields the client needs to build the tx preimage:
- `networkId`
- `txv`
- `iw` (peer writer key)
- `bs` (subnet bootstrap)
- `mbs` (MSB bootstrap)
- `operationType` (currently `12`)

From there, the client computes locally:
- `command_hash = blake3(JSON.stringify(prepared_command))` (hex32)
- `tx = blake3(createMessage(networkId, txv, iw, command_hash, bs, mbs, nonce, operationType))` (hex32)

### Step D — Sign locally in the wallet
### Step D — Sign locally with the wallet

Wallet signs the **bytes** of `tx` (32 bytes) with its private key to produce:
- `signature` (hex64)
Expand Down Expand Up @@ -222,10 +225,17 @@ curl -s -X POST http://127.0.0.1:5001/v1/contract/tx \

### Step G — Read app state

Apps typically write under `app/...`. Read via:
Apps typically write under `app/...` (app-defined). Read via:

```sh
curl -s 'http://127.0.0.1:5001/v1/state?key=app%tuxedex%2F<wallet-pubkey-hex32>&confirmed=false' | jq
curl -s 'http://127.0.0.1:5001/v1/state?key=<urlencoded-hyperbee-key>&confirmed=false' | jq
```

Example (Tuxemon demo app):

```sh
curl -s 'http://127.0.0.1:5001/v1/state?key=app%2Ftuxedex%2F<wallet-pubkey-hex32>&confirmed=false' | jq
```
```

The `confirmed` flag controls whether you read from:
Expand Down
14 changes: 8 additions & 6 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,9 +418,9 @@ npm run peer:pear-rpc -- \
- `GET /v1/contract/schema`
- Read state:
- `GET /v1/state?key=app%2Fkv%2Ffoo&confirmed=true`
- Wallet/dApp tx flow:
- Client tx flow (dapp constructs, wallet signs):
- `GET /v1/contract/nonce`
- `POST /v1/contract/tx/prepare` body: `{ "prepared_command": { "type": "...", "value": {} }, "address": "<pubkey-hex32>", "nonce": "<hex32>" }`
- `GET /v1/contract/tx/context` (returns MSB tx context for client-side tx derivation)
- `POST /v1/contract/tx` body: `{ "tx": "<hex32>", "prepared_command": { ... }, "address": "<pubkey-hex32>", "signature": "<hex64>", "nonce": "<hex32>", "sim": true|false }`

Notes:
Expand All @@ -446,7 +446,7 @@ All nodes in the subnet must run the same Protocol/Contract logic for determinis

## How `/tx` works (the lifecycle)

When you run `/tx --command "..."` in the CLI (or a wallet uses the RPC tx flow), the flow is:
When you run `/tx --command "..."` in the CLI (or a client uses the RPC tx flow), the flow is:

1) The command string is mapped into an operation object: `{ type, value }`.
2) trac-peer hashes and signs the operation and broadcasts a settlement tx to MSB.
Expand All @@ -458,9 +458,11 @@ Where does step (1) happen?
- In the demo runner (`scripts/run-peer.mjs`) it’s in the protocol class’s `mapTxCommand(...)` (example: `src/dev/tuxemonProtocol.js`).
- The base protocol method is `Protocol.mapTxCommand(...)` in `src/protocol.js`. For your own app you override that function.

dApp tx flow specifics:
- The dApp sends a typed command (`prepared_command`) and asks the peer to compute `tx` via `POST /v1/contract/tx/prepare`.
- The wallet signs `tx` and submits it to `POST /v1/contract/tx` with `sim: true` to simulate (recommended), then `sim: false` to broadcast.
Client tx flow specifics:
- The client fetches MSB tx context from `GET /v1/contract/tx/context`.
- The client computes `command_hash = blake3(JSON.stringify(prepared_command))`, then computes `tx` from the MSB preimage fields + `nonce`.
- The wallet signs `tx`.
- The client submits the signed payload to `POST /v1/contract/tx` with `sim: true` to simulate (recommended), then `sim: false` to broadcast.

---

Expand Down
189 changes: 189 additions & 0 deletions PEER_RPC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# trac-peer RPC (HTTP) — API Reference

This is a **reference** for the public HTTP RPC exposed by `trac-peer`.

Base URL example:
- `http://127.0.0.1:5001`

All endpoints below are under the `/v1` prefix.

## Conventions

- All responses are JSON.
- Request bodies (where applicable) are JSON.
- Hex formats:
- `hex32`: 32-byte hex string (64 hex chars)
- `hex64`: 64-byte hex string (128 hex chars)

## Errors

Error responses have the shape:

```json
{ "error": "message" }
```

Common status codes:
- `200` success
- `400` bad request (missing/invalid parameters)
- `404` not found (unknown route)
- `413` request body too large
- `500` internal error

---

## `GET /v1/health`

Health check.

### Response `200`

```json
{ "ok": true }
```

---

## `GET /v1/status`

Returns a status summary for the running peer and its MSB client view.

### Query parameters
None

### Response `200`

Object with:
- `peer`: identifiers + subnet view info (writability, signed length, bootstrap, etc.)
- `msb`: MSB bootstrap/networkId/signedLength as seen by this peer’s MSB client

---

## `GET /v1/contract/schema`

Returns an ABI-like schema describing:
- which contract tx types exist (`contract.txTypes`)
- optional per-tx input structure (`contract.ops`)
- the Protocol API method schema (`api.methods`)

### Query parameters
None

### Response `200`

```json
{
"schemaVersion": 1,
"schemaFormat": "json-schema",
"contract": {
"contractClass": "TuxemonContract",
"protocolClass": "TuxemonProtocol",
"txTypes": ["catch"],
"ops": {
"catch": { "value": {} }
}
},
"api": { "methods": {} }
}
```

---

## `GET /v1/contract/nonce`

Generates a nonce for signing.

### Query parameters
None

### Response `200`

```json
{ "nonce": "<hex32>" }
```

---

## `GET /v1/contract/tx/context`

Returns the MSB transaction context needed by a client/dapp to compute the `tx` hash locally.

### Query parameters
None

### Response `200`

```json
{
"msb": {
"networkId": 918,
"txv": "<hex32>",
"iw": "<hex32>",
"bs": "<hex32>",
"mbs": "<hex32>",
"operationType": 12
}
}
```

---

## `POST /v1/contract/tx`

Simulates or broadcasts a signed contract transaction.

### Request body (JSON)

Required fields:
- `tx` (`hex32`): transaction hash computed by the client/dapp
- `prepared_command` (`object`): `{ "type": "<string>", "value": <any> }`
- `address` (`hex32`): wallet public key (hex) used for signature verification
- `signature` (`hex64`): ed25519 signature over `tx` bytes
- `nonce` (`hex32`)

Optional:
- `sim` (`boolean`, default `false`): when `true`, run MSB preflight + contract simulation; when `false`, broadcast

Example:

```json
{
"tx": "<hex32>",
"prepared_command": { "type": "catch", "value": {} },
"address": "<hex32>",
"signature": "<hex64>",
"nonce": "<hex32>",
"sim": true
}
```

### Response `200`

```json
{ "result": {} }
```

Result shape is protocol-dependent.

---

## `GET /v1/state`

Reads a single key from the subnet state (Hyperbee).

### Query parameters

- `key` (required, string): the exact Hyperbee key to read
- `confirmed` (optional, boolean, default `true`):
- `true`: read from signed/confirmed view
- `false`: read from latest local view

### Response `200`

```json
{
"key": "app/tuxedex/<pubKeyHex>",
"confirmed": false,
"value": {}
}
```
12 changes: 3 additions & 9 deletions rpc/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getState,
getContractSchema,
contractGenerateNonce,
contractPrepareTx,
contractTxContext,
contractTx,
} from "./services.js";

Expand Down Expand Up @@ -36,14 +36,8 @@ export async function handleContractNonce({ respond, peer }) {
respond(200, { nonce });
}

export async function handleContractPrepareTx({ req, respond, peer, maxBodyBytes }) {
const body = await readJsonBody(req, { maxBytes: maxBodyBytes });
if (!body || typeof body !== "object") return respond(400, { error: "Missing JSON body." });
const payload = await contractPrepareTx(peer, {
prepared_command: body.prepared_command,
address: body.address,
nonce: body.nonce,
});
export async function handleContractTxContext({ req, respond, peer, maxBodyBytes }) {
const payload = await contractTxContext(peer);
respond(200, payload);
}

Expand Down
5 changes: 2 additions & 3 deletions rpc/routes/v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
handleGetState,
handleGetContractSchema,
handleContractNonce,
handleContractPrepareTx,
handleContractTxContext,
handleContractTx,
} from "../handlers.js";

Expand All @@ -13,8 +13,7 @@ export const v1Routes = [
{ method: "GET", path: "/status", handler: handleStatus },
{ method: "GET", path: "/state", handler: handleGetState },
{ method: "GET", path: "/contract/schema", handler: handleGetContractSchema },
// Wallet→peer flow: server-side tx prepare + wallet signature + broadcast.
{ method: "GET", path: "/contract/nonce", handler: handleContractNonce },
{ method: "POST", path: "/contract/tx/prepare", handler: handleContractPrepareTx },
{ method: "GET", path: "/contract/tx/context", handler: handleContractTxContext },
{ method: "POST", path: "/contract/tx", handler: handleContractTx },
];
Loading