diff --git a/CLAUDE.md b/CLAUDE.md index e577e234b..95a55b526 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,7 +89,7 @@ pnpm workspaces (no nx/turbo/lerna). All packages under `packages/`: |---------|---------| | `cluster-tool` (`@wireio/cluster-tool`) | THE core library: orchestration engine (PhaseGroup → Phase → Step → Report), process managers, chain clients, config/bind resolution, Steps palette, flow substrate (`FlowCLI`/`FlowScenario`), CLI | | `cluster-tool-shared` (`@wireio/cluster-tool-shared`) | Zod schema-first persisted shapes (`ClusterConfig`, `BindConfig`, `ClusterState`, `SignatureProviderConfig`, `ExternalOutpostConfig`, `ExternalClusterConfig`, `ChainTokenAmount`, `QueryEngineConfig`) behind the generic `SchemaCodec` (validate-both-ends serialize/deserialize) | -| `flow-*` (21 packages; 13 enabled, 8 disabled at launch) | One scenario each — standalone executables built on `FlowCLI.create(Scenario).run()`; batch-operator lifecycle (slashing/termination), collateral, reserves, emissions soak, node-owner NFT, yield distribution, liq syndication, liq yield, and the six swap variants | +| `flow-*` (13 enabled packages) | One scenario each — standalone executables built on `FlowCLI.create(Scenario).run()`; batch-operator lifecycle, depot collateral, emissions, node-owner NFT, yield, LIQ syndication and underwriting, and emergency stop | | `debugging-shared` / `debugging-server` / `debugging-client-shared` / `debugging-client-tool` / `debugging-client-tool-tui` | OPP debugging surface: shared types + storage paths, ingest server, RPC client, CLI, TUI | | `test-app-server` | Fixture app server used by debugging tests | @@ -300,13 +300,7 @@ secret-id pattern), plus `--external-outpost-config ` (bootstrap the depot against already-deployed REMOTE ETH+SOL outposts — no local anvil/validator), `--bind-config ` (a complete `BindConfig` used verbatim, or a partial override merged over the resolved defaults; a remote anvil/solana address -requires `--external-outpost-config`), `--enable-mock-reserves` (default off — -seed the 8 mock (chain, token) PRIMARY reserves at bootstrap; a real / external -depot leaves these unseeded), `--enable-launch-withheld-operations` (default -off — run the Solana outpost bootstrap calls the launch build of the program -withholds with `OperationDisabled`: `init_reserve`, `create_reserve_native` and the -mock SPL reserve provisioning that writes `sol-mock-mints.json`; a default cluster -skips them), `--enable-mock-liq-pools` (default off — seed the +requires `--external-outpost-config`), `--enable-mock-liq-pools` (default off — seed the 2 mock shadow-liq yield pools on `sysio.swap` and back the shadow in outpost custody), `--enable-mock-syndication-import` (default off — import the mock bonder's LIQSOL/LIQETH positions during epoch zero and back all mock shadow), `--api-count ` (default 0 — plan N API nodes: @@ -357,14 +351,8 @@ inline signing key (the GHA workflow is SSM-only, so published archives do not). the SAME `applyClusterBuildOptionsArgs` surface every flow uses (env vars `WIRE_*` seed the path flags). Exit code mirrors the bootstrap Report. -**Flow authoring — mock reserves, liq pools and syndication import.** A flow that reads the mock -(chain, token) PRIMARY reserves sets `enableMockReserves: true` in its -`Scenario.defaults` (the same mechanism it uses for `operatorsPerEpoch` / -collateral) — never in `plan()`. The bootstrap seeds them during epoch 0; a -flow's `plan()` phases always run AFTER `EpochBootstrap` advances epoch 0→1, and -the depot gates `regreserve` to epoch 0, so `regreserve` can never be called from -a flow phase. The mock shadow-liq yield pools (`enableMockLiqPools: true`, -`sysio.liq::regliqpool`) follow the same rule for the same reason. The shadow +**Flow authoring — mock LIQ pools and syndication import.** The mock shadow-liq yield pools (`enableMockLiqPools: true`, +`sysio.liq::regliqpool`) must be seeded during epoch zero through scenario defaults, before flow phases run. The shadow symbols themselves (`sysio.liq::create`, one per registered liq token), the swap's `setconfig` and the kicker are registry setup a real depot performs too, so the bootstrap does those unconditionally. A flow needing the first bonder sets @@ -401,37 +389,9 @@ are `SolanaLiqSyndicationTool` (`planSetFrozen`, `planDonateToPool`, PDA) and `EthereumSyndicationTool` (`planSyndicate`, `planDonateToPool`, `planSetPaused`, `planPayPendingDesyndication`). -**Flow authoring — launch-withheld operations.** `enableLaunchWithheldOperations: -true` makes the bootstrap RUN the Solana outpost calls the launch build of the -program withholds (`init_reserve`, `create_reserve_native`, the mock SPL reserve -provisioning), so a flow that sets it requires a Solana program build without the -launch restrictions; against the launch build those calls return -`OperationDisabled` (6086). A flow sets it in its `Scenario.defaults` — never in -`plan()` — when its scenario needs the bootstrap-seeded Solana reserves or the mock -SPL mints. The flag does not decide which flows the launch program can run: the -eight that set it are **disabled at launch** (`flow-reserve-lifecycle`, `flow-swap-from-wire`, -`flow-swap-non-native-tokens`, `flow-swap-private-reserves`, `flow-swap-to-wire`, -`flow-swap-variance-revert`, `flow-swap-with-underwriting`, -`flow-underwriter-slashing`) need Solana operations the launch build withholds, and -`flow-yield-distribution` injects attestations through the withheld -`add_attestation` instruction — it does not set the flag, which governs only -bootstrap calls. The collateral, producer-registration and batch-operator-termination -flows bond on the depot, do not set the flag, and run against the launch program. - -Each of the eight disabled flows opts into `enableLaunchWithheldOperations: true` -and exercises -reserves/swaps, which the launch Solana program refuses: wire-solana `89565920` -("Disable unaudited reserve and swap operations") returns `OperationDisabled` -6086 at `InitReserve` during bootstrap. There is no launch replacement for these -operations; they return when reserves/swaps are enabled after audit. - -Their `package.json` live-flow script is named `test:disabled` instead of `test`. -Both local `scripts/run-flow.mjs` discovery (including exact names, short names, -regex and the picker) and CI's `run-flows.mjs` require a `test` script, so these -packages are skipped. To re-enable a flow after audit, rename `test:disabled` -back to `test` in its `package.json` (one line per flow). All packages remain in -`pnpm build`; root `pnpm test` still runs Jest and every `test:unit` script. -These eight packages currently have no unit tests. +**Removed functionality.** The eight reserve/swap flow packages and their bootstrap +flags have been deleted. Only batch operators run OPP daemons. Syndication bond +providers act through `sysio.bond`; they do not run swap-underwriter daemons. **Flow authoring — API nodes.** A flow that needs an API node sets `apiCount` (and, if needed, `queryEngine`) in its `Scenario.defaults`; the nodes start in diff --git a/README.md b/README.md index 62f3e04db..0380b35d3 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ that OPP envelopes circulate and that on-chain state stays consistent end to end The OPP message flow spans three chains: - **WIRE depot** (`nodeop` + `kiod`) — system contracts `sysio.epoch`, `sysio.msgch`, - `sysio.opreg`, `sysio.uwrit`, `sysio.reserv`, `sysio.chalg`, … -- **Ethereum outpost** (`anvil`) — `OPP.sol`, `OPPInbound.sol`, `OperatorRegistry.sol`, - `ReserveManager.sol`, `StakingManager.sol` (+ `liqEth`). + `sysio.opreg`, `sysio.chalg`, `sysio.synd`, `sysio.bond`, `sysio.liq`, `sysio.swap` + (a depot-local AMM with no outpost participation), `sysio.andon`, `sysio.dclaim`, … +- **Ethereum outpost** (`anvil`) — `OPP.sol`, `OPPInbound.sol`, `OutpostManager.sol`, + `SyndicationPool.sol`, `BAR.sol`, `StakingManager.sol` (an inert placeholder) (+ `liqEth`). - **Solana outpost** (`solana-test-validator`) — all four wire-solana Anchor programs loaded at genesis (`liqsol_core`, which hosts the OPP outpost interface, plus `liqsol_token` / `transfer_hook` / `validator_leaderboard`), with the liqsol @@ -131,19 +132,11 @@ pnpm workspace (no nx/turbo/lerna); everything lives under `packages/`. |---|---|---| | `cluster-tool` | `@wireio/cluster-tool` | Core harness: process managers, chain clients, bootstrap, **`wire-cluster-tool` CLI** | | `flow-operator-collateral-deposit` | `@wireio/test-flow-operator-collateral-deposit` | Node-operator collateral deposit + withdraw remit | -| `flow-swap-with-underwriting` | `@wireio/test-flow-swap-with-underwriting` | **Disabled at launch (reserves/swaps withheld)** — Bidirectional SWAP (ETH ↔ SOL) with underwriting | -| `flow-swap-non-native-tokens` | `@wireio/test-flow-swap-non-native-tokens` | **Disabled at launch (reserves/swaps withheld)** — SWAP of non-native tokens (USDC / USDT / LIQ) | -| `flow-swap-variance-revert` | `@wireio/test-flow-swap-variance-revert` | **Disabled at launch (reserves/swaps withheld)** — Swap variance-tolerance revert | | `flow-batch-operator-termination` | `@wireio/test-flow-batch-operator-termination` | Batch-operator termination via delivery underperformance | | `flow-yield-distribution` | `@wireio/test-flow-yield-distribution` | `STAKING_REWARD` (Ethereum) → `sysio.dclaim::onreward` → `fundclaim` | | `flow-liq-syndication` | `@wireio/test-flow-liq-syndication` | Real syndication reaches the destination wallet; reported yield is fully released | | `flow-liq-yield` | `@wireio/test-flow-liq-yield` | Syndicated liqSOL parked → linked → credited; reported yield minted, sold through `sysio.swap`, claimed as WIRE; `DESYNDICATE_LIQ` paid on the outpost | | `flow-emissions-soak` | `@wireio/test-flow-emissions-soak` | Multi-hour emissions + `sysio.dclaim` payout soak | -| `flow-reserve-lifecycle` | `@wireio/test-flow-reserve-lifecycle` | **Disabled at launch (reserves/swaps withheld)** | -| `flow-swap-from-wire` | `@wireio/test-flow-swap-from-wire` | **Disabled at launch (reserves/swaps withheld)** | -| `flow-swap-to-wire` | `@wireio/test-flow-swap-to-wire` | **Disabled at launch (reserves/swaps withheld)** | -| `flow-swap-private-reserves` | `@wireio/test-flow-swap-private-reserves` | **Disabled at launch (reserves/swaps withheld)** | -| `flow-underwriter-slashing` | `@wireio/test-flow-underwriter-slashing` | **Disabled at launch (reserves/swaps withheld)** | | `debugging-*` / `test-app-server` | `@wireio/debugging-*` | OPP debugging server, client tooling, TUI, shared types | Flow packages depend on the harness via `workspace:*`. @@ -169,7 +162,7 @@ The other seven enabled flows (`flow-batch-operator-slashing`, `flow-operator-collateral-deposit`, `flow-producer-registration`, `flow-yield-distribution`) submit no user LIQ syndication/desyndication. Imported bootstrap collateral and reward funding have their own assertions. The eight -disabled swap/reserve flows remain disabled and have no direct LIQ syndication call. +former disabled swap/reserve flows have been removed. `WireSyndicationTool.planResolveEnvelope` is an explicit `sysio.bond::rslvvalid` shortcut for an unbonded OPEN request. It refuses provider-funded, challenged or @@ -197,22 +190,11 @@ payout after the existing queued-redemption assertions. TypeScript compilation, stopped at the bootstrap toolchain check because the shell selected Solana 4.0.3; the successful runs above selected the installed pinned 4.2.0 toolchain explicitly. -### Disabled reserve and swap flows +### Removed reserve and swap flows -Each of the eight disabled flows opts into `enableLaunchWithheldOperations: true` -and exercises -reserves/swaps, which the launch Solana program refuses: wire-solana `89565920` -("Disable unaudited reserve and swap operations") returns `OperationDisabled` -6086 at `InitReserve` during bootstrap. There is no launch replacement for these -operations; they return when reserves/swaps are enabled after audit. - -Their `package.json` live-flow script is named `test:disabled` instead of `test`. -Both local `scripts/run-flow.mjs` discovery (including exact names, short names, -regex and the picker) and CI's `run-flows.mjs` require a `test` script, so these -packages are skipped. To re-enable a flow after audit, rename `test:disabled` -back to `test` in its `package.json` (one line per flow). All packages remain in -`pnpm build`; root `pnpm test` still runs Jest and every `test:unit` script. -These eight packages currently have no unit tests. +The eight former reserve/swap flow packages, their dedicated helpers and bootstrap +flags have been deleted. The 13 remaining flows exercise supported functionality. +Syndication underwriting uses depot bond providers, without swap-underwriter daemons. ## Running flows @@ -418,7 +400,7 @@ wire-cluster-tool create \ --producer-count=5 \ --node-count=1 \ --batch-operator-count=3 \ - --underwriter-count=1 \ + --underwriter-count=0 \ --epoch-duration-sec=60 \ --ethereum-path=/data/shared/code/wire-platform/wire-ethereum \ --solana-path=/data/shared/code/wire-platform/wire-solana \ @@ -486,7 +468,7 @@ command comes first). | `--node-count` | `-n` | `1` | producer node **processes** to launch | | `--producer-count` | `-p` | `1` | producer **accounts** to register on-chain | | `--batch-operator-count` | `-b` | `3` | batch operators | -| `--underwriter-count` | `-u` | `1` | underwriters | +| `--underwriter-count` | `-u` | `0` | compatibility option; nonzero values are rejected | | `--epoch-duration-sec` | | `60` | minimum epoch duration in seconds (the depot floor — `sysio.epoch::setconfig` rejects lower) | | `--warmup-epochs` | | `1` | epochs before an operator goes `WARMUP` → `ACTIVE` | | `--cooldown-epochs` | | `1` | epochs before an operator can deregister after `COOLDOWN` | @@ -495,8 +477,6 @@ command comes first). | `--terminate-max-percent-misses24h` | | — | 24h missed-delivery percentage termination threshold | | `--terminate-window-ms` | | — | termination evaluation window in ms | | `--bind-all` | | `false` | bind every daemon to `0.0.0.0` instead of loopback | -| `--enable-mock-reserves` | | `false` | seed the 8 mock (chain, token) PRIMARY reserves at bootstrap | -| `--enable-launch-withheld-operations` | | `false` | run the Solana outpost bootstrap calls the launch build withholds (`init_reserve`, `create_reserve_native`, mock SPL reserves); a flow that sets it (in its scenario defaults, when it needs the bootstrap-seeded Solana reserves or the mock SPL mints) requires a Solana program build without the launch restrictions, which answer these calls with `OperationDisabled` (6086). The flag does not decide which flows the launch program can run: the eight opted-in flows need withheld Solana operations, and `flow-yield-distribution` needs the withheld `add_attestation` instruction without opting in (see CLAUDE.md, "Flow authoring — launch-withheld operations") | | `--enable-mock-liq-pools` | | `false` | seed the mock LIQETH/LIQSOL yield pools during epoch zero and fund outpost custody to back all outstanding shadow | | `--enable-mock-syndication-import` | | `false` | import the mock bonder's LIQSOL and LIQETH positions during epoch zero, seal import, and back all mock shadow in outpost custody | | `--api-count` | | `0` | API nodes — non-producing nodeops meshed with bios + producers, serving `/v1/chain/*` and the query engine's `POST /v1/query/execute`; never `producer_api_plugin` | @@ -575,11 +555,8 @@ config; the Solana program id is parsed from the IDL): } ``` -External mode ALSO requires an EXPLICIT `--underwriter-count 0`. The flag -defaults to `1`, so omitting it asks for one underwriter — and an external -cluster has no local outpost for an underwriter to bond collateral on, so -`create` fails fast naming whichever cause applies ("you asked for N" vs "you -omitted it and got the default"). +Swap-underwriter daemon counts default to zero in all deployment modes. Nonzero +values are rejected; syndication bond providers do not need a daemon. At `create` the harness verifies the external endpoints are reachable (`eth_chainId` matches the configured `chainId`; Solana `getVersion` responds) diff --git a/STYLE.md b/STYLE.md index 22843e8cb..45a5b531c 100644 --- a/STYLE.md +++ b/STYLE.md @@ -529,8 +529,8 @@ const { Notes: async defaults are fine (`const { port = await BindConfigProvider.findAvailable(…) } = options`); a fallback to ANOTHER member destructures both then coalesces the locals -(`const { batchOperatorAccount, underwriterAccount } = node` → -`batchOperatorAccount ?? underwriterAccount`); optional chains and computed +(`const { primary, fallback } = options` → +`primary ?? fallback`); optional chains and computed members (`arr[0] ?? x`) stay accessor-form. ### Module-level joined declarations @@ -966,8 +966,8 @@ define protocol latency): |---|---|---| | `EpochExtensionMaxSec` (30s) | via `effectiveEpochSec()` | every N-epoch deadline: `N × (epochDurationSec + 30) × MsPerSecond` | | `CollateralVerifyBudgetMs` (6 min) | deposit → depot verification gates | -| `SingleHopBudgetMs` (7 min) | act on an outpost, verify on the depot — or the reverse (uwreq appears, race confirms, reserve relay/READY, attestation propagation) | -| `DoubleHopBudgetMs` (14 min) | outpost → depot → outpost tails (remit/payout, SWAP_REVERT round-trips) | +| `SingleHopBudgetMs` (7 min) | act on an outpost, verify on the depot — or the reverse (a `SYNDICATE_LIQ` envelope reaches `REQUESTED`, a `DESYNDICATE_LIQ` payout reaches the outpost wallet, attestation propagation) | +| `DoubleHopBudgetMs` (14 min) | outpost → depot → outpost tails (an outpost syndication bonded and released on the depot, then redeemed back to the outpost wallet via `DESYNDICATE_LIQ`) | Principles: diff --git a/docs/local-setup.md b/docs/local-setup.md index 6ecafcbd9..9c45a69eb 100644 --- a/docs/local-setup.md +++ b/docs/local-setup.md @@ -1,12 +1,15 @@ # wire-cluster-tool: Local Setup (Non-Docker) -End-to-end instructions for building Wire's `wire-cluster-tool` CLI from source on the host (Ubuntu 24.04 / WSL2), **without** the Docker image. Based on the five repos' own README / CLAUDE.md files; the reference Dockerfile is out of date in a few places (see "Differences from the Dockerfile" at the bottom). +End-to-end instructions for building Wire's `wire-cluster-tool` CLI (alias `wtc`) and running the `flow-*` +suites from source on the host (Ubuntu 24.04 / WSL2), **without** a container image. Each repo's own +README / CLAUDE.md stays authoritative for its build; this page strings them together in dependency order. +The container route is described in `wire-devcontainer` (see §5). -> **Status:** built and smoke-tested on Ubuntu 24.04 WSL2, 2026-04-24. -> End-to-end `create` + `run` both succeed — cluster spins up 1 bios + 5 -> producer + 3 batchop + 1 uwrit `nodeop` processes, `anvil` with OPP -> contracts, and `solana-test-validator` with the `opp_outpost` BPF -> program preloaded. +**What `create` brings up:** one bios `nodeop`, the producer nodes, one `nodeop` per batch operator (3 by +default, each running the OPP relay daemon), optional API nodes, `kiod`, `anvil` with the Ethereum outpost +contracts deployed, and `solana-test-validator` with all four wire-solana programs loaded at genesis +(`liqsol_core`, which hosts the Solana OPP outpost, plus `liqsol_token`, `transfer_hook` and +`validator_leaderboard`). --- @@ -24,18 +27,23 @@ sudo apt-get install -y \ doxygen fish golang \ libbz2-dev libcurl4-openssl-dev libgmp-dev liblzma-dev \ libncurses5-dev libusb-1.0-0-dev libzstd-dev zlib1g-dev \ + openjdk-21-jre-headless \ python3 python3-pip python3-venv python3-dev \ sudo tar unzip vim zip ``` +`openjdk-21-jre-headless` runs the ANTLR parser generator of wire-sysio's query engine plugin at build time. + ### 1.2 Toolchains (one-time) -Install only what's missing. Versions below match CI: +Install only what's missing. The Solana / Anchor / Rust versions are pinned by `wire-solana` +(`Anchor.toml` → `anchor_version = "0.31.0"`, `solana_version = "4.2.0"`; `rust-toolchain.toml` → `1.86.0`) — +a mismatched validator or Anchor CLI produces program-load failures that look like flow bugs. ```bash -# -- Rust (stable) -- -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable -export PATH="$HOME/.cargo/bin:$PATH" +# -- Rust -- +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +. "$HOME/.cargo/env" # -- Foundry (anvil) -- curl -L https://foundry.paradigm.xyz | bash @@ -46,17 +54,21 @@ export PATH="$HOME/.foundry/bin:$PATH" sh -c "$(curl -sSfL https://release.anza.xyz/v4.2.0/install)" export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" -# -- Node.js 24+ -- -# wire-libraries-ts and wire-e2e-tests roots declare engines.node >=22, -# but the bundled CLI packages (protoc-gen-solidity, protoc-gen-solana, -# protobuf-bundler) declare engines.node >=24 and `@yao-pkg/pkg` fetches -# a v24 runtime to bundle into their binaries. Use 24. CI also runs 24. -# (Verified: Node 20.x does NOT satisfy the engine check.) +# -- Anchor via avm -- +cargo install --git https://github.com/coral-xyz/anchor avm --force +avm install 0.31.0 +avm use 0.31.0 + +# -- Node.js >= 24.9 -- +# wire-tools-ts declares engines.node >=24.9. Below it, require(ESM) on yargs fails and +# every jest suite that reaches the @wireio/cluster-tool barrel dies at import. +# wire-sysio's OPP bundle generation also requires node >= 24. nvm install 24 && nvm alias default 24 # -- pnpm (corepack) -- corepack enable corepack prepare pnpm@10.32.1 --activate +pnpm setup # creates the pnpm global bin dir; start a new shell afterwards (see §6.1) ``` Verify: @@ -64,9 +76,10 @@ Verify: ```bash rustc --version; cargo --version anvil --version -solana --version -node --version # must be >= 24 -pnpm --version # must be 10.x +solana --version # 4.2.0 ... Agave +anchor --version # anchor-cli 0.31.0 +node --version # must be >= 24.9 +pnpm --version # 10.x clang-18 --version cmake --version ninja --version @@ -74,48 +87,32 @@ ninja --version ### 1.3 Repo layout -All five repos must sit in the **same parent directory** so sibling-based -package resolution (`.pnpmfile.cjs`, `generate-opp-bundles.fish`) works: +The repos must sit in the **same parent directory** — `wire-tools-ts/.pnpmfile.cjs` resolves `@wireio/*` +from its siblings (`../wire-libraries-ts/packages/*` and `../wire-sysio/build/opp/typescript`): ``` / ├── wire-cdt/ -├── wire-sysio/ +├── wire-sysio/ # build// holds the binaries; build/opp/ holds the generated OPP bundles ├── wire-libraries-ts/ ├── wire-ethereum/ ├── wire-solana/ -├── wire-e2e-tests/ -├── wire-opp/ # GENERATED by generate-opp-bundles.fish, not cloned -└── wire-install/ # GENERATED install prefix for wire-cdt + wire-sysio +├── wire-tools-ts/ +└── wire-install/ # install prefix for wire-cdt (consumed by wire-sysio) ``` -Clone each (`git clone --recursive`) if not already present. +The platform checks these out together with Google's `repo` tool from `wire-platform-manifest`; cloning each +repo by hand (`git clone --recursive`) into one parent directory works too. -The `wire-opp/` directory **must exist** before the wire-sysio build runs — `generate-opp-bundles.fish` only writes there when the directory is already present (it's the local-dev hand-off path detected by `.pnpmfile.cjs` in `wire-ethereum`/`wire-e2e-tests`). It can start empty: +### 1.4 Branches -```bash -mkdir -p "$WIRE_ROOT/wire-opp" -``` +Use the branches the manifest declares (`wire-platform-manifest/default.xml`): the default revision +`master` for `wire-cdt`, `wire-sysio`, `wire-libraries-ts` and `wire-tools-ts`, and `next` for +`wire-ethereum` and `wire-solana`. -### 1.4 Branches required for the OPP cross-chain integration - -`master` isn't enough — the `wire-cluster-tool` harness expects artifacts -(contracts, scripts, Solana program, protos) that only exist on the OPP -feature branches: - -| Repo | Branch | What's on it | -|---|---|---| -| `wire-sysio` | any branch that carries the OPP protos — `feature/protobuf-support-for-opp` or a branch merged from it (e.g. `feature/intrinsic-span-cleanup`) | `libraries/opp/proto/sysio/opp/**/*.proto`, `sysio.{chalg,epoch,msgch,uwrit,opreg}` contracts, `cdt::protoc-gen-zpp` wiring | -| `wire-cdt` | `feature/kv-secondary-primary-id` (current) | `protoc-gen-zpp` tool + install target | -| `wire-libraries-ts` | `master` | `@wireio/shared*`, `@wireio/sdk-core`, `protoc-gen-*` + `wire-protobuf-bundler` CLIs | -| `wire-ethereum` | `feature/protobufs-for-opp` | `src/scripts/deployLocal.ts` (called by harness ETHBootstrapper), `contracts/outpost/*.sol` consuming `@wireio/opp-solidity-models` | -| `wire-solana` | `feature/opp-solana-outpost-integration` | `programs/opp-outpost` Anchor program + `wallets/opp-outpost-keypair.json` | -| `wire-e2e-tests` | `master` | the harness + flow tests | - -Switch each repo before building: ```bash -(cd wire-ethereum && git fetch && git checkout feature/protobufs-for-opp) -(cd wire-solana && git fetch && git checkout feature/opp-solana-outpost-integration) +(cd wire-ethereum && git fetch && git checkout next) +(cd wire-solana && git fetch && git checkout next) ``` --- @@ -136,64 +133,42 @@ export NUM_JOBS=$(( $(nproc) - 2 )) ## 3. Build steps -> Each step assumes `cd "$WIRE_ROOT/"` unless stated otherwise. - -### 3.1 `wire-libraries-ts` — shared JS/TS packages + protoc plugins - -This produces the `@wireio/*` packages and the three protoc CLI binaries -(`wire-protobuf-bundler`, `protoc-gen-solidity`, `protoc-gen-solana`) that -`generate-opp-bundles.fish` needs on PATH. - -```bash -cd "$WIRE_ROOT/wire-libraries-ts" -pnpm install # postinstall runs pnpm dist for protoc-gen-* (slow first time) -pnpm build - -# Put the three CLI binaries on PATH for the OPP generation step: -for p in protoc-gen-solidity protoc-gen-solana protobuf-bundler; do - (cd "packages/$p" && pnpm run dist && pnpm link --global) -done - -# Verify: -command -v wire-protobuf-bundler protoc-gen-solidity protoc-gen-solana -``` - -**Note on `pnpm link --global`:** Only the three CLI packages need this, and -only so the `.fish` script finds the binaries on PATH. `@wireio/shared`, -`@wireio/shared-node`, `@wireio/sdk-core`, etc. are resolved automatically by -`wire-e2e-tests/.pnpmfile.cjs` — **do not** manually link them. +> Each step assumes `cd "$WIRE_ROOT/"` unless stated otherwise. Build order: +> wire-cdt → wire-sysio → wire-libraries-ts → wire-ethereum → wire-solana → wire-tools-ts. -### 3.2 `wire-cdt` — Contract Development Toolkit (WASM/ABI compilers) +### 3.1 `wire-cdt` — Contract Development Toolkit (WASM/ABI compilers) -Required by `wire-sysio` so `BUILD_SYSTEM_CONTRACTS=ON` can build the `.wasm` -contracts. Installs into a local prefix we point `wire-sysio` at. +Required by `wire-sysio` so `BUILD_SYSTEM_CONTRACTS=ON` can build the `.wasm` contracts. Installs into a +local prefix that `wire-sysio` is pointed at. ```bash cd "$WIRE_ROOT/wire-cdt" ./vcpkg/bootstrap-vcpkg.sh # one-time -cmake -S . -B build/debug -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ +cmake -S . -B build/release -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ -DENABLE_CCACHE=ON \ - -DENABLE_TESTS=ON \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" \ -DCMAKE_TOOLCHAIN_FILE="$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake" \ -DCMAKE_INSTALL_PREFIX="$WIRE_ROOT/wire-install" \ -DCMAKE_PREFIX_PATH="$WIRE_ROOT/wire-install" -cmake --build build/debug -j"$NUM_JOBS" -cmake --install build/debug +cmake --build build/release -j"$NUM_JOBS" +cmake --install build/release ``` -### 3.3 `wire-sysio` — node software (`nodeop`, `kiod`, `clio`, `sys-util`) +Rebuild wire-cdt whenever you update it — wire-sysio's system contracts compile against its headers, and a +stale CDT shows up as contract compile errors in wire-sysio. + +### 3.2 `wire-sysio` — node software, system contracts and the OPP bundles -> **Constraint from `CLAUDE.md`:** the build directory MUST be under -> `wire-sysio/build/` (e.g. `build/claude`, `build/debug-clang-clion`). -> Other locations break `configure_file` for test reference data. +> **Constraint from `wire-sysio/CLAUDE.md`:** the build directory MUST be under `wire-sysio/build/` +> (e.g. `build/debug`, `build/release`). Other locations break `configure_file` for test reference data. ```bash cd "$WIRE_ROOT/wire-sysio" +git submodule update --init --recursive vcpkg libraries/appbase ./vcpkg/bootstrap-vcpkg.sh # one-time export BUILD_DIR="$PWD/build/debug" @@ -206,94 +181,81 @@ cmake -S . -B "$BUILD_DIR" -G Ninja \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" \ -DCMAKE_TOOLCHAIN_FILE="$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake" \ - -DCMAKE_INSTALL_PREFIX="$WIRE_ROOT/wire-install" \ - -DCMAKE_PREFIX_PATH="$WIRE_ROOT/wire-install/cdt" + -DCMAKE_PREFIX_PATH="$WIRE_ROOT/wire-install" -# Make sure the protoc CLIs are on PATH — there's a POST_BUILD step on -# libopp.a that invokes generate-opp-bundles.fish (see §6.5 below). -export PATH="$HOME/.local/share/pnpm:$PATH" cmake --build "$BUILD_DIR" -j"$NUM_JOBS" # Some compilation units need ~4GB RAM; reduce -j if you see OOMs. ``` -> **Note on the zpp-generated contract headers.** The `contracts_project` -> ExternalProject depends on `opp_cdt_models.protos` (which runs -> `cdt::protoc-gen-zpp` to produce `sysio/opp/**/*.pb.hpp` under -> `build//libraries/opp/generated-cdt/`). wire-sysio commit -> `1015c98d3` (Apr 2026) wired `add_dependencies(contracts_project -> opp_cdt_models.protos)` so a single `cmake --build` should now produce -> the headers before the contracts compile. If you still hit -> `fatal error: 'sysio/opp/types/types.pb.hpp' file not found` on a cold -> build, run `ninja -C "$BUILD_DIR" opp_cdt_models.protos` once and -> resume the full build. +This produces `$BUILD_DIR/bin/{nodeop,kiod,clio,sys-util}` and the system-contract `.wasm`/`.abi`. + +**OPP bundles.** `BUILD_OPP_BUNDLES` is `ON` by default: after the protobuf generation, a POST_BUILD step +runs `libraries/opp/tools/scripts/generate-opp-bundles.sh`, which builds the repo-local protoc plugins +(`protoc-gen-solidity`, `protoc-gen-solana`, `wire-protobuf-bundler` under `libraries/opp/tools/`) and writes +the TypeScript, Solidity and Solana model packages to `wire-sysio/build/opp/{typescript,solidity,solana}/`. +It requires `bash`, `npm`, `node >= 24` and `pnpm >= 10` at configure time; when any is missing CMake prints +`BUILD_OPP_BUNDLES is ON, but requirements are not met` and the bundles are not generated (see §6.2). Never +run `protoc` by hand — this build step is the only producer of those packages. -### 3.4 Generate the `wire-opp/` sibling directory +### 3.3 `wire-libraries-ts` — shared JS/TS packages -`wire-opp` is **not** a repo — it's generated from the proto definitions in -`wire-sysio/libraries/opp/proto/` via a fish script, and its output is -consumed by `wire-ethereum` and `wire-e2e-tests` through their -`.pnpmfile.cjs` hooks. +Produces `@wireio/shared`, `@wireio/shared-node`, `@wireio/sdk-core`, … which `wire-tools-ts` links from +source via its `.pnpmfile.cjs` (no `pnpm link` needed). ```bash -cd "$WIRE_ROOT/wire-sysio/libraries/opp/tools" -./generate-opp-bundles.fish -# Creates $WIRE_ROOT/wire-opp/{typescript,solidity,solana}/ +cd "$WIRE_ROOT/wire-libraries-ts" +pnpm install +pnpm build ``` -Prerequisites: the three CLIs from step 3.1 must be on PATH. The script checks and aborts with a clear error otherwise. - -### 3.5 `wire-ethereum` — Solidity contracts (Hardhat) +### 3.4 `wire-ethereum` — Ethereum outpost contracts (Hardhat) -wire-ethereum uses **npm**, not pnpm — the `.pnpmfile.cjs` auto-linking does -not apply here. Link the generated `@wireio/opp-*` packages globally into -npm's global bin first, then link them into the project: +wire-ethereum uses **npm** (it ships a `package-lock.json`), so `.pnpmfile.cjs` auto-linking does not apply. +To build against the freshly generated Solidity models instead of the published +`@wireio/opp-solidity-models`, link the wire-sysio bundle first: ```bash -# One-time: register the wire-opp/* packages as globally-linkable. -# Both npm and pnpm globals are needed — npm for wire-ethereum below, -# pnpm so any pnpm-based consumer (or sibling wire-e2e-tests workspace -# falling back to a global resolve) finds them. -(cd "$WIRE_ROOT/wire-opp/solidity" && npm link && pnpm link --global) -(cd "$WIRE_ROOT/wire-opp/typescript" && npm link && pnpm link --global) +(cd "$WIRE_ROOT/wire-sysio/build/opp/solidity" && npm link) cd "$WIRE_ROOT/wire-ethereum" -git checkout feature/protobufs-for-opp # see §1.4 npm install -npm link @wireio/opp-solidity-models @wireio/opp-typescript-models -npx hardhat compile +npm link @wireio/opp-solidity-models +npm run build # npx hardhat compile ``` -### 3.5b Anchor — build `opp-outpost` in `wire-solana` +### 3.5 `wire-solana` — the four Anchor programs -The harness loads this Solana program onto `solana-test-validator` via -`--bpf-program`, and reads its IDL for CPI. Requires `anchor-cli 0.31.0`. +The harness loads `liqsol_core`, `liqsol_token`, `transfer_hook` and `validator_leaderboard` onto +`solana-test-validator` at genesis and runs wire-solana's own `anchor run init-*` scripts during the +bootstrap. The Solana OPP outpost lives inside `liqsol_core` (`programs/liqsol-core/src/instructions/opp/`); +there is no separate outpost program. ```bash -# One-time: install anchor-cli (solana-cli already installed in §1.2) -cargo install --git https://github.com/coral-xyz/anchor --tag v0.31.0 anchor-cli --locked -# (or via avm: cargo install --git https://github.com/coral-xyz/anchor avm --locked && avm install 0.31.0 && avm use 0.31.0) - cd "$WIRE_ROOT/wire-solana" -git checkout feature/opp-solana-outpost-integration # see §1.4 -anchor build -p opp-outpost -# Produces target/deploy/opp_outpost.so and target/idl/opp_outpost.json +npm install +npm run build:programs # the build the platform gate runs and the harness deploys +# Produces target/deploy/{liqsol_core,liqsol_token,transfer_hook,validator_leaderboard}.so (+ IDLs) ``` -### 3.6 `wire-e2e-tests` — harness monorepo + `wire-cluster-tool` CLI +A bare `anchor build` is not equivalent to `build:programs`. + +### 3.6 `wire-tools-ts` — harness monorepo + `wire-cluster-tool` CLI ```bash -cd "$WIRE_ROOT/wire-e2e-tests" -pnpm install # .pnpmfile.cjs auto-links @wireio/* from ../wire-libraries-ts and ../wire-opp -pnpm build # tsc -b composite build across flow-a…flow-d + harness +cd "$WIRE_ROOT/wire-tools-ts" +pnpm install # .pnpmfile.cjs links @wireio/* from ../wire-libraries-ts and ../wire-sysio/build/opp +pnpm build # tsc -b across packages/cluster-tool, the flow-* packages and the debugging packages -# Expose wire-cluster-tool on PATH — the harness package.json declares -# "bin": { "wire-cluster-tool": "lib/cjs/cli.js" }, so a plain link -# picks up the post-tsc output. No pkg/standalone binary step. -(cd packages/harness && pnpm link --global) +# The CLI ships in packages/cluster-tool (bins `wire-cluster-tool` and `wtc`, both +# bin/wire-cluster-tool, which runs the built lib/cjs/cli/index.js). Put it on PATH: +export PATH="$WIRE_ROOT/wire-tools-ts/packages/cluster-tool/bin:$PATH" command -v wire-cluster-tool ``` +`pnpm install` prints a `Linked …` line per sibling package it resolved; if `@wireio/opp-typescript-models` +is not among them, `wire-sysio/build/opp/typescript` does not exist yet (see §3.2). + --- ## 4. Running the cluster @@ -306,93 +268,101 @@ killall nodeop kiod anvil solana-test-validator 2>/dev/null || true export CHAIN_DIR="$HOME/ext/wire-chains/dev-001" -wire-cluster-tool \ +wire-cluster-tool create \ --cluster-path="$CHAIN_DIR" \ --force \ - create \ - --build-path="$WIRE_ROOT/wire-sysio/build/debug" \ - --ethereum-path="$WIRE_ROOT/wire-ethereum" \ - --solana-path="$WIRE_ROOT/wire-solana" \ - --prod-count=5 \ - --pnodes=1 \ - --batch-operators=3 \ - --underwriters=1 \ - --epoch-duration=60 - -wire-cluster-tool --cluster-path="$CHAIN_DIR" run -# Ctrl+C to stop. To wipe: wire-cluster-tool --cluster-path="$CHAIN_DIR" destroy + --build-path="$WIRE_ROOT/wire-sysio/build/debug" \ + --ethereum-path="$WIRE_ROOT/wire-ethereum" \ + --solana-path="$WIRE_ROOT/wire-solana" \ + --producer-count=5 \ + --node-count=1 \ + --batch-operator-count=3 \ + --epoch-duration-sec=60 + +wire-cluster-tool run --cluster-path="$CHAIN_DIR" +# Ctrl+C to stop. To wipe: wire-cluster-tool destroy --cluster-path="$CHAIN_DIR" ``` > Note: the harness CLI looks for `nodeop`/`kiod`/`clio`/`sys-util` at > `/bin/`. A full successful `cmake --build` populates > that directory automatically via the `copy_bin` macro. If `build/bin/` > is missing the binaries, the build didn't actually finish; check the -> ninja log for earlier failures (e.g. the OPP POST_BUILD fish gotcha in -> §6.5). +> ninja log for earlier failures. + +The command comes first and every option follows it. Commonly used `create` +flags (the full reference, including the mock-data, API-node and external-outpost +options, is the `create` table in the repo root `README.md`): + +| Flag | Notes | +|---|---| +| `--cluster-path` / `-d` | Directory for chain data, config, logs (required) | +| `--force` | Replace an existing cluster directory | +| `--build-path` | wire-sysio build directory (required) | +| `--ethereum-path` | wire-ethereum root; bootstraps anvil + the outpost contracts (required) | +| `--solana-path` | wire-solana root; bootstraps solana-test-validator with the wire-solana programs (required) | +| `--node-count` / `-n` | Producer node processes (default 1) | +| `--producer-count` / `-p` | Producer accounts to register (default 1) | +| `--batch-operator-count` / `-b` | Batch operators (default 3) | +| `--epoch-duration-sec` | Minimum epoch duration in seconds (default 60) | +| `--enable-mock-liq-pools` | Seed the mock LIQETH/LIQSOL yield pools during epoch zero | +| `--enable-mock-syndication-import` | Import the mock bonder's LIQ positions during epoch zero | + +Swap-underwriter daemons have been removed: `--underwriter-count` still parses, +but any nonzero value is rejected. + +### 4.1 Running a flow + +Each `flow-*` package is a standalone scenario that bootstraps its own cluster. There are 13: + +| Flow | Flow | Flow | +|---|---|---| +| `flow-batch-operator-slashing` | `flow-liq-syndication` | `flow-syndication-challenge` | +| `flow-batch-operator-termination` | `flow-liq-yield` | `flow-syndication-rate-limit` | +| `flow-emergency-stop` | `flow-node-owner-nft` | `flow-syndication-underwriting` | +| `flow-emissions-soak` | `flow-operator-collateral-deposit` | `flow-yield-distribution` | +| `flow-producer-registration` | | | -Flag reference (from `wire-e2e-tests/packages/harness/src/cli.ts`): +Launch one with `scripts/run-flow.mjs` and watch it with `scripts/flow-heartbeat-monitor.mjs` (one monitor +per run, pointed at the same cluster path): -| Flag | Scope | Notes | -|---|---|---| -| `--cluster-path` | global, required | Directory for chain data, config, logs | -| `--force` | global | Remove existing cluster-path before `create` | -| `--build-path` | create, required | wire-sysio build directory | -| `--ethereum-path` | create | wire-ethereum root; bootstraps anvil + OPP contracts | -| `--solana-path` | create | wire-solana root; bootstraps solana-test-validator | -| `-p/--pnodes` | create | Producer node count | -| `-n/--nodes` | create | Non-producer node count | -| `--prod-count` | create | Producers to register (default 21) | -| `--batch-operators` | create | 3–21 | -| `--underwriters` | create | 1–100 | -| `--epoch-duration` | create | Seconds | +```bash +cd "$WIRE_ROOT/wire-tools-ts" ---- +./scripts/run-flow.mjs flow-operator-collateral-deposit \ + --cluster-path /tmp/wire-flow \ + --wire-build-path "$WIRE_ROOT/wire-sysio/build/debug" \ + --ethereum-path "$WIRE_ROOT/wire-ethereum" \ + --solana-path "$WIRE_ROOT/wire-solana" + +# In a second shell: +node scripts/flow-heartbeat-monitor.mjs --cluster-path /tmp/wire-flow +``` -## 5. Differences from the reference Dockerfile - -Noted because the Dockerfile in circulation is partly stale: - -- **`wire-opp` layout.** Dockerfile uses `${WIRE_ROOT}/wire-opp` and assumes - it appears as a side-effect of the `wire-sysio` build. It does not — you - must run `generate-opp-bundles.fish` explicitly. -- **`pnpm link --global` for shared/sdk-core.** Dockerfile globally-links - `shared`, `shared-node`, `shared-web`, `sdk-core`. Not needed locally — - `wire-e2e-tests/.pnpmfile.cjs` hooks resolve them from - `../wire-libraries-ts/packages/` automatically. The packages that still - need a global link are the three CLI binaries (so `.fish` finds them on - PATH) and the generated `wire-opp/{solidity,typescript}` packages (so - `wire-ethereum`'s npm install and any pnpm-based consumer can `link` - them — see §3.5). -- **wire-ethereum `npm i` of pinned opp-models.** Dockerfile does - `npm i && npm link @wireio/opp-solidity-models @wireio/opp-typescript-models` - to work around a `package-lock.json` that pins 1.0.9 (missing types). Still - applies locally. -- **A line in the Dockerfile is a bug:** - `RUN test -d ${WIRE_OPP_ROOT}/typescript && ${WIRE_OPP_ROOT}/solidity && …` - executes `${WIRE_OPP_ROOT}/solidity` as a command instead of checking the - directory. It would silently fail; we don't replicate this. +`run-flow.mjs` accepts the full name, the short form (`operator-collateral-deposit`) or a regex; with no +argument it opens an interactive picker. Each path option falls back to an env var — `WIRE_BUILD_PATH`, +`WIRE_ETH_PATH`, `WIRE_SOLANA_PATH`, `WIRE_CLUSTER_PATH` — and `--cluster-path` is optional (a fresh temp +cluster path is generated per run when omitted). It validates that the build path contains `bin/nodeop` and +the Ethereum path contains `hardhat.config.ts`. A flow exits `0` iff every Report step succeeded; the +Report lands under `/reports/`. Give parallel runs disjoint `--cluster-path` directories. --- -## 6. Troubleshooting / gotchas hit during setup +## 5. Container route -### 6.1 `@yao-pkg/pkg-fetch` race during `pnpm install` in `wire-libraries-ts` +`wire-devcontainer` builds the same toolchain into an image (`Dockerfile`, `e2e-build.Dockerfile`, driven by +`scripts/wire-local-setup.bash`); its `LOCAL_DOCKER_E2E_CLUSTER_GUIDE.md` and `LOCAL_SETUP_GUIDE.md` cover +that path. Where an image's pinned branches or build steps disagree with this page, the repos' own +README / CLAUDE.md files win. -Two `protoc-gen-*` postinstall scripts run `pnpm dist` in parallel, both -calling `pkg-fetch` which writes to the same `~/.pkg-cache/v3.5/` — one wins, -the other fails with `ENOENT: ... fetched-v24.-linux-x64.downloading`. -**Fix:** pre-populate the cache once before `pnpm install`: +--- -```bash -npx --yes @yao-pkg/pkg-fetch node24 linux x64 # fills ~/.pkg-cache/v3.5/ -``` +## 6. Troubleshooting / gotchas -### 6.2 `pnpm link --global` errors with `ERR_PNPM_NO_GLOBAL_BIN_DIR` +### 6.1 `pnpm add --global` / `pnpm link --global` errors with `ERR_PNPM_NO_GLOBAL_BIN_DIR` -Fresh pnpm installs don't have a global bin dir wired. Run `pnpm setup` -once — it picks `$HOME/.local/share/pnpm`, writes `PNPM_HOME` and the -PATH export into your shell rc, and configures `global-bin-dir` / -`global-dir`: +`generate-opp-bundles.sh` registers its CLIs with `pnpm add --global`, which needs a global bin dir. Fresh +pnpm installs don't have one wired. Run `pnpm setup` once — it picks `$HOME/.local/share/pnpm`, writes +`PNPM_HOME` and the PATH export into your shell rc, and configures `global-bin-dir` / `global-dir`: ```bash pnpm setup @@ -400,72 +370,33 @@ pnpm setup # so PNPM_HOME and PATH pick up. ``` -### 6.3 `protoc-gen-ts not found` during `generate-opp-bundles.fish` +### 6.2 `wire-sysio/build/opp/` is missing after a successful build -`wire-protobuf-bundler` calls `protoc-gen-ts` from `@protobuf-ts/plugin` -(not the 3 CLIs we built from `wire-libraries-ts`). Install globally: +The OPP bundle step only attaches when CMake finds `bash`, `npm`, `pnpm` and `node` at configure time and +`node >= 24` / `pnpm >= 10`. Otherwise configure prints +`BUILD_OPP_BUNDLES is ON, but requirements are not met: … OPP bundles (TypeScript, Solidity, Solana) will NOT +be generated.` and the build succeeds without them. Fix the toolchain (§1.2), re-run the `cmake -S . -B +"$BUILD_DIR" …` configure so the check re-evaluates, and rebuild. -```bash -pnpm add -g @protobuf-ts/plugin # provides protoc-gen-ts on PATH -``` +### 6.3 Cold-build error: `'sysio/opp/types/types.pb.hpp' file not found` -### 6.4 Cold-build error: `'sysio/opp/types/types.pb.hpp' file not found` - -The `contracts_project` ExternalProject needs the zpp-generated `.pb.hpp` -headers before it compiles. wire-sysio commit `1015c98d3` (Apr 2026) wired -`add_dependencies(contracts_project opp_cdt_models.protos)`, so a single -`cmake --build` should now produce the headers in order. If you still see -the error on a cold build, run the protos target explicitly first: +The `contracts_project` ExternalProject needs the zpp-generated `.pb.hpp` headers before it compiles; the +CMake wiring adds that dependency, so a single `cmake --build` produces the headers in order. If you still +see the error on a cold build, run the protos target explicitly first: ```bash ninja -C build/debug opp_cdt_models.protos cmake --build build/debug -j${NUM_JOBS} ``` -### 6.5 Build fails at `libopp.a` with `required tools are not found on PATH` - -`libraries/opp/CMakeLists.txt` runs `generate-opp-bundles.fish` as a -POST_BUILD step on `libopp.a`. If `wire-protobuf-bundler`, -`protoc-gen-solidity`, or `protoc-gen-solana` aren't on `PATH` when ninja -runs that step, the link fails with: - -``` -Error: The following required tools are not found on PATH: - - wire-protobuf-bundler - - protoc-gen-solidity - - protoc-gen-solana -``` - -(wire-sysio commit `b66eea7243` fixed an earlier bug where the CMake -"tools missing → skip" branch never fired due to `find_program` cache -reuse — now that branch works, and CMake will warn-and-skip when the CLIs -are absent. Older builds without that fix attached the POST_BUILD step -unconditionally.) - -Fix: install the three CLIs via §3.1 and ensure `$PNPM_HOME` (typically -`$HOME/.local/share/pnpm`) is on `PATH` before `cmake --build`. - -### 6.6 `wire-cluster-tool create` crashes with `paths[0] must be string` when `--solana-path` is omitted - -Harness `cli.ts:233` calls `Path.resolve(argv.solanaPath)` unconditionally. -Until fixed in harness, always pass `--solana-path=` even -if the Solana program isn't deployed — `SOLBootstrap` will warn and skip -program init when the expected `/wallets/opp-outpost-keypair.json` -is absent. - -### 6.7 `wire-solana` master is the liqSol repo — use the OPP branch - -`wire-solana` on `master` is the Capital Staking / liqSol program and does -not contain `opp-outpost`. Checkout -`feature/opp-solana-outpost-integration`, which adds `programs/opp-outpost/`, -`wallets/opp-outpost-keypair.json`, and the Anchor.toml entry. Then -`anchor build -p opp-outpost` produces the `.so` + IDL the harness expects. +### 6.4 `pkill -f ""` self-terminates your session -### 6.8 `pkill -f ""` self-terminates your session +`pkill -f "solana-test-validator|anvil|kiod|nodeop"` matches the regex against the full argv (including the +pattern string itself), so `pkill` kills its parent shell — the Bash/zsh invocation exits 144 and +`wire-cluster-tool` never runs. Use `killall ` instead. -The Dockerfile's run instructions suggest -`pkill -f "solana-test-validator|anvil|kiod|nodeop"`. Because that regex is -matched against the full argv (including the pattern string itself), `pkill` -kills its parent shell — the Bash/zsh invocation exits 144 and `wire-cluster-tool` -never runs. Use `killall ` instead. +### 6.5 Workstation disk usage above 90% +`nodeop`'s resource monitor shuts a node down when its data volume passes the 90% threshold. Local clusters +set `resource-monitor-not-shutdown-on-threshold-exceeded = true`, but stale cluster directories still +consume space quickly — clear old `--cluster-path` directories before starting several clusters. diff --git a/docs/production-bootstrap.md b/docs/production-bootstrap.md index 5f1a21e10..e9fe4d33b 100644 --- a/docs/production-bootstrap.md +++ b/docs/production-bootstrap.md @@ -12,8 +12,8 @@ defaults*. They split into two kinds: - **Structural / fixed** — core symbol, chain/token codes, ROA byte price, operator caps, authority shapes, the `setemitcfg` split ratios. Production keeps these. - **Deployment-tunable / dev stand-in** — keys, the finalizer set, supplies, epoch duration, collateral - amounts and lock windows, the external (ETH/SOL) chain ids, mock token contract addresses, reserve seed - sizes, operator/producer counts. Production substitutes real keys, the real finalizer set, real external + amounts and lock windows, the external (ETH/SOL) chain ids, mock token contract addresses, syndication + bucket sizes and challenge window, operator/producer counts. Production substitutes real keys, the real finalizer set, real external chain ids/addresses, and final economic policy. Each such value is flagged **(cluster; production: …)**. ## Conventions @@ -27,7 +27,7 @@ defaults*. They split into two kinds: `sysio` (giftram self-reference). `code` = raw wasm bytes (hex); `abi` = PACKED `abi_def` bytes; `vmtype = vmversion = 0`. Both actions are signed `[sysio@active]`. - **slug(X)** — the `slug_name` codename for string `X`, packed into the `{ value: }` shape the - regenerated ABI emits. Chain/token/reserve codes are slug names. + regenerated ABI emits. Chain/token codes are slug names. ## RAM model — no unlimited accounts Every account's RAM is **finite** and **gifted from the `sysio` pool** as a conserving transfer (never minted): @@ -49,8 +49,7 @@ Every account's RAM is **finite** and **gifted from the `sysio` pool** as a cons code/abi bytes from the pool. `giftram` REJECTS a non-finite target (`"giftram target must have a finite RAM limit"`), so the account must already be finite. - **System-contract table rows** — config/state/registration/log rows on the separate-account system - contracts (`sysio.token` plus the OPP set: chains, tokens, epoch, opreg, msgch, uwrit, reserv, chalg, - dclaim) — are billed directly to `sysio` via a per-contract `ram_payer = "sysio"_n` (privileged-contract + contracts (`sysio.token` plus the OPP set, Stage 8) — are billed directly to `sysio` via a per-contract `ram_payer = "sysio"_n` (privileged-contract model). `setsyscode`'s `giftram` covers only code/abi, so a self-billed row would overflow the contract's exact limit; routing rows to the pool keeps each contract account finite at code/abi size. `bios`/`system` code lives on `sysio` and likewise consumes the pool directly. @@ -63,9 +62,9 @@ Every account's RAM is **finite** and **gifted from the `sysio` pool** as a cons - **All `sysio.*` accounts: owner = active = `sysio@active`** — an account-authority delegating to `sysio`: `{threshold:1, keys:[], accounts:[{permission:{actor:"sysio",permission:"active"},weight:1}], waits:[]}`; no standalone key. Governance (`sysio`, msig-backed in production) controls every system account and signs every - `[sysio.X@active]` step. Stage 8 only ADDs `@sysio.code` weights on the nine OPP accounts' **owner** - authorities on top of that `sysio@active` base (never removing it). No other authority is rewritten during - the bootstrap. + `[sysio.X@active]` step. Stage 8 only ADDs `@sysio.code` weights on the eleven OPP accounts' **owner** + authorities on top of that `sysio@active` base (never removing it). Stage 10b additionally creates and + links `sysio.andon@pull` and `sysio.andon@clear`, both children of its unchanged `active` permission. - The root **`sysio`** account is the one exception: its own `active` authority carries a **standalone key** from genesis (cluster: `DEV_K1`; production: the governance key / msig) and is never rewritten. It is never a `sysio@active` self-reference. @@ -93,11 +92,11 @@ DEV_BLS = BLS.regenerate(SHA256("wire")) # BLS12-381 finalizer key | `DEV_BLS_PUBLIC_KEY` | Genesis `initial_finalizer_key` (bios finalizer). | `DEV_BLS.publicKey` (`PUB_BLS_*` spelling) | | `DEV_BLS_PROOF_OF_POSSESSION` | PoP for `DEV_BLS_PUBLIC_KEY`. | `DEV_BLS.proofOfPossession` (`SIG_BLS_*` spelling) | | Per-node K1/BLS keys | Producer nodes' own block-signing (K1) + finalizer (BLS) keys, distinct from `DEV_*`; used by `setprodkeys` / `setfinalizer` AND as the producer accounts' owner/active keys. | generated at runtime (`clio create key --k1`, `sys-util bls create key`) | -| Per-operator K1/EM/ED keys | Each batch operator / underwriter's UNIQUE identity: a WIRE account key (K1, imported into kiod so `@active` signs), an ETH key (EM, anvil-mnemonic HD-derived), and a SOL key (ED25519). The chain ACCOUNT is node-owner-generated (`wireno.` via `roa::newuser`); the deterministic labels (`batchop.a`, `uwrit.a`) are the newuser sponsor nonces + the tooling's keystore keys. | generated at runtime (`KeyGenerator`) | +| Per-operator K1/EM/ED keys | Each batch operator's UNIQUE identity: a WIRE account key (K1, imported into kiod so `@active` signs), an ETH key (EM, anvil-mnemonic HD-derived), and a SOL key (ED25519). The chain ACCOUNT is node-owner-generated (`wireno.` via `roa::newuser`); the deterministic labels (`batchop.a`, …) are the newuser sponsor nonces + the tooling's keystore keys. | generated at runtime (`KeyGenerator`) | | `BOOTSTRAP_NODE_OWNER` | Bootstrap tier-1 node owner (2–6 chars to satisfy `valid_name_for_tier`); its tier-1 reserve is what post-bootstrap resource policies are issued from. | `wireno` | | `DEFAULT_WALLET_NAME` | kiod wallet the bootstrap creates and every helper re-opens. | `default` | -¹ **`DEV_K1` derivation & governance scope.** `DEV_K1_PUBLIC_KEY` is `K1` regenerated from `SHA256("nathan")` (the well-known dev key, SYS-prefixed). It is the genesis block-signing key, the standalone key on `sysio`'s own `active`, and the owner/active key of node owner `wireno` — nothing else. Producer accounts are keyed by their **hosting node's generated K1**, operators (`batchop.*` / `uwrit.*`) by their **own per-operator generated K1**, and the `sysio.*` system accounts (and `dev.owner1`) by `sysio@active` — production substitutes real keys / msig throughout. +¹ **`DEV_K1` derivation & governance scope.** `DEV_K1_PUBLIC_KEY` is `K1` regenerated from `SHA256("nathan")` (the well-known dev key, SYS-prefixed). It is the genesis block-signing key, the standalone key on `sysio`'s own `active`, and the owner/active key of node owner `wireno` — nothing else. Producer accounts are keyed by their **hosting node's generated K1**, operators (`batchop.*`) by their **own per-operator generated K1**, and the `sysio.*` system accounts (and `dev.owner1`) by `sysio@active` — production substitutes real keys / msig throughout. ### Core symbol, tokens & supplies | Constant | Value | @@ -113,9 +112,12 @@ DEV_BLS = BLS.regenerate(SHA256("wire")) # BLS12-381 finalizer key | `activateroa.total_sys` | `75496.0000 SYS` **(cluster; production: real pool sizing)** | | `ROA_BYTES_PER_UNIT` | `104` (fixed) | -### Resource-policy weights (`sysio.roa::addpolicy`, issued as `wireno`) — NOT part of the bootstrap -The bootstrap registers `wireno` (Stage 10) so its tier-1 reserve can issue these policies, but issues none -itself — flows/tools provision users and non-bootstrapped operators with them post-bootstrap: +### Resource-policy weights (`sysio.roa::addpolicy`, issued as `wireno`) +The bootstrap registers `wireno` (Stage 10) so its tier-1 reserve can issue these policies. Stage 10b issues +the standard policy below to the panic account and a separate RAM-only policy to `sysio.andon` for native +permissions and action links (`net_weight = cpu_weight = "0.0000 SYS"`, `ram_weight = "0.0100 SYS"`, +`time_block = network_gen = 0`). Flows/tools use the standard policy for users and non-bootstrapped +operators after bootstrap: | Field | Value | |---|---| @@ -130,7 +132,7 @@ itself — flows/tools provision users and non-bootstrapped operators with them | `producerCount` | `21` (`MAX_PRODUCERS`) | accounts `defproducera … defproduceru` | | `nodeCount` | `1` **(cluster)** | producer nodes hosting the producers | | `batchOperatorCount` | `3` **(cluster)** | labels `batchop.a/b/c` (chain accounts are `wireno.`, node-owner-generated) | -| `underwriterCount` | `1` **(cluster)** | label `uwrit.a` (chain account likewise generated) | +| `underwriterCount` | `0` | swap-underwriter daemons have been removed; any nonzero value is rejected | | `apiCount` | `0` **(cluster)** | non-producing API nodes serving the chain API + `/v1/query/execute` | | `epochDurationSec` | `90` **(cluster; production: real cadence)** | | | `EnvelopeLogRetentionEpochs` | `10` | `sysio.epoch::setconfig.epoch_retention_envelope_log_count` | @@ -144,14 +146,19 @@ itself — flows/tools provision users and non-bootstrapped operators with them effectively active from genesis — wire-sysio whitelists the `preactivate_feature` intrinsic in its genesis intrinsic set — so the tooling simply skips its digest; no producer-API scheduling step exists. "already activated" errors are benign and ignored. -3. `sysio::setfinalizer({finalizer_policy:{threshold, finalizers:[…]}})` — `[sysio@active]`: - - `threshold = floor(N*2/3) + 1`, where `N` = number of producer nodes (cluster default `N = 1` ⇒ - `threshold = 1`). - - each finalizer `= {description:"finalizer-", weight:1, public_key:, pop:}`. These are the **producer nodes' own generated BLS - keys**, not `DEV_BLS` (which is only the genesis/bios finalizer). - The chain finalizes on these node BLS keys and keeps producing on the genesis `sysio` producer until the - Stage 5 handoff — no early `setprods`/`setprodkeys` (producer accounts don't exist pre-pool). +3. `sysio::setfinalizer({finalizer_policy:{threshold, finalizers:[…]}})` — `[sysio@active]` — a bios-ABI + action, so it runs before `system` replaces `bios` in Stage 3: + - `threshold = floor(N*2/3) + 1`, where `N` = number of producer **accounts** (`producerCount = 21` ⇒ + `threshold = 15`). + - each finalizer `= {description:, weight:1, public_key:, pop:}`. Every producer account owns a BLS finalizer key minted for + it before any node starts (its block-signing K1 stays shared with its hosting node); these are not + `DEV_BLS` (which is only the genesis/bios finalizer). The policy is keyed on accounts, not nodes, + because `sysio.system` rebuilds the finalizer policy from the keys accounts registered via `regfinkey` + (step 23) the first time producer ranking publishes; a node-keyed genesis policy would be replaced by + one the running nodes hold no key for. + The chain finalizes on these keys and keeps producing on the genesis `sysio` producer until the Stage 5 + handoff — no early `setprods`/`setprodkeys` (producer accounts don't exist on chain pre-pool). ## Stage 2 — Bring-up-essential accounts only (native `newaccount`, pre-ROA) 4. `sysio::newaccount({creator:"sysio", name, owner:sysio@active, active:sysio@active})` — `[sysio@active]` — @@ -189,7 +196,7 @@ unlimited. | Group | Accounts | |---|---| | System / authority | `sysio.noop`, `sysio.bpay`, `sysio.msig`, `sysio.names`, `sysio.token`, `sysio.vpay`, `sysio.wrap`, `sysio.authex` | -| OPP set | `sysio.chains`, `sysio.tokens`, `sysio.epoch`, `sysio.opreg`, `sysio.msgch`, `sysio.uwrit`, `sysio.reserv`, `sysio.chalg`, `sysio.dclaim` | +| OPP set | `sysio.chains`, `sysio.tokens`, `sysio.epoch`, `sysio.opreg`, `sysio.msgch`, `sysio.chalg`, `sysio.dclaim`, `sysio.swap`, `sysio.liq`, `sysio.andon`, `sysio.bond`, `sysio.synd` | | T5 buckets | `sysio.gov` (governance), `sysio.ops` (capex/ops) | | Dev-only (cluster) | `dev.owner1` | 11. `sysio::setprodkeys({schedule:[{producer_name, block_signing_key}…]})` — `[sysio@active]` — one row per @@ -216,14 +223,18 @@ standalone key, and `sysio.authex` keeps the plain `sysio@active` owner/active i ## Stage 8 — OPP contracts + owner `sysio.code` grants 18. deploy the OPP set — **sys** — `[sysio@active]`, in order: `sysio.chains`, `sysio.tokens`, `sysio.epoch`, - `sysio.opreg`, `sysio.msgch`, `sysio.uwrit`, `sysio.reserv`, `sysio.chalg`, `sysio.dclaim`. + `sysio.opreg`, `sysio.msgch`, `sysio.chalg`, `sysio.dclaim`, `sysio.andon`, `sysio.swap`, `sysio.liq`, + `sysio.bond`, `sysio.synd`. `sysio.andon` precedes the four contracts that read its cord (swap, liq, bond, + synd), `sysio.bond` precedes `sysio.synd`, and `sysio.synd` precedes the first liq-token registration + (Stage 11). 19. `sysio::updateauth` on **each OPP account's owner** (`grantSysioCode`) — `[@owner]` — owner ← `{threshold:1, keys:[], accounts:[{sysio@active,1},{@sysio.code,1}], waits:[]}` (sorted by name - value; `sysio` sorts first). (9 calls.) Lets each contract inline-send its own actions (epoch `advance`, - `evalcons`, `dispatch`, …) while staying governed by `sysio@active`. + value; `sysio` sorts first). (11 calls: every OPP account except `sysio.andon`, which sends no inline + actions.) Lets each contract inline-send its own actions (epoch `advance`, `evalcons`, `dispatch`, …) + while staying governed by `sysio@active`. -These nine owner grants are the ONLY authority rewrites in the bootstrap. The former cross-contract -active-permission delegations (`@sysio.code` weights for `sysio.msgch` on opreg/roa and for `sysio.roa` on +These eleven owner grants are separate from the native Andon child permissions and action links created +in Stage 10b. The former cross-contract active-permission delegations (`@sysio.code` weights for `sysio.msgch` on opreg/roa and for `sysio.roa` on authex) are no longer configured. `sysio.dclaim` must be deployed before the first `sysio.authex::createlink` or `recordlink`. Both link @@ -235,7 +246,7 @@ uses `sysio.roa::setsyscode`, which grants privilege as part of deployment, so t `setpriv` action—is the production precondition. This bootstrap satisfies it: DClaim is deployed in Stage 8 before the first node-owner link in Stage 10 and operator `createlink` calls in Stage 12. -## Stage 9 — OPP / application configuration (epoch, opreg, emissions, dclaim) +## Stage 9 — OPP / application configuration (epoch, opreg, genesis producer registration, emissions, dclaim) 20. `sysio.epoch::setconfig({epoch_duration_sec:90, operators_per_epoch:1, batch_operator_minimum_active:3, batch_op_groups:3, epoch_retention_envelope_log_count:10})` — `[sysio.epoch@active]`. Sizing is computed from the operator counts: @@ -249,7 +260,7 @@ before the first node-owner link in Stage 10 and operator `createlink` calls in |---|---|---| | `max_available_producers` | `21` | | | `max_available_batch_ops` | `63` | | -| `max_available_underwriters` | `21` | | +| `max_available_underwriters` | `21` | still part of the `opreg` config; the bootstrap registers no underwriter operators | | `terminate_prune_delay_ms` | `600000` | 10 min **(cluster; production: larger)** | | `terminate_max_consecutive_misses` | `5` | | | `terminate_max_pct_misses_24h` | `5` | | @@ -258,7 +269,27 @@ before the first node-owner link in Stage 10 and operator `createlink` calls in | `req_batchop_collat` | `[]` | empty by default | | `req_uw_collat` | `[]` | empty by default | -22. **WIRE token + emissions** — `sysio.token` is reused for a separate `9,WIRE` token the emissions contract +22. **Genesis producer operators** — `sysio.opreg::regoperator({account:, + type:OPERATOR_TYPE_PRODUCER, is_bootstrapped:true})` — `[sysio.opreg@active]` — one call per producer + account (`defproducera … defproduceru`). Registered here rather than in Stage 5 because `sysio.opreg` is + only deployed (Stage 8) and configured (step 21) by now. Bootstrapped by fiat: genesis producers post no + collateral, `req_prod_collat` ships empty, and a non-bootstrapped registration against an empty + requirement vector would never become eligible. As bootstrapped operators they are also exempt from + `termcheck`. +23. **Genesis producer registration** — per producer account, in this order: + - `sysio::regproducer({producer:, producer_key:, url:"", location:0})` — + `[@active]`. `sysio.system` admits it only for an ACTIVE PRODUCER operator, so it follows + step 22. Registration is what makes a producer rankable: the ranked schedule is built only from + producers with an active `producers` row. + - `sysio::regfinkey({finalizer_name:, finalizer_key:, + proof_of_possession:})` — `[@active]`. Requires the `producers` row written just + above; a producer's first finalizer key is activated by `regfinkey` itself, so no `actfinkey` follows. + The key is the same per-account BLS key the Stage 1 finalizer policy was built from. + + The producer, finalizer-key and finalizer rows are billed to `sysio.system`; producers receive no RAM + grant for them. The bootstrap makes no producer collateral deposit. + +24. **WIRE token + emissions** — `sysio.token` is reused for a separate `9,WIRE` token the emissions contract reads from `sysio`'s balance. Five actions, in order: - `sysio.token::create({issuer:"sysio", maximum_supply:"1000000000.000000000 WIRE"})` — `[sysio.token@active]`. - `sysio.token::issue({to:"sysio", quantity:"1000000000.000000000 WIRE", memo:"initial WIRE for emissions"})` — `[sysio@active]`. @@ -304,17 +335,17 @@ before the first node-owner link in Stage 10 and operator `createlink` calls in | `epoch_log_retention_count` | `8640` | emissions pay-log retention, in epochs | | `pay_cadence_epochs` | `1` | fire `payepoch` every epoch **(cluster; production: higher)** | -23. `sysio.dclaim::setconfig({})` — `[sysio.dclaim@active]` — idempotent; creates the `cap_config` singleton +25. `sysio.dclaim::setconfig({})` — `[sysio.dclaim@active]` — idempotent; creates the `cap_config` singleton with the contract's default 180-day claim window. (No `setclmwindow`/`importseed`/`importdone` in the bootstrap — those are external/operational tools, not part of the sequence.) ## Stage 10 — Register the bootstrap node owner (real `nodeownreg` flow; NO `forcereg`) Drives the two `sysio.roa` actions the OPP NFT-claim depot (`sysio.msgch`) would inline-send for a real claim: -24. `sysio.roa::newnameduser({account:"wireno", pubkey:DEV_K1_PUBLIC_KEY, tier:1})` — `[sysio.roa@active]` — +26. `sysio.roa::newnameduser({account:"wireno", pubkey:DEV_K1_PUBLIC_KEY, tier:1})` — `[sysio.roa@active]` — creates `wireno` (owner = active = `DEV_K1`) with a finite pool-gifted RAM allocation. `tier:1` = T1 (Validator); `NodeOwnerTier` = `{T1:1, T2:2, T3:3}`. -25. `sysio.roa::nodeownreg({owner:"wireno", tier:1, eth_pub_key:, wire_pub_key:DEV_K1_PUBLIC_KEY, eth_address:<20-byte ETH address>})` — +27. `sysio.roa::nodeownreg({owner:"wireno", tier:1, eth_pub_key:, wire_pub_key:DEV_K1_PUBLIC_KEY, eth_address:<20-byte ETH address>})` — `[sysio.roa@active]` — records the depositor ETH key as a `sysio.authex` link (inline `recordlink`) and allocates the tier-1 reserve post-bootstrap resource policies are issued from. `eth_pub_key` is a **fresh random `PUB_EM_*` secp256k1 key (cluster throwaway; production: the NFT depositor's key)**; its @@ -323,15 +354,58 @@ Drives the two `sysio.roa` actions the OPP NFT-claim depot (`sysio.msgch`) would aborting the transaction, so the tooling follows with a verify that the `nodeowners` row exists (surfacing the audit rejection if not). -## Stage 11 — Outpost deploys, then registry seeding + underwriter config +## Stage 10b — Emergency stop (`sysio.andon`) +`sysio.andon` exposes `pull(reason)` and `clear(note)`. Authorization is configured with native +`sysio::updateauth` and `sysio::linkauth`; there is no contract-managed panic account or puller registry. +Create the panic account first so the delegated authority can reference it. + +**Companion requirement:** deploy the native-permission Andon contract from wire-sysio #662 and use +tooling that implements the sequence below. Older `ClusterBuildDefaults` / `AndonContractSteps` that +invoke `setpanic` or `addpuller` must be updated before running this bootstrap against that contract; +those actions no longer exist. This sequence supersedes that older harness configuration. + +28. **Panic account** — `sysio::newaccount({creator:"sysio", name:"andon.panic", owner:DEV_K1_PUBLIC_KEY, + active:DEV_K1_PUBLIC_KEY})` — `[sysio@active]` — then `sysio.roa::addpolicy({owner:"andon.panic", + issuer:"wireno", net_weight:"25.0000 SYS", cpu_weight:"25.0000 SYS", ram_weight:"25.0000 SYS", + time_block:0, network_gen:0})` — `[wireno@active]`. No WIRE is transferred to it **(cluster dev key; + production: the governance-held panic key)**. +29. **Permission RAM** — `sysio.roa::addpolicy({owner:"sysio.andon", issuer:"wireno", + net_weight:"0.0000 SYS", cpu_weight:"0.0000 SYS", ram_weight:"0.0100 SYS", time_block:0, + network_gen:0})` — `[wireno@active]`. Fund the native permission and link rows before creating them; + the contract deployment's RAM gift covers code/ABI, not these additional rows. +30. **Delegate and link each action**, first `pull`, then `clear`. For each `permission`: + - `sysio::updateauth({account:"sysio.andon", permission, parent:"active", auth:{threshold:1, + keys:[], accounts:[{permission:{actor:"andon.panic",permission:"active"},weight:1}, + {permission:{actor:"sysio",permission:"active"},weight:1}], waits:[]}})` — `[sysio.andon@active]`. + Sort the authority's accounts by chain name value when substituting a production panic account. + - `sysio::linkauth({account:"sysio.andon", code:"sysio.andon", type:permission, + requirement:permission})` — `[sysio.andon@active]`. + +The panic account signs with its own key but declares `[sysio.andon@pull]` for `pull({reason})` and +`[sysio.andon@clear]` for `clear({note})`. Governance is also delegated in both authorities and retains +control through the parent `active` permission. Inspect both authorities and action links with +`clio get account sysio.andon` before traffic begins. + +Privileged `sysio.synd` automatically pulls on a custody shortfall using the existing +`sysio.andon@active` permission; it needs no puller registration. Changing the panic delegate through +`updateauth` does not revoke that privileged inline path. Neither `sysio.andon`'s owner nor its active +authority is replaced by this setup. + +## Stage 11 — Outpost deploys, then registry + syndication configuration The ETH and SOL outposts deploy here (chain-side, not depot actions): anvil starts (instamine), the Ethereum -outpost contracts deploy + seed, anvil switches to interval mining; solana-test-validator starts with -`liqsol_core` (the OPP outpost), then PDAs init + SPL reserves provision. These deploys produce the artifact -files (`outpost-addrs.json`, `liqeth-addrs.json`, `sol-mock-mints.json`) the registry rows below read their -chain-side addresses from; production registers the canonical contract/mint addresses via the same shapes. - -26. **Chains** — `sysio.chains::regchain({kind, code, external_chain_id, name, description})` — - `[sysio.chains@active]`, one per chain (registered ACTIVE; there is no separate `activchain`): +outpost contracts deploy (`OPP`, `OPPInbound`, `OutpostManager`, `SyndicationPool`, `BAR`, the inert +`StakingManager`, plus the `liqEth` suite) and the tooling verifies the `SyndicationPool` configuration and +the panic account's pause role, then anvil switches to interval mining; solana-test-validator starts with all +four wire-solana programs at genesis (`liqsol_core` hosts the OPP outpost), the liqsol surface is stood up by +wire-solana's own `init-*` scripts, and the OPP PDAs are initialized with the native-SOL token binding and +precision. These deploys produce the artifact files (`outpost-addrs.json`, `liqeth-addrs.json`) the registry +rows below read their chain-side addresses from; in external-outpost mode the operator-supplied artifacts +(including `sol-mock-mints.json`) are copied into place instead. Production registers the canonical +contract/mint addresses via the same shapes. + +31. **Chains** — `sysio.chains::regchain({kind, code, external_chain_id, name, description, outpost})` — + `[sysio.chains@active]`, one per chain (registered ACTIVE; there is no separate `activchain`). `outpost` + is left empty here and filled by `setoutpost` in Stage 12: | `kind` | `code` | `external_chain_id` | `name` | `description` | |---|---|---|---|---| @@ -339,92 +413,110 @@ chain-side addresses from; production registers the canonical contract/mint addr | `CHAIN_KIND_EVM` | `slug("ETHEREUM")` | `31337` **(cluster anvil; production: real EVM id)** | `Ethereum (anvil)` | local anvil EVM chain | | `CHAIN_KIND_SVM` | `slug("SOLANA")` | `0` | `Solana (test-validator)` | local solana-test-validator | -27. **Tokens** — `sysio.tokens::regtoken({kind, code, symbol_name, description, precision, address})` — +32. **Tokens** — `sysio.tokens::regtoken({kind, code, symbol_name, description, precision, address})` — `[sysio.tokens@active]`, one per token (registered ACTIVE; no separate `activtoken`). `precision` is `9` for NATIVE/LIQ tokens and `6` for the ERC-20/SPL stablecoins (their chain-native decimals). `address = {kind, address}`; NATIVE leaves `address` empty; non-native carries the chain-side contract bytes (hex, - `0x` stripped): + `0x` stripped) when the deploy artifact provides them, and is empty otherwise: | `kind` | `code` | `symbol_name` | `precision` | `address` source | |---|---|---|---|---| | `TOKEN_KIND_NATIVE` | `slug("WIRE")` | `Wire` | `9` | empty | | `TOKEN_KIND_NATIVE` | `slug("ETH")` | `Ether` | `9` | empty | | `TOKEN_KIND_LIQ` | `slug("LIQETH")` | `Liquid ETH` | `9` | deployed LiqETH EVM address **(runtime)** | -| `TOKEN_KIND_ERC20` | `slug("USDC")` | `USD Coin` | `6` | mock USDC EVM address **(runtime)** | -| `TOKEN_KIND_ERC20` | `slug("USDT")` | `Tether USD` | `6` | mock USDT EVM address **(runtime)** | +| `TOKEN_KIND_ERC20` | `slug("USDC")` | `USD Coin` | `6` | `MockUsdc` from `outpost-addrs.json` when present (the local deploy no longer ships one, so empty) | +| `TOKEN_KIND_ERC20` | `slug("USDT")` | `Tether USD` | `6` | `MockUsdt` from `outpost-addrs.json` when present (likewise empty locally) | | `TOKEN_KIND_NATIVE` | `slug("SOL")` | `Sol` | `9` | empty | -| `TOKEN_KIND_LIQ` | `slug("LIQSOL")` | `Liquid SOL` | `9` | mock LIQSOL SPL mint **(runtime)** | -| `TOKEN_KIND_SPL` | `slug("USDCSOL")` | `USDC (Solana)` | `6` | mock USDC SPL mint **(runtime)** | -| `TOKEN_KIND_SPL` | `slug("USDTSOL")` | `USDT (Solana)` | `6` | mock USDT SPL mint **(runtime)** | +| `TOKEN_KIND_LIQ` | `slug("LIQSOL")` | `Liquid SOL` | `9` | LIQSOL mint from `sol-mock-mints.json` when present | +| `TOKEN_KIND_SPL` | `slug("USDCSOL")` | `USDC (Solana)` | `6` | USDC mint from `sol-mock-mints.json` when present | +| `TOKEN_KIND_SPL` | `slug("USDTSOL")` | `USDT (Solana)` | `6` | USDT mint from `sol-mock-mints.json` when present | -28. **Chain-token bindings** — `sysio.tokens::regctok({chain_code, token_code, contract_addr, is_native})` — +33. **Chain-token bindings** — `sysio.tokens::regctok({chain_code, token_code, contract_addr, is_native})` — `[sysio.tokens@active]`, one per binding (no separate `activctok`). Exactly one `is_native:true` per chain; non-native bindings carry the same chain-side address bytes as their token row (empty when unavailable): `(WIRE,WIRE,native)`, `(ETHEREUM,ETH,native)`, `(ETHEREUM,LIQETH)`, `(ETHEREUM,USDC)`, `(ETHEREUM,USDT)`, `(SOLANA,SOL,native)`, `(SOLANA,LIQSOL)`, `(SOLANA,USDCSOL)`, `(SOLANA,USDTSOL)`. -29. **Reserves** — `sysio.reserv::regreserve({chain_code, token_code, reserve_code:slug("PRIMARY"), name, - description, initial_chain_amount, initial_wire_amount:10000000000, source_token_precision, - connector_weight_bps:5000, is_private:false, owner:""})` — `[sysio.reserv@active]`, one PRIMARY reserve - per external chain-token (registered ACTIVE). Eight reserves: `ETHEREUM×{ETH, LIQETH, USDC, USDT}` and - `SOLANA×{SOL, LIQSOL, USDCSOL, USDTSOL}`. Every reserve seeds a 10-token notional on each leg **(cluster - devnet sizing; production: real seeds)**, 50% Bancor connector weight, public, no owner. The depot frames - each chain leg at `min(native, 9)` decimals, recorded per-reserve as `source_token_precision`: - - non-stable rows: `initial_chain_amount = 10,000,000,000` (9-dec frame), `source_token_precision = 9`; - - stablecoin rows (`USDC`/`USDT`/`USDCSOL`/`USDTSOL`): `initial_chain_amount = 10,000,000` (the same 10 - tokens in their 6-dec native frame), `source_token_precision = 6`. - -30. `sysio.uwrit::setconfig({fee_bps:30, collateral_lock_duration_ms:600000, min_fromwire_amount:100000000, - fromwire_revert_fee_bps:10})` — `[sysio.uwrit@active]`: - - `fee_bps = 30` — the per-spoke swap fee, taken out of the WIRE leg of every swap; `sysio.reserv` routes - the collected fee 50/50 to its rewards bucket and the `sysio` emissions treasury - (`FEE_REWARD_SHARE_BPS`) **(cluster 30; the contract default is 10)**. - - `collateral_lock_duration_ms = 600000` — the **wall-clock** challenge window: locks are never released - by delivery; they expire this many ms after creation and are swept by `chklocks` at epoch advance - **(cluster 10 min; the contract default is 12 h = 43,200,000 ms — production uses that)**. - - `min_fromwire_amount = 100000000` — minimum `swapfromwire` escrow (9-dec base units) = 0.1 WIRE - **(cluster, matching the flow's escrow exactly; the contract default is 5 WIRE = 5,000,000,000)**. - - `fromwire_revert_fee_bps = 10` — fee on caller-fault drain-time reverts of queued `swapfromwire` rows - (zero quote / missed variance at `drainfwq`), routed like the settlement fee (mirrors the contract - default; happy-path flows never pay it and system-caused reverts refund in full). +34. `sysio.swap::setconfig({fee_authority:"sysio", system_token:{sym:"9,WIRE", contract:"sysio.token"}})` — + `[sysio.swap@active]` — the depot-local AMM's fee authority and system token. `sysio.swap` trades on the + depot only; it has no outpost participation and emits no OPP attestations. +35. **Shadow liq symbols** — `sysio.liq::create({sym, chain_code, token_code})` — `[sysio.liq@active]`, one per + registered liq token: `{sym:"9,LIQETH", chain_code:slug("ETHEREUM"), token_code:slug("LIQETH")}` and + `{sym:"9,LIQSOL", chain_code:slug("SOLANA"), token_code:slug("LIQSOL")}`. Syndicated outpost custody is + minted 1:1 into these symbols. +36. `sysio.liq::setkicker({bps:200})` — `[sysio.liq@active]` — the T5 yield kicker. +37. **Underwriting + syndication rules:** + - `sysio.bond::setconfig({hold_bps:1000})` — `[sysio.bond@active]` — the hold bond, in bps of a request's + covered amount (the contract default, set explicitly). + - `sysio.synd::setconfig({...})` — `[sysio.synd@active]`, one per shadow pair (`ETHEREUM/LIQETH`, + `SOLANA/LIQSOL`). A pair with no row releases no syndication and accepts no desyndication: + +| Field | Value | Notes | +|---|---|---| +| `synd_fee_bps` / `desynd_fee_bps` | `0` / `0` | **(cluster; production: real fees)** | +| `synd_burst` / `synd_refill` | `1000000000000000` / same | one million tokens at 9 decimals per bucket and per-epoch refill **(cluster)** | +| `desynd_burst` / `desynd_refill` | `1000000000000000` / same | likewise | +| `window_sec` | `60` | challenge window before `sysio.bond::approve` can succeed **(cluster; production: longer)** | +| `bounty` | `0` | bounty posted on each envelope's `sysio.bond` request | +| `challenge_extra` | `1000000000` | one token charged to a challenger on top of the hold bond (the contract refuses `0`) | + +The tooling then verifies that every shadow symbol is at the depot frame's precision and that each +`(chain, token)` pair is an active `TOKEN_KIND_LIQ` token with an active binding (otherwise `sysio.msgch` +drops every `SYNDICATE_LIQ` of the pair). +38. **Mock data — opt-in only, never production.** Both run inside the epoch-0 window: + - `--enable-mock-liq-pools`: `sysio.liq::regliqpool` × 2 — `[sysio.liq@active]` — a `LIQETHP` / `LIQSOLP` + yield pool per shadow on `sysio.swap`, each seeded with `10000000000` shadow and `10000000000` WIRE base + units, fee `30`, 30 s conversion horizon, 3000 bps depth cap, clip floor `1000`. + - `--enable-mock-syndication-import`: `sysio.synd::importsynd` × 2 — one parked `100000000000` (100-token) + position each of LIQSOL (ED key) and LIQETH (EM key) for an unlinked mock bonder — then + `sysio.synd::importdone`, and a verify that both are parked with no custody mismatch. + - With either flag, outpost custody is then funded (Solana pool ATA, Ethereum `SyndicationPool`) to back + all outstanding mock shadow. ## Stage 12 — Operator provisioning + first epoch The genesis-replacing real producer schedule is already live. NOTHING here uses `forcereg`. -31. **Operator accounts (node-owner-created)** — `sysio.roa::newuser({creator:"wireno", nonce: