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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ jobs:
with:
persist-credentials: false
repository: MultiAgency/platform
# The commit @multiagency/sdk 0.5.0 was published from (tag sdk-v0.5.0); startStack checks it.
ref: 6ba0831f18ca84b2093187750f1a5f0cc8ed14d9
# The commit @multiagency/sdk 0.5.1 was published from (tag sdk-v0.5.1); startStack checks it.
ref: 2223d11995042ee62d9006ff2e6520988b4e0ea5
path: platform
- uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# treasury

The Treasury app's owner UI, built on [`@multiagency/sdk`](https://www.npmjs.com/package/@multiagency/sdk) 0.5.0: the owner's Treasury pages, replacing the prototype's, in the session the owner signs in to with their wallet (`/a/<agent>/`, `ownerClient` same-origin). It lives in `web/` ([below](#the-owner-ui)). Beside it, `apps/server/` is the app's own server on the app route ([below](#the-app-server), #20): the only way to reach Treasury proposals.
The Treasury app's owner UI, built on [`@multiagency/sdk`](https://www.npmjs.com/package/@multiagency/sdk) 0.5.1: the owner's Treasury pages, replacing the prototype's, in the session the owner signs in to with their wallet (`/a/<agent>/`, `ownerClient` same-origin). It lives in `web/` ([below](#the-owner-ui)). Beside it, `apps/server/` is the app's own server on the app route ([below](#the-app-server), #20): the only way to reach Treasury proposals.

The owner UI moves to the app's own routes on the platform (`/apps/v1`): the owner session gets no production host ([MultiAgency/platform#14](https://github.com/MultiAgency/platform/issues/14)). Most of its calls move by changing its one platform module, `web/src/platform.ts`; the rest are served only by the owner session, and which of them reach app access is [MultiAgency/platform#17](https://github.com/MultiAgency/platform/issues/17). SDK 0.5.0 adds app access for DAO-owned agents (consent, setup and owner actions by the DAO's vote); it isn't on any host yet, and this repository doesn't use it yet ([MultiAgency/platform#13](https://github.com/MultiAgency/platform/issues/13)).
The owner UI moves to the app's own routes on the platform (`/apps/v1`): the owner session gets no production host ([MultiAgency/platform#14](https://github.com/MultiAgency/platform/issues/14)). Most of its calls move by changing its one platform module, `web/src/platform.ts`. SDK 0.5.1 answers which of the rest reach app access ([MultiAgency/platform#17](https://github.com/MultiAgency/platform/issues/17)); [ADR 2](docs/adr/0002-owner-ui-app-access.md) gives the replacement for each one that doesn't. The hosts serve them once the platform deploys that code.

This repository had a CLI on Treasury's machine route (`/machine/v1`). It was removed in favour of the app route: the SDK's [getting-started](https://github.com/MultiAgency/platform/blob/sdk-v0.5.0/src/contract/skills/getting-started/SKILL.md) skill says the machine route isn't extended and is retired once Treasury moves to app access, and that a new app uses `appClient`. `watch` (#2) comes back as the app server's proposer (#20).
This repository had a CLI on Treasury's machine route (`/machine/v1`). It was removed in favour of the app route: the SDK's [getting-started](https://github.com/MultiAgency/platform/blob/sdk-v0.5.1/src/contract/skills/getting-started/SKILL.md) skill says the machine route isn't extended and is retired once Treasury moves to app access, and that a new app uses `appClient`. `watch` (#2) comes back as the app server's proposer (#20).

Both run against the platform's simulated stack at [`6ba0831`](https://github.com/MultiAgency/platform/commit/6ba0831) (tag `sdk-v0.5.0`, the commit SDK 0.5.0 was published from). Its provider, chain, signatures and settlement are simulated. Pointing at a hosted platform is a separate decision. Requires Node ≥ 24.22 and pnpm 12.16, and the platform checked out beside this repository:
Both run against the platform's simulated stack at [`2223d11`](https://github.com/MultiAgency/platform/commit/2223d11) (tag `sdk-v0.5.1`, the commit SDK 0.5.1 was published from). Its provider, chain, signatures and settlement are simulated. Pointing at a hosted platform is a separate decision. Requires Node ≥ 24.22 and pnpm 12.16, and the platform checked out beside this repository:

```sh
git clone https://github.com/MultiAgency/platform.git ../platform && git -C ../platform checkout 6ba0831
git clone https://github.com/MultiAgency/platform.git ../platform && git -C ../platform checkout 2223d11
(cd ../platform && pnpm install --frozen-lockfile)
pnpm install
```
Expand All @@ -20,7 +20,7 @@ SDK and platform feedback goes to [MultiAgency/platform](https://github.com/Mult

## The app server

`apps/server/` is its own package in the pnpm workspace: Node running TypeScript directly, `node:http`, `@multiagency/sdk` 0.5.0 and `near-kit` 0.20.2. It holds the deployment's `tap_` credential and calls the platform with `appClient`. Its design, rules and plan are in #20. The first version so far:
`apps/server/` is its own package in the pnpm workspace: Node running TypeScript directly, `node:http`, `@multiagency/sdk` 0.5.1 and `near-kit` 0.20.2. It holds the deployment's `tap_` credential and calls the platform with `appClient`. Its design, rules and plan are in #20. The first version so far:

- **Owner sign-in:** a NEP-413 challenge addressed to this server, verified with `near-kit` before any chain call, then a full-access key of the signing account on chain (FastNEAR's RPC, configured as the platform's `src/chain.ts`). Challenges are stateless (the nonce carries its issue time and an HMAC under a per-process key), so issuing one stores nothing and a flood can't crowd out an owner's; each is single-use once validly signed (only its canonical base64 text is accepted) and expires in 5 minutes. Challenges and sign-in attempts are each rate-limited per client (an IPv4 address, or an IPv6 /64).
- **Sessions:** a `Secure`, httpOnly, `SameSite=Strict` cookie, ended after 30 minutes idle, 12 hours or sign-out, and swept when abandoned; in memory, so a restart signs everyone out. Signing in again ends the previous session, and sign-out always clears the cookie, with or without a body. Every write checks `Origin` and requires JSON.
Expand Down Expand Up @@ -48,7 +48,7 @@ To run it, set `TREASURY_PLATFORM_ORIGIN`, `TREASURY_DEPLOYMENT`, `TREASURY_CRED

The app-access migration's target architecture, contract gaps and acceptance steps are in [ADR 2](docs/adr/0002-owner-ui-app-access.md). Run `pnpm --filter @treasury/web audit:app-access` to compare current owner UI calls with the installed SDK's consent scopes. The owner-session build below remains the simulated-stack reference until that migration is complete.

`web/` is its own package in the pnpm workspace: React 19, TanStack Router and Query, rsbuild, Tailwind 4 and shadcn, on `@multiagency/sdk` 0.5.0 only. Nothing is imported from the platform's or the prototype's source.
`web/` is its own package in the pnpm workspace: React 19, TanStack Router and Query, rsbuild, Tailwind 4 and shadcn, on `@multiagency/sdk` 0.5.1 only. Nothing is imported from the platform's or the prototype's source.

- **One platform module.** `web/src/platform.ts` makes every call: the gateway's session routes, and each agent's reads, actions and streams. Reads are cached and may retry (`reads`). An action or stream returns its result, decided by `kind`, and is never sent again. Screens import only from it.
- **Features port one at a time** from MultiAgency/treasury-prototype's `web/src`, each in its own issue. A page not ported yet shows a placeholder where its feature will land.
Expand All @@ -65,4 +65,4 @@ pnpm --filter @treasury/web test:stack # builds, then drives the owner UI on a

The stack test starts the platform's stack with its browser, whose owner page starts signed in, and attaches to it over CDP. It covers sign-in and out across tabs, the redirects, an outage of the agent's provider (setup unavailable, then recovered), the keyboard paths, and an axe scan (WCAG A and AA) of every page it opens.

CI (`.github/workflows/ci.yml`) runs the app server's typecheck and unit tests and the owner UI's typecheck, unit tests and build on every pull request and push to `main`, then both stack tests on a stack from the platform at 6ba0831. Locally, the stack tests need `../platform` (or `TREASURY_PLATFORM`) at 6ba0831 with its dependencies installed.
CI (`.github/workflows/ci.yml`) runs the app server's typecheck and unit tests and the owner UI's typecheck, unit tests and build on every pull request and push to `main`, then both stack tests on a stack from the platform at 2223d11. Locally, the stack tests need `../platform` (or `TREASURY_PLATFORM`) at 2223d11 with its dependencies installed.
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"test:sandbox": "node --test test/sandbox.e2e.ts"
},
"dependencies": {
"@multiagency/sdk": "0.5.0",
"@multiagency/sdk": "0.5.1",
"near-kit": "0.20.2",
"zod": "^4.6.6"
},
Expand Down
20 changes: 10 additions & 10 deletions docs/adr/0002-owner-ui-app-access.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
# 2. Move owner pages behind the Treasury app server

**Status:** accepted, 2026-11-05. The first steps landed in #118 (owner reads and a read-only portfolio through the app server). SDK 0.5.1 settles platform#17: the table below is the 0.5.0 inventory, and the upgrade rewrites it.
**Status:** accepted, 2026-11-05. The first steps landed in #118 (owner reads and a read-only portfolio through the app server). SDK 0.5.1 answers platform#17; the gaps below are as of 0.5.1.

Replaces the production destination in [ADR 1](0001-owner-session-and-app-server.md). Keep the existing owner-session build as the simulated-stack reference until its replacement passes equivalent workflows.

## Target architecture

The Treasury app server serves owner pages and proposals from one origin. Its existing wallet sign-in, session, per-agent ownership checks and DAO membership checks gate every API route. Only the server calls `/apps/v1`; deployment credentials never reach browser code. The browser uses the existing server session rather than the platform's `/gw/*` session.

Add explicit, schema-checked endpoints per supported workflow, each checking the required member permission and platform consent. Connecting an agent must not silently add consent scopes. The existing proposals connection requests only `read` and `treasury`; wallet owner actions need an explicit consent change for `owner`, and deposit needs `payout`. DAO consent cannot include those scopes in SDK 0.5.0. SDK 0.5.1 puts `deposit` in the `treasury` scope, so funding needs no `payout` consent, for wallet and DAO owners alike.
Add explicit, schema-checked endpoints per supported workflow, each checking the required member permission and platform consent. Connecting an agent must not silently add consent scopes. The existing proposals connection requests only `read` and `treasury`, which covers funding (`deposit`) for wallet and DAO owners alike. Wallet owner actions need an explicit consent change for `owner`, which a DAO's consent can't include.

Wallet NEP-366 actions open the platform's confirm page and follow their status. DAO owner actions use the SDK's DAO preparation/vote flow. Neither uses the owner-session submit flow as a generic replacement. Actions and streams remain single-attempt; follow an interrupted action with observations rather than replaying it.

## Verified contract gaps

Run `pnpm --filter @treasury/web audit:app-access` to inventory current literal `.read`, `.act` and `.stream` call sites against the installed SDK's scope rules. This is a source inventory, not proof that every flow works; dynamic calls and gateway routes require separate review. A unit test tracks the unsupported procedure set so an SDK or UI change triggers a migration review.

At SDK 0.5.0, seven procedures used by the owner UI are absent from every app scope:
At SDK 0.5.1, `reconcileBooks` is under `read`. Three procedures the owner UI uses are absent from every app scope, and a DAO's consent is refused three more:

| Procedure | Required treatment |
| --- | --- |
| `setup` | Replace with identity, instance and overview after the missing agent wallet and executor-held grant ID have a supported observation. Never infer the held grant from the grants list. Grant adoption is also a platform concern. |
| `recover` | Replace the manual trigger in the app-access UI with reads of `unresolved` while the platform reconciles automatically, as decided in platform#14. |
| `reconcileBooks` | Await the platform's read-scope decision; ledger records cannot substitute for independent reconciliation evidence. |
| `treasury/link`, `treasury/unlink`, `treasury/setConfidential`, `treasury/fund` | Await explicit scope and DAO authorization decisions. |
| `setup` | Read `account` (`read`): the agent's wallet, the grant its executor holds and any staged grant, matched by the token, never inferred from the grants list. Owner and signer come from `GET /apps/v1/agents/<agent>`. It adopts nothing: a staged grant takes effect once its owner action settles. |
| `recover` | Remove the manual trigger and follow `unresolved`, which the platform reconciles on its own (platform#14). After a lost reply, read the action's own record; never resend while it's unknown. |
| `treasury/fund` | `deposit` (`treasury`) for the address, then the SDK's `fundingTransaction()`: the owner's wallet sends it, or a DAO member files the same calls as one proposal. |
| `treasury/link`, `treasury/unlink`, `treasury/setConfidential` | Under `treasury` for wallet owners. Refused under a DAO's consent: a DAO agent's link is the DAO, fixed at setup, so its page hides the controls. |

The planned Runs/manual-rebalance/steward work additionally needs decisions for `autopilot/tick` and `steward/propose`, and explicit `treasury-execute` consent for execution without per-swap signatures. Creation uses app `setup` or `daoSetups`, followed by wallet confirmation or a DAO vote, rather than `/gw/agents`.
Run now is `autopilot/tick` under `treasury`; offer it only when the held grant binds the saved strategy, or the tick swaps nothing. Suggested swaps come from Treasury's own agent through `proposals/create`; `steward/propose` stays off app access. Execution without per-swap signatures needs explicit `treasury-execute` consent. Creation uses app `setup` or `daoSetups`, followed by wallet confirmation or a DAO vote, rather than `/gw/agents`.

Sources checked on 2026-11-04: [platform#14 decision](https://github.com/MultiAgency/platform/issues/14), [platform#17 open contract decisions](https://github.com/MultiAgency/platform/issues/17), and the installed SDK's exported scope definitions. The SDK is the executable contract.
Sources checked on 2026-11-05: [platform#14 decision](https://github.com/MultiAgency/platform/issues/14), [platform#17](https://github.com/MultiAgency/platform/issues/17) as answered by SDK 0.5.1 (platform `2223d11`), and the installed SDK's exported scope definitions. The SDK is the executable contract.

## Implementation order and acceptance

1. Resolve frame observations and scope decisions in the platform; publish the SDK and update this repository's SDK/stack pin together.
1. Platform decisions, the SDK and this repository's SDK/stack pin: done with 0.5.1. The hosts answer these procedures only once the platform deploys that code.
2. Add explicit owner observation endpoints. Test ownership transfer, DAO roles, cross-organization isolation, missing consent and malformed upstream responses. Consolidate page reads to respect the deployment's shared 1,200-call hourly allowance.
3. Move browser discovery and owner reads to the server session. Preserve retained-record recovery and clear private caches when account or access changes.
4. Port owner actions, strategy and funding with consent review and wallet-confirm/DAO-vote outcomes. Required workflows must be supported before declaring the migration complete.
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ packages:
- web
- apps/*
- packages/*
# Published 2026-11-04T19:20:52Z, inside pnpm's minimum release age (24 hours here).
# Remove after 2026-11-05T19:21Z, once 0.5.0 is past it.
# Published 2026-11-05T02:44:20Z, inside pnpm's minimum release age (24 hours here).
# Remove after 2026-11-06T02:45Z, once 0.5.1 is past it.
minimumReleaseAgeExclude:
- '@multiagency/sdk@0.5.0'
- '@multiagency/sdk@0.5.1'
permissions:
'@multiagency/sdk':
skills: true
6 changes: 3 additions & 3 deletions scripts/stack.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// The platform's simulated stack (`pnpm test:e2e:stack --no-browser --treasury` at 6ba0831), through its state file and
// The platform's simulated stack (`pnpm test:e2e:stack --no-browser --treasury` at 2223d11), through its state file and
// control files only: never through its internals.
import { execFileSync, spawn } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { request } from "node:https";
import { join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";

/** The platform commit the stack runs from: the one `@multiagency/sdk` 0.5.0 was published from (tag `sdk-v0.5.0`). */
export const PLATFORM_COMMIT = "6ba0831";
/** The platform commit the stack runs from: the one `@multiagency/sdk` 0.5.1 was published from (tag `sdk-v0.5.1`). */
export const PLATFORM_COMMIT = "2223d11";

export const USDC = "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1";
export const WNEAR = "nep141:wrap.near";
Expand Down
Loading
Loading