How a team builds an app that uses the platform's identity, consent, records and guarded execution instead of copying
them. Apps live in their own repositories and depend only on @multiagency/sdk. Start with the package README
(src/contract/README.md, shipped as the package's README.md) and its bundled skills. This page covers what the
platform side expects. The reuse map records which workflows reused each piece as of 2026-10-16.
| The platform provides | The app owns | Never in an app |
|---|---|---|
Owner consent and app credentials (/apps/v1), or host-bound sign-in for a browser product (/gw/*) |
Its UI, copy, navigation and server | A gateway, executor, grant token or provider credential |
| Per-agent, per-procedure authorization on every call | Non-authoritative drafts and preferences | Authoritative payment, treasury, proposal or owner-intent records |
| Owner actions: prepare, the owner's signature (in the app, or on the platform's confirm page), one submission, durable status | Which owner actions it offers, and how it explains each | Signing for the owner, or treating approval as settlement |
One send site (dispatch), unresolved-operation blocking, reconciliation |
Its own release and rollback | Callbacks into the platform (none exist; a demonstrated need comes first) |
| The isolated stack and simulated provider for local proof | Its own tests | Live funds, credentials or production state |
The path for the separate app repositories; the dashboard and Treasury are moving to it. The app-server skill has
the complete examples, and treasury-app covers Treasury proposals.
- Get a service credential. The platform operator issues the deployment's
tap_…credential (pnpm app-credential). It is a server secret: never in a browser, URL or log. - Ask the owner for consent. The app's server builds
consentMessage(...)(orsetupMessage(...)for a hosted agent that doesn't exist yet), the owner's wallet signs it with NEP-413 in the app, and the server submits it. The consent names one host, deployment, Organization, agent and its scopes (read,owner,payout,treasury,treasury-execute), and it expires. - Call within the consent.
appClient({ base, credential }).agent(organization, agent).call(...)reaches only the reads and actions its scopes list. Everything else is refused before it reaches the agent. - Relay owner actions.
owner/prepareanswers either a NEP-413 intent the owner reads and signs in the app, or, for a NEP-366 action, aconfirmlink to the platform's confirm page (/confirm/<token>). A NEP-366 action is bytes the wallet can't show, so the page shows the agent's own record of its terms and releases the bytes only to the key the action names: an app can't hand the wallet a different action. The app then followsowner/status; it never signs or submits again. - Send money once. A payout request or a proposal's execution carries the app's
requestId, recorded before the call; the platform acts on it at most once. Afterunknown, read its status by that id; never retry under a new one. - Test on the stack.
pnpm test:e2e:stack --app <deployment>(add--treasuryfor Treasury) starts the real gateway, agent servers and simulated provider, and issues the app's credential at startup.test/e2e/stack-app.spec.mjsis the reference app server.
Status: wallet-owned agents only; DAO consent is not built. The read, owner and payout scopes are mainnet-observed
on platform staging (2026-10-28, 2026-10-30) through the platform's own stand-in app; treasury and treasury-execute
are offline- and stack-tested. No production host serves /apps/v1 yet. Details: agent access,
"App access".
A page the platform serves on one of its hosts, using the owner's session there.
- Choose a host and its agents. A host entry in
dashboard.jsonnames itsdemovalue, its agents and, for an independent build,frontendDist.demois a fixed enum (showcase,payables,treasury,invoices) and agents have areapayablesortreasury; a new value needs a reviewed platform config change. - Declare capabilities. Ship a
contract.jsonwithversion,productandrequiredcapabilities (ownerCapabilitiesandpayeeContractin the SDK name them). On a host served by the shared release server (frontendhost config) the gateway checks it before serving HTML and refuses an incompatible release (503, records unchanged). A legacyfrontendDistdirectory is not checked. - Call same-origin through the gateway. Sign in with
/gw/challenge→ walletsignMessage→/gw/session, read the account from/gw/mewithgatewayDiscovery, then useownerClient({ base: "/a/<agent>" }). - Keep the owner action's identity.
owner/prepare→ keep thecorrelationIddurably → showpreview.summary→ the wallet signsintent.payload(NEP-413) →owner/submitonce → followowner/statusfor thatcorrelationId. After a lost reply or a reload, follow that record; never sign or submit again. - Release independently. Follow the Invoices asset-service convention (invoices-extraction.md):
an immutable artifact that keeps old hashed assets during rollout and rolls back without touching backend records.
Offline E2E only (
owner-release,treasury-migrationspecs); not deployed. - Test on the stack.
pnpm example:build, thenpnpm test:e2e:stack --frontend examples/minimal-prototype/dist(or your build's absolute path). See testing.md.
examples/minimal-prototype/ is the reference: one rsbuild page that signs in through the gateway, reads overview for
the payout agent and pauses or resumes it with an agent_freeze/agent_unfreeze intent, keeping the correlationId
across a lost reply and a reload. test/e2e/minimal-prototype.spec.mjs runs it on the real gateway with a simulated
provider, including a lost preparation reply and a lost submission reply, with one signature and one submission in
total. It is offline-tested only.
- DAO-owned agents can't use app access yet.
- Host
demoand agentareaare fixed enums. - Owner procedures outside the Treasury/Payables set (for example
ownerIdentity,demoGuide/compose) have no packaged contract. - Independent owner-app releases are proven on the local stack only.
- No app-callback channel exists. Any server-side need is reviewed first.