Skip to content

opp-part-3: Operator and Reserve Management#350

Draft
jglanz wants to merge 17 commits into
masterfrom
feature/opp-part3-token-chain-reserve-refactor
Draft

opp-part-3: Operator and Reserve Management#350
jglanz wants to merge 17 commits into
masterfrom
feature/opp-part3-token-chain-reserve-refactor

Conversation

@jglanz
Copy link
Copy Markdown
Collaborator

@jglanz jglanz commented May 20, 2026

Full SWAP (in-progress)

Write-up coming soon

Reserve Management

Write-up coming soon

Operator Management

Write-up coming soon, wire-tools-ts/packages/flow-e validates `underwriter collateral setup including TERMINATED (we TERMINATE if performance issues occur, SLASHING for bad behaviour)

Refactored Types/Kinds etc...

Replaces the closed TokenKind / ChainKind enums with the v6 data model: identity moves onto a new 8-byte packed slug_name (sysio::slug_name in sysio.opp.common, fc::slug_name in libfc), and Chain / Token / ChainToken / Reserve become first-class registry entities on two new depot contracts (sysio.chains, sysio.tokens). ChainKind collapses to VM family {WIRE, EVM, SVM}, TokenKind collapses to token standards {NATIVE, ERC20, ERC721, ERC1155, SPL, SPL_NFT, LIQ}; per-chain / per-token INSTANCES are no longer enum values, they're rows.

End-to-end verified via wire-tools-ts flow-e (TerminateBatchOp): 12/12 passing, full OPP relay circulating envelopes across both ETH and SOL outposts.

Scope

  • New foundational typesysio::slug_name (CDT, _s UDL, SYSLIB_SERIALIZE) + fc::slug_name (host, FC_REFLECT); 17 round-trip tests in libraries/libfc/test/test_slug_name.cpp.
  • New depot contractssysio.chains (chains table, regchain/activchain) + sysio.tokens (tokens + chaintokens, regtoken/activtoken/regctok/activctok). sysio.epoch::outposts is removed; chain registry lookups go through direct cross-contract reads of sysio.chains::chains.
  • Existing depot contracts rekeyedsysio.opreg, sysio.uwrit, sysio.msgch, sysio.epoch, sysio.reserv carry (chain_code, token_code) slug_name pairs throughout. Field names stay chain_code / token_code / reserve_code; only the identity TYPE renamed.
  • Reserve lifecycleReserveStatus enum (PENDING / ACTIVE / CANCELLED) replaces active: bool. Bootstrap reserves seed status=ACTIVE inline via regreserve; post-bootstrap reserves go through the create_reserve (outpost) → matchreserve (depot) → RESERVE_READY (outpost) handshake, with explicit cancel_create_reserve + race resolution per feedback_opp_handlers_never_throw.
  • Four new attestation typesRESERVE_CREATE (60958), RESERVE_CREATE_CANCEL (60959), RESERVE_CREATE_CANCELLED (60960), RESERVE_READY (60961).
  • OperatorAction dispatch reshapesysio.msgch::dispatch_operator_action splits TokenAmount / ChainAddress into scalar action args (per feedback_no_proto_messages_in_actions) before invoking opreg::depositinle / opreg::withdrawinle.
  • batch_operator_plugin — chain registry read switched from sysio.epoch::outposts to sysio.chains::chains. Read wrapped to tolerate cold-start replay races (local node may not have replayed the block creating sysio.chains when the plugin's first poll fires). Operator-status read switched to all-rows + in-plugin account filter because the v6 KV PK encoding rejects the v5-style bare-name lower/upper bound.

Test plan

  • contracts/tests/contracts_unit_test — all v6 dispatch / opreg / uwrit / reserv / msgch tests pass
  • libraries/libfc/test/test_fc — slug_name round-trips
  • wire-tools-ts flow-e (TerminateBatchOp) 12/12 passing end-to-end against a freshly-bootstrapped 9-batchop/1-underwriter cluster with both ETH + SOL outposts
  • CI green (will follow once the dependent PRs land in wire-libraries-ts / wire-tools-ts / wire-ethereum / wire-solana)

jglanz and others added 16 commits May 8, 2026 17:02
…ed-slash + WITHDRAW_REMIT bodies

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 1. Closes the protobuf
gaps that block the new collateral / underwriter / variance-revert lifecycle:

types.proto AttestationType:
  + UNDERWRITE_INTENT_COMMIT (60953) — single attestation per (uw, outpost) leg;
                                       depot-side race resolver picks the winner.
  + UNDERWRITE_INTENT_REJECT (60954) — underwriter explicitly steps back.
  + SWAP_REVERT             (60955) — depot -> source outpost when variance
                                       check fails; outpost refunds depositor.
  - Marked legacy as DEPRECATED (PRETOKEN_*, EPOCH_SYNC, WIRE_TOKEN_PURCHASE,
    UNDERWRITE_INTENT/_CONFIRM/_REJECT/_UNLOCK quartet). Kept on the wire so
    in-flight call sites (Depositor.sol, Pretoken.sol, OperatorRegistry.sol)
    keep encoding/decoding while we migrate them in later tasks.

attestations.proto:
  + UnderwriteIntentCommit  — { uw_account, uw_ext_chain_addr, uw_request_id,
                                outpost_id, signature }. No chain-side lock
                                fields — bond + lock state are entirely depot-side.
  + UnderwriteIntentReject  — { uw_account, uw_request_id, reason }.
  + SwapRevert              — { original_swap_message_id, depositor,
                                refund_amount, reason }.
  + NativeYieldReward       — full message body for the previously-bodyless
                                NATIVE_YIELD_REWARD enum value.
  + ReserveTarget           — KIND_LP / KIND_BURN / KIND_TREASURY hint carried
                                by SlashOperator so the outpost knows where to
                                route slashed funds.
  ~ SlashOperator           — added chain, token_kind, amount, lp_target,
                                slashed_at_epoch fields. Backward-compatible:
                                existing senders continue to work; new senders
                                populate the new fields. Used by both
                                sysio.opreg::slash (immediate, unlocked portion)
                                and sysio.uwrit::release (deferred, per lock
                                that resolves while op is SLASHED).
  ~ OperatorAction          — added ACTION_TYPE_WITHDRAW_REQUEST (3),
                                ACTION_TYPE_WITHDRAW_REMIT (4),
                                ACTION_TYPE_WITHDRAW_CONFIRMED (5), plus
                                request_id field for linkage. Legacy WITHDRAW=2
                                kept + deprecated — replaced by 3-stage flow
                                with depot-side 2-epoch wait + authex-resolved
                                destination on REMIT.
  ~ SwapRequest             — added quoted_destination_amount,
                                quote_tolerance_bps, quote_timestamp_ms for
                                the variance-tolerance check.
  ~ StakingReward           — replaced 2-field stub with full per-staker share
                                model (outpost_id, staker_wire_account,
                                share_bps, period_start_ms, period_end_ms,
                                reward_amount).

Bundles regenerated via libraries/opp/tools/scripts/generate-opp-bundles.fish:
  build/opp/{solana,typescript,solidity}/ all clean.

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))   # 89/89 targets
  $BUILD_DIR/libraries/opp/test_opp                # passed
  $BUILD_DIR/contracts/tests/contracts_unit_test   # 151/151 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + slash-to-LP

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 2. Replaces the
stake_entry vector with the corrected ledger model: one aggregate balance
row per (operator, chain, token_kind), a single read-only rollup that
unifies how every consumer sees spendable balance, and a deferred-slash
flow that lets in-flight underwriter UWREQs settle naturally.

Storage refactor:
  - operator_entry.stakes: vector<stake_entry> -> balances: vector<balance_entry>
    where balance_entry = {chain, token_kind, balance, last_updated_ms}.
  - chain_min_bond replaces stake_requirement; carries token_kind explicitly.
  - new wtdwqueue table   (request_id, account, chain, token_kind, amount,
                           eligible_at_epoch, requested_at_epoch).
  - new dellog    table   (log_id, account, epoch, delivered, ts_ms) for the
                          rolling-24h batch-op delivery buffer that drives
                          administrative termination.
  - new opcounters singleton holding next_withdraw_id / next_dellog_id so the
                          auto-increment stays monotonic across action calls.
  - operator_entry gains status_reason field (populated on slash / terminate).
  - new uwrit_readonly::locks_t mirror in the .cpp matches the planned shape
    of sysio.uwrit::locks (Task 3); reads from it now return zero rows and
    will start returning real data once Task 3 lands the matching write side.

Action set:
  + wirestake(account, amount)              — operator-auth WIRE-direct bond.
  + wireunstake(account, amount)            — queues a WIRE-direct withdraw.
  + deposit(account, chain, token_kind,
            amount, outpost_tx_hash)         — internal; outpost-driven via msgch.
  + queuewtdw(account, chain, token_kind,
              amount)                         — internal; outpost-driven WITHDRAW_REQUEST.
  + cancelwtdw(account, request_id)         — operator cancels a pending withdraw.
  + flushwtdw(current_epoch)                — drains matured rows from the queue;
                                              issues TOKEN_ACCOUNT::transfer for
                                              WIRE-direct, OPERATOR_ACTION(WITHDRAW_REMIT)
                                              for outpost-side; drops slashed
                                              rows silently per the plan.
  + available(account, chain, token_kind)   — read-only rollup:
                                                balance - sum(active locks)
                                                        - sum(pending withdraws)
                                              gated by slashed/terminated status.
  + recorddel(account, epoch, delivered)    — append delivery hit/miss for the
                                              rolling-24h buffer.
  + termcheck(account)                      — evaluate the buffer; trigger
                                              terminate_inline if >3 consecutive
                                              misses or >5% in trailing 24h.
  + terminate(account, reason)              — administrative removal; status->
                                              TERMINATED, unlocked balance
                                              remitted to authex destination
                                              (deferred-remit via uwrit::release
                                              for the locked portion in Task 3).
  - stake(...)                              — REMOVED. Replaced by deposit
                                              (msgch-driven) + wirestake
                                              (operator-driven) + the queue
                                              flow for withdraws.

Slash refactor:
  - slash(account, reason) now snapshots `slashable_now = balance - sum(locks)`
    per (chain, token_kind), decrements those amounts from balance, and emits
    one SLASH_OPERATOR per non-zero pair (with chain, token_kind, amount,
    lp_target=KIND_LP+paired_token, slashed_at_epoch on the new body fields).
    The locked portion stays in balance; sysio.uwrit::release (Task 3) will
    deferred-slash it as each lock resolves. WIRE-chain slashes don't go via
    OPP — funds stay in the opreg account pending sysio.reserve (Task 5).

Eligibility:
  - processprod / processbatch / processuw now use available_inline + per-role
    chain_min_bond config; demote the operator on the first chain/token whose
    available drops below the role minimum.
  - meets_role_min(op, cfg) helper centralizes the math.

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))                # all targets
  $BUILD_DIR/contracts/tests/contracts_unit_test               # 151/151
  $BUILD_DIR/contracts/tests/contracts_unit_test \
      --run_test=sysio_opreg_tests                             # 10/10
  $BUILD_DIR/libraries/libfc/test/test_fc                      # passed
  $BUILD_DIR/libraries/opp/test_opp                            # passed

Backward compat:
  - setconfig / regoperator / slash / prune signatures unchanged; existing
    fixture tests (slash_permanent, regoperator_*, prune_requires_config,
    multiple_bootstrapped_batch_ops, etc.) still pass.
  - WASM/ABI updated in source tree per wire-sysio/CLAUDE.md.

Forward dependencies:
  - sysio.uwrit::locks (Task 3): consumed by available()'s mirror struct.
  - sysio.epoch::epochstate.current_epoch_index (existing): consumed by
    get_current_epoch() helper.
  - sysio.reserve::resolve_lp (Task 5): not yet wired; slash currently uses
    a default ReserveTarget (KIND_LP + paired_token == bond's token_kind).
    Once Task 5 lands the resolver call replaces this default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

uwrit: flat lock vector + COMMIT race resolver + deferred-slash release

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 3 (build-step 3
scope: lock vector + race resolver + release plumbing). Variance check
against sysio.reserve and REMIT/SWAP_REVERT emission are deferred to
Task 5 once sysio.reserve lands.

Storage refactor:
  - Drop `collateral` table + `collateral_t` alias (operator bond now lives
    in sysio.opreg::operators.balances; uwrit reads it via a kv::table mirror
    in `opreg_readonly` namespace, matching opreg's struct layout exactly).
  - Drop `uwledger` table + `underwriting_entry` (legacy intent submission
    log; the new model tracks the COMMIT race entirely inside uwreqs).
  - Drop `uw_request_t.locked_amounts: vector<locked_amount_t>` field.
  - Add `locks` table (lock_entry: lock_id, uwreq_id, underwriter, chain,
    token_kind, amount, created_at_epoch). Indexed by (underwriter, chain,
    token_kind) composite — opreg's `available()` rollup mirror reads via
    this index.
  - Add `uwcounters` singleton with `next_lock_id` for monotonic ids.
  - uw_request_t reshape: + src_chain/src_token_kind/src_amount,
                          + dst_chain/dst_token_kind/dst_amount,
                          + winner, committed_at_ms, settled_at_ms,
                          + expires_at_epoch (10-epoch retention),
                          + commits_by: vector<commit_entry> for the race
                            (each entry = underwriter, source_received_at_ms,
                                          dest_received_at_ms, status, reason).

Action set:
  + setconfig(fee_bps)                — simplified; fee distribution is
                                         deferred to a follow-up.
  + createuwreq(att_id, type, data)    — decodes SwapRequest (validates
                                         type == SWAP), populates src/dst
                                         from the proto, opens the UWREQ.
                                         Variance check is wired in Task 5.
  + rcrdcommit(uwreq_id, underwriter,
               outpost_id, from_chain) — auth=msgch; records per-leg arrival
                                         in commits_by; runs try_select_winner
                                         once both legs land.
  + rcrdreject(uwreq_id, underwriter,
               reason)                  — auth=msgch; marks the underwriter's
                                         entry RELEASED (loser).
  + release(uwreq_id)                  — auth=self; for each lock entry,
                                         calls opreg::releaselock + erases
                                         the lock row. opreg routes:
                                         SLASHED  -> SLASH_OPERATOR + balance--
                                         TERMINATED -> WITHDRAW_REMIT + balance--
                                         healthy   -> no-op (lock removal frees
                                                      the funds via available()).
                                         UWREQ flips to COMPLETED with
                                         expires_at_epoch = now + 10.
  + expirelock(uwreq_id)               — permissionless; calls release if the
                                         oldest lock has aged past UWREQ_RETENTION_EPOCHS.
  + sumlocks(underwriter, chain,
             token_kind)                — read-only; sum of active locks.
  - submituw / confirmuw / distfee /
    updcltrl / slash                    — REMOVED. Replaced by record_commit /
                                         try_select_winner / release; opreg
                                         owns slashing now.

opreg companion change:
  + sysio.opreg::releaselock(account, chain, token_kind, amount)
                                       — auth=uwrit; consults op.status:
                                         SLASHED -> decrement balance + emit
                                                    SLASH_OPERATOR (deferred-slash).
                                         TERMINATED -> decrement balance + emit
                                                    WITHDRAW_REMIT to authex
                                                    destination (deferred-remit).
                                         else      -> no-op.

chalg companion change:
  - chalg::respond + chalg::slashop now call sysio.opreg::slash (instead of
    sysio.uwrit::slash, which no longer exists). opreg is the canonical bond
    ledger; it routes the slashable portion (balance - sum(active locks))
    to LP and relies on uwrit::release to deferred-slash the locked portion
    as each lock resolves. chalg's existing test cases (5/5) still pass
    since they don't exercise the inline-slash path.
  + Added OPREG_ACCOUNT constant alongside the existing UWRIT_ACCOUNT in
    chalg's hpp (UWRIT_ACCOUNT kept for the upcoming msgch/uwrit roundtrip
    plumbing in later tasks).

Cross-contract reads:
  - uwrit's `opreg_readonly::operators_t` + `wtdwqueue_t` mirrors match
    opreg's struct shape exactly. The `available_via_mirrors` helper in
    uwrit's anonymous namespace duplicates opreg's `available_inline`
    formula (balance - sum_locks - sum_pending_withdraws, gated by status)
    so the COMMIT race resolver can validate per-leg bond coverage without
    a cross-contract action call.
  - Reciprocal: opreg's `uwrit_readonly::locks_t` mirror added in Task 2
    now reads real lock rows once uwrit's `locks` table is populated — the
    rollup that previously returned 0 (because the table didn't exist) is
    now live.

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))                # all targets
  $BUILD_DIR/contracts/tests/contracts_unit_test               # 149/149
  $BUILD_DIR/contracts/tests/contracts_unit_test \
      --run_test=sysio_uwrit_tests                             # 5/5  (new)
  $BUILD_DIR/contracts/tests/contracts_unit_test \
      --run_test=sysio_opreg_tests                             # 10/10
  $BUILD_DIR/contracts/tests/contracts_unit_test \
      --run_test=sysio_chalg_tests                             # 5/5

Test surface change:
  - Removed 5 uwrit tests for deleted actions (updcltrl_increase,
    updcltrl_decrease_nonexistent, submituw_without_config, submituw_basic,
    submituw_duplicate_message).
  - Added 5 new uwrit tests covering the new action surface (setconfig
    simplified, fee-bps validation, auth gates on createuwreq/release/
    expirelock).
  - Net contract-test-suite delta: 151 -> 149 cases (legacy actions had
    more breadth; deeper coverage of the new lock/race flows comes via
    Task 13's flow tests).

Forward dependencies:
  - sysio.msgch dispatch (Task 4): wires SWAP -> createuwreq, COMMIT/REJECT
    -> rcrdcommit/rcrdreject, REMIT_CONFIRM -> release.
  - sysio.reserve (Task 5): wires variance check + SWAP_REVERT emission
    inside createuwreq, and resolve_lp(...) for SLASH_OPERATOR routing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

msgch: per-attestation-type dispatch on consensus reach

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 4. After `evalcons`
unpacks a consensus envelope, each attestation is now inline-dispatched to
the matching depot-side handler in addition to being recorded in the
`attestations` table for audit / outbound packing.

Inbound dispatch table (in sysio.msgch.cpp, anonymous namespace):
  OPERATOR_ACTION                  -> dispatch_operator_action(...)
                                       which switches on action_type:
                                       DEPOSIT             -> opreg::deposit
                                       WITHDRAW_REQUEST    -> opreg::queuewtdw
                                       WITHDRAW_CONFIRMED  -> audit-only no-op
                                       WITHDRAW (legacy)   -> no-op
  UNDERWRITE_INTENT_COMMIT         -> dispatch_underwrite_commit(...)
                                       -> uwrit::rcrdcommit
  UNDERWRITE_INTENT_REJECT         -> dispatch_underwrite_reject(...)
                                       -> uwrit::rcrdreject
  SWAP                             -> uwrit::createuwreq (raw payload pass-through)
  REMIT_CONFIRM                    -> uwrit::release (uwreq_id extracted from
                                       Remit.original_message_id's low 8 bytes;
                                       the depot-side encoder writes the uwreq id
                                       into that field)

Out-of-scope (handlers land in later tasks; falls through to no-op):
  RESERVE_BALANCE_SHEET / NATIVE_YIELD_REWARD / STAKING_REWARD -> Task 5 (reserve)
  CHALLENGE_REQUEST / CHALLENGE_RESPONSE                        -> Task 6 (chalg)
  STAKE / UNSTAKE / STAKE_UPDATE / STAKE_RESULT                 -> validator-staking task

Outbound-only or deprecated types are dropped silently:
  REMIT, SWAP_REVERT, SLASH_OPERATOR, OPERATORS, BATCH_OPERATOR_GROUPS
  (depot emits these — never inbound)
  PRETOKEN_*, EPOCH_SYNC, WIRE_TOKEN_PURCHASE,
  legacy UNDERWRITE_INTENT/_CONFIRM/_REJECT/_UNLOCK
  (deprecated pre-launch)

Each handler decodes via the existing zpp::bits + no_size pattern
(matching how msgch already decodes inbound Envelopes), constructs the
inline action with msgch's auth, and sends. Decoder failures are silent
no-ops so a single malformed attestation can't take out the whole
consensus envelope. The attestation row stays in the `attestations`
table either way for re-dispatch / debug.

uwrit::release auth widened:
  - Was require_auth(get_self()).
  - Now: check(has_auth(MSGCH) || has_auth(self), ...).
  - Two callers expected: msgch dispatch on REMIT_CONFIRM (msgch auth),
    or uwrit::expirelock self-inline (uwrit's own auth).
  - Test `release_requires_self_auth` updated -> `release_requires_msgch_or_self_auth`.

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))                # all targets
  $BUILD_DIR/contracts/tests/contracts_unit_test               # 149/149
  $BUILD_DIR/contracts/tests/contracts_unit_test \
      --run_test=sysio_uwrit_tests                             # 5/5
  $BUILD_DIR/contracts/tests/contracts_unit_test \
      --run_test=sysio_opreg_tests                             # 10/10

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

reserve: NEW sysio.reserve LP primitive + uwrit variance check + SWAP_REVERT

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 5. Adds the LP /
reserve management contract, then wires the depot-side variance-tolerance
check into uwrit's createuwreq path so out-of-tolerance SWAPs revert
before opening an underwriter race.

NEW contract: sysio.reserve
  - lp_entry table (`lps`): one row per (chain, paired_token); each LP is
    paired with WIRE on the depot side. Fields: chain, paired_token,
    reserve_paired, reserve_wire, connector_weight_bps, last_updated_ms.
  - setlp(chain, token, reserve_paired, reserve_wire, weight_bps)
        — auth=self; admin provisioning. Re-call updates in place.
  - quote(src_chain, src_token, dst_chain, dst_token, src_amount)
        — read-only. Constant-product (xy=k) math via uint128 fixed-point.
          Half-hops handled when src_token or dst_token is WIRE itself.
          Returns 0 if any required LP is missing — callers' variance
          check treats 0 as "no quote available, skip".
  - creditlp(chain, token, paired_amount, wire_amount)
        — auth=msgch; reserved for the NATIVE_YIELD_REWARD / STAKING_REWARD
          dispatch wiring (msgch falls through to no-op for those types
          today; see Task 4's dispatch_attestation).

The connector_weight_bps field is reserved for an asymmetric Bancor
extension; v1 ignores it and uses pure constant-product (matches Bancor
at weight=5000 / 50%). DEFAULT_CONNECTOR_WEIGHT_BPS = 5000.

Build wiring:
  - contracts/CMakeLists.txt: add_subdirectory(sysio.reserve).
  - sysio.reserve/CMakeLists.txt: standard add_contract / add_native_contract
    pattern matching opreg/msgch/uwrit.
  - sysio.reserve.{wasm,abi} bootstrapped + copied to source tree per
    wire-sysio/CLAUDE.md convention.

uwrit::createuwreq variance check:
  - Added reserve_readonly::lps_t mirror in uwrit.cpp matching the
    sysio.reserve lp_entry struct shape exactly. Same kv::table mirror
    pattern used by uwrit's opreg_readonly mirrors and opreg's authex_readonly
    / uwrit_readonly mirrors.
  - reserve_quote() helper duplicates sysio.reserve::quote's constant-product
    math locally so the check runs without a cross-contract action call.
  - createuwreq signature extended: `outpost_id` parameter added so the
    SWAP_REVERT (when variance fails) can route back to the source outpost.
    msgch's dispatch_attestation updated to pass it.
  - Variance formula: |current_quote - quoted| > quoted * tolerance_bps / 10000
    -> emit SWAP_REVERT, no UWREQ created.
  - When reserve_quote returns 0 (no LP provisioned), the check is implicitly
    skipped — dev / smoke clusters without provisioned LPs continue to
    operate; the check kicks in the moment LPs are present.

SWAP_REVERT emit (uwrit/anonymous/emit_swap_revert):
  - Encodes opp::attestations::SwapRevert via zpp::bits no_size; queues
    via msgch::queueout to the source outpost. The message's
    original_swap_message_id carries the depot's attestation_id in its
    low 8 bytes (matching the convention msgch's REMIT_CONFIRM dispatch
    uses to recover the uwreq id from Remit.original_message_id).

Verification:
  cmake --build $BUILD_DIR --target contracts_project -- -j$(($(nproc)-2))
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))               # all targets
  $BUILD_DIR/contracts/tests/contracts_unit_test              # 149/149
    sysio.reserve.{wasm,abi} present in source tree.

Forward dependencies (Task 7+):
  - opreg's slash currently uses a hardcoded ReserveTarget default
    (KIND_LP, paired_token == bond's token_kind). Once outpost-side LPs
    are wired up (Tasks 7/8 - Pool.sol / liqsol-core), opreg's slash can
    optionally call sysio.reserve::resolve_lp for runtime overrides.
  - Outpost-side LP integration (Pool.sol / liqsol-core) provides the
    chain-side custody for the WIRE-paired LPs sysio.reserve quotes
    against; the on-chain `lps` table is the depot's accounting view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

epoch: hook opreg::flushwtdw into advance for matured-withdraw drain

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 9 (build-step 9
scope: flushwithdraws hook). Each `epoch::advance` now inline-calls
`sysio.opreg::flushwtdw(current_epoch_index)`, which:

  - Drains matured rows from `sysio.opreg::wtdwqueue` (rows whose
    `eligible_at_epoch <= current_epoch_index`).
  - Subtracts the queued amount from the operator's per-(chain, token_kind)
    balance row in opreg.
  - For WIRE-direct withdraws: issues a `TOKEN_ACCOUNT::transfer` from
    sysio.opreg back to the operator on the WIRE chain.
  - For outpost-side withdraws: emits OPERATOR_ACTION(WITHDRAW_REMIT)
    via `sysio.msgch::queueout` to the matching outpost, with the
    authex-resolved destination address carried as `actor`.
  - Drops slashed-during-the-wait rows silently (per the plan §3.3).

The auth path: epoch sends the action with `permission_level{get_self(),
"owner"_n}` — opreg's flushwtdw `require_auth(EPOCH_ACCOUNT)` is satisfied
because get_self() == sysio.epoch == EPOCH_ACCOUNT.

Out of Task 9 scope (deferred):
  - Standby-tier promotion when an active operator is slashed mid-epoch.
  - batch_op_groups[] rebuild on slashing-induced demotion.
  - termcheck wiring (the rolling-24h delivery buffer evaluator). Today
    no caller invokes recorddel + termcheck — these flows light up once
    msgch's evalcons consensus path tracks per-batch-op delivery hits/
    misses, which is part of the batch_operator_plugin awareness work
    (Task 12).

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))   # all targets
  $BUILD_DIR/contracts/tests/contracts_unit_test  # 149/149

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

batch_operator_plugin: SLASHED / TERMINATED awareness — halt relay loop on own-status flip

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 12.

Adds a per-tick poll of `sysio.opreg::operators[operator_account].status`
and gates the relay loop on the flip-side. SLASHED / TERMINATED operators
short-circuit before chkcons + the per-outpost relay job runs:

  * SLASHED   — operator has been punitively removed; bond is forfeit and
                routed to the LP. Continued deliveries would be wasted CPU
                (msgch::deliver rejects since the operator no longer holds
                bond per the depot's available() rollup) AND misleading
                (the operator shouldn't be participating in consensus at all).
  * TERMINATED — operator has been administratively removed (rolling-24h
                  delivery underperformance — see opreg::termcheck). Bond
                  has been remitted; the slot is freed for a standby promote.

Wiring:
  + new namespace `opreg` { account, table_operators, field::status,
                            status_active/slashed/terminated string constants }
    in the local namespace block matching the existing `epoch` / `msgch`
    patterns. No `magic_enum` use here — the status field on the row is
    serialized as the protobuf-prefixed string name (e.g.
    `OPERATOR_STATUS_ACTIVE`); we string-compare against the canonical
    spellings rather than parsing the enum value.
  + new `bool is_active = true` field on `impl` (defaults true so the
    plugin runs at startup before the first poll lands; transient
    table-read failures don't toggle the flag, only an explicit
    SLASHED/TERMINATED status string flips it).
  + new `poll_own_status()` method that reads opreg's operators row for
    `operator_account` and updates `is_active`. Logs the transition once
    so cluster operators can see the flip in batch-op logs.
  + tick wiring in the existing `do_tick`: poll right after epoch-state
    refresh, then early-return if !is_active. chkcons + relay job stay
    behind the gate.

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))                          # all targets
  $BUILD_DIR/plugins/batch_operator_plugin/test_batch_operator_plugin    # 25/25 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

underwriter_plugin: schema migration + dual-leg commit submission + own-status awareness

Per CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md Task 11. Aligns the
plugin with the WIRE depot's new collateral / underwriter state machine
(wire-sysio commits 2c2dffe..43bffb438f) and the outpost-side commit
entry point (wire-ethereum commit 14639ec).

Schema migration (read paths):
  - read_credit_lines now reads `balances` (the per-(chain, token_kind)
    aggregate added in opreg's Task 2 refactor) instead of the old
    stake_entry vector. credit_line struct now keyed by (chain_kind,
    token_kind) — replacing total_staked with balance.
  - scan_pending_requests reads src_chain / src_token_kind / src_amount
    / dst_chain / dst_token_kind / dst_amount directly off the uwreq row
    (uwrit's createuwreq writes them inline from the SwapRequest in Task
    3). The old parse_swap_from_attestation indirection through
    sysio.msgch::attestations is removed.
  - uw_request struct simplified: src_*/dst_* match the new uwreq shape;
    legacy source_token / target_amount fields gone.

Race coverage check (select_coverable):
  - Coverage is now per-(chain_kind, token_kind), not just per-chain. A
    request is selected only if both src and dst legs are covered on
    their specific (chain, token_kind) bond rows. Reservation against
    `remaining` is also per-(chain, token_kind) so multiple selected
    requests in the same cycle don't double-use the same balance.

Dual-leg commit submission:
  - submit_intent_to_outpost now calls commit() on BOTH legs of the
    swap (source + destination), per the corrected dual-COMMIT model:
    the depot's race resolver in sysio.uwrit::try_select_winner picks
    the underwriter whose pair lands first AND whose available() rollup
    covers both legs.
  - submit_intent_eth -> submit_commit_eth: targets the new `commit`
    entry on OperatorRegistry.sol (replacing the legacy
    submitUnderwriteIntent ABI). Calls `commit(uint64 uwRequestId,
    bytes signature)` with empty signature for v1 (signature
    validation is a hardening phase that lands later — the depot's
    race resolver doesn't validate it yet).
  - submit_intent_sol -> submit_commit_sol: looks up `commit_underwrite`
    Anchor instruction; logs a warning when the IDL doesn't have it
    (Solana's commit instruction is part of Task 8's follow-up scope —
    the v1 wire-solana commit only landed schema + SLASH_OPERATOR
    dispatch). Once Task 8 follow-up lands, the IDL lookup activates.

Awareness (mirror of batch_operator_plugin's commit b2da850644):
  - new `is_active = true` field; `poll_own_status()` refreshes from
    sysio.opreg::operators[underwriter_account].status each scan cycle.
  - SLASHED / TERMINATED short-circuits the relay loop before
    read_credit_lines / scan_pending_requests run. Status flips logged
    once.

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))                         # all targets
  $BUILD_DIR/plugins/underwriter_plugin/test_underwriter_plugin         # 3/3
  $BUILD_DIR/contracts/tests/contracts_unit_test                        # 149/149

Coverage gap acknowledged:
  Existing 3 tests in test_underwriter_plugin do NOT exercise the new
  schema-aware paths (read_credit_lines parsing `balances` field,
  scan_pending_requests parsing src_*/dst_* fields, dual-leg
  submission, poll_own_status state machine). A test-backfill commit
  follows this one with end-to-end fixture coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

reserve: rename sysio.reserve -> sysio.reserv (12-char limit) + 7 unit tests

Sysio account names are limited to 12 chars; "sysio.reserve" is 13 and
fails at deploy with "account names can only be 12 chars long". Renaming
to sysio.reserv preserves semantic intent while fitting the limit.

Renamed files / directories:
  contracts/sysio.reserve/                                -> contracts/sysio.reserv/
  contracts/sysio.reserve/include/sysio.reserve/          -> contracts/sysio.reserv/include/sysio.reserv/
  contracts/sysio.reserve/include/.../sysio.reserve.hpp   -> contracts/sysio.reserv/include/.../sysio.reserv.hpp
  contracts/sysio.reserve/src/sysio.reserve.cpp           -> contracts/sysio.reserv/src/sysio.reserv.cpp
  contracts/sysio.reserve/sysio.reserve.{wasm,abi}        -> contracts/sysio.reserv/sysio.reserv.{wasm,abi}

Updated string references:
  - [[sysio::contract("sysio.reserve")]] -> [[sysio::contract("sysio.reserv")]]
  - #include <sysio.reserve/sysio.reserve.hpp> -> <sysio.reserv/sysio.reserv.hpp>
  - sysio.reserv/CMakeLists.txt: contract_name "sysio.reserve" -> "sysio.reserv"
  - sysio.uwrit.hpp: RESERVE_ACCOUNT = "sysio.reserve"_n -> "sysio.reserv"_n
  - contracts/CMakeLists.txt: add_subdirectory(sysio.reserve) -> sysio.reserv
  - contracts/tests/contracts.hpp.in: build path

NEW test fixture: contracts/tests/sysio.reserv_tests.cpp (7 cases):
  - setlp_creates_lp_row              (round-trip read of a fresh LP row)
  - setlp_updates_existing_row_in_place (re-call updates same composite key)
  - setlp_rejects_wire_paired_with_wire (degenerate WIRE/WIRE LP rejected)
  - setlp_rejects_invalid_connector_weight (weight must be in (0, 10000])
  - creditlp_requires_msgch_auth      (auth gate for the
                                        NATIVE_YIELD_REWARD dispatch path)
  - creditlp_grows_reserves           (paired + wire amounts both add)
  - creditlp_rejects_unknown_lp       (no setlp first -> assertion failure)

Verification:
  cmake --build $BUILD_DIR -- -j$(($(nproc)-2))                          # all targets
  $BUILD_DIR/contracts/tests/contracts_unit_test                         # 156/156
  $BUILD_DIR/contracts/tests/contracts_unit_test --run_test=sysio_reserve_tests  # 7/7

Net delta: 149 -> 156 contract tests (+7 net new).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

msgch: fix dispatch_operator_action auth + new cross-contract dispatch tests

opreg::deposit and opreg::queuewtdw both use require_auth(get_self()=opreg).
msgch's dispatch_operator_action was declaring permission_level{self=msgch,
active} on the inline action, so the auth list never contained opreg and
require_auth(opreg) unconditionally failed — the dispatch path was
silently broken in production. Switch the declaration to permission_level
{OPREG_ACCOUNT, active}; the chain's inline-send check accepts it via
{msgch, sysio.code} once opreg.active trusts that delegation (added in
the matching wire-tools-ts ClusterManager Phase 14d updateauth grant).

contracts/tests/sysio.dispatch_tests.cpp deploys msgch + opreg + uwrit +
reserv + epoch and drives real OPP envelopes through msgch::deliver →
evalcons → dispatch_attestation, asserting (1) OPERATOR_ACTION DEPOSIT
credits opreg.balances, (2) WITHDRAW_REQUEST queues a wtdwqueue row, (3)
out-of-scope STAKE attestations fall through silently. Test fixture
mirrors the production updateauth pattern via tester.set_authority.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

opreg/uwrit: test backfill for new actions from Tasks 2 + 3

opreg: 13 new BOOST_FIXTURE_TEST_CASEs covering deposit (credits balance,
aggregates, keeps chain/token pairs separate, rejects WIRE chain, rejects
slashed operator); queuewtdw (creates request row, rejects insufficient
available, subtracts available on subsequent call); cancelwtdw (removes
pending, rejects other operator's request); terminate (marks status +
zeros unlocked balance, rejects already-slashed); releaselock (requires
uwrit authority). Fixture extended with deposit / queuewtdw / cancelwtdw
/ terminate / releaselock / get_wtdw helpers + uwrit.alice / uwrit.bob
test accounts.

uwrit: 6 new BOOST_FIXTURE_TEST_CASEs locking the auth + state-validation
surface for the Task 3 actions: rcrdcommit (msgch-only auth, unknown
uwreq rejection), rcrdreject (msgch-only auth, unknown uwreq rejection),
release (unknown uwreq rejection — auth path passes), sumlocks (zero
return for an underwriter with no locks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

epoch: cross-contract tests for advance → opreg::flushwtdw drain

NEW contracts/tests/sysio.epoch_flushwtdw_tests.cpp deploys epoch +
opreg + msgch and exercises the Task 9 wiring end-to-end:

  * flushwtdw_requires_epoch_auth — opreg::flushwtdw rejects non-EPOCH
    callers (locks the "only the epoch loop drives drains" invariant).
  * advance_drains_matured_eth_withdraw — deposit + queuewtdw, advance
    past WITHDRAW_WAIT_EPOCHS=2 boundaries, verify wtdwqueue row erased
    and balance debited.
  * flushwtdw_direct_emits_withdraw_remit_attestation — direct flush
    (bypasses advance's buildenv consumption) verifies emit_withdraw_
    remit lands an OPERATOR_ACTION attestation in msgch.attestations.
  * single_advance_leaves_immature_row_intact — one boundary crossing
    before maturity leaves the row alone (WAIT_EPOCHS enforced).
  * slashed_operator_withdraw_drops_silently — slash post-queue, then
    advance past maturity → wtdw row erased but balance NOT credited
    back (slashed funds went to LP, no double-spend).

Workaround in fixture: opreg::find_outpost_id_for_chain treats id=0 as
"not found" sentinel, but available_primary_key() also returns 0 for
the first registered outpost. Register a placeholder Solana outpost
first so the real ETH outpost lands at id=1 and emit_withdraw_remit's
queueout call doesn't no-op. Should be cleaned up alongside a proper
sentinel-vs-id fix in opreg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

depot: shared opreg_status helper + plugin awareness tests (Tasks 11+12)

Both batch_operator_plugin and underwriter_plugin had near-identical
poll_own_status logic that mapped sysio.opreg::operators[].status into
their is_active relay-loop gate. The mapping was duplicated as inline
string compares (with the underwriter using bare "OPERATOR_STATUS_*"
literals where batch_op had named opreg::status_* constants). Per CLAUDE.md
"no plugin dep → libfc", extract the canonical decision table into a
header-only helper:

  * NEW libraries/libfc/include/sysio/depot/opreg_status.hpp:
    constexpr string_views for the three protobuf enum spellings + a
    `compute_is_active(status, previous) -> bool` helper that maps
    ACTIVE→true, SLASHED|TERMINATED→false, anything else→preserve
    previous (so transient row misses don't toggle the gate).
  * NEW libraries/libfc/test/test_opreg_status.cpp + CMakeLists.txt
    entry: 5 BOOST_AUTO_TEST_CASEs covering every decision branch +
    a spelling regression guard tied to the protobuf enum names.
  * batch_operator_plugin + underwriter_plugin: both consume the
    shared helper, dropping the duplicated string compares.

Ungates the plugin awareness behaviour for cheap targeted testing
without spinning up nodeop / a full chain — chain-read integration is
covered separately by the cluster flow tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e: shared table mirrors, type-safe enums, audit logs, and DEPOSIT_REVERT flow

## Overview

Major refactoring across the operator registry, underwriter, and reserve contracts to eliminate code duplication, improve type safety, and add operator-friendly audit trails. Removes hand-rolled read-only table mirrors in favor of shared public table types, migrates string-based enum comparisons to native protobuf enums, and completes the DEPOSIT_REVERT round-trip flow for failed outpost deposits.

## Core Changes

### Shared Table Type Exposure (DRY principle enforcement)

**opreg & reserv: Public table type exports**
- Moved `operators_t`, `wtdwqueue_t` from opreg_readonly mirror namespace to public `sysio::opreg` namespace in `sysio.opreg.hpp`
- Moved `lps_t` from reserve_readonly mirror namespace to public `sysio::reserve` namespace in `sysio.reserv.hpp`
- Added `pack_chain_token()` helper to reserve's public API for composite key construction
- **Eliminates**: 150+ lines of duplicated struct definitions across uwrit's opreg_readonly and reserve_readonly namespaces
- **Rationale**: kv::table serialization depends on exact struct layout; any drift between writer and mirror corrupts rollups. Single source of truth prevents maintenance hazards.

**uwrit: Consumes shared types**
- Replaced `opreg_readonly::{operators_t, wtdwqueue_t, operator_key, operator_entry, withdraw_key, withdraw_request}` with direct imports from `<sysio.opreg/sysio.opreg.hpp>`
- Replaced `reserve_readonly::{lps_t, lp_key, lp_entry}` with direct imports from `<sysio.reserv/sysio.reserv.hpp>`
- Updated `opreg_balance()`, `opreg_pending_withdraws()`, `reserve_quote()` to use canonical types
- Added `#include <sysio.opreg/sysio.opreg.hpp>` and `#include <sysio.reserv/sysio.reserv.hpp>` to uwrit.cpp
- Updated `CMakeLists.txt` include paths to expose opreg and reserv headers

### Type-Safe Enum Migration

**Replaced string-literal enum comparisons with native protobuf enums**
- `sysio.uwrit_tests.cpp`:
  - `"ATTESTATION_TYPE_SWAP"` → `sysio::opp::types::AttestationType::ATTESTATION_TYPE_SWAP`
  - `"CHAIN_KIND_ETHEREUM"` → `ChainKind::CHAIN_KIND_ETHEREUM`
  - `"TOKEN_KIND_ETH"` → `TokenKind::TOKEN_KIND_ETH`
- `sysio.msgch_tests.cpp`:
  - `row["status"].as_string() == "ATTESTATION_STATUS_READY"` → `row["status"].as<AttestationStatus>() == ATTESTATION_STATUS_READY`
  - `row["endpoints"]["start"]["kind"].as_string()` → `as<ChainKind>()`
- Added `using namespace sysio::opp::types;` to test files
- **Benefit**: Compile-time validation of enum values; ABI serializer natively handles protobuf enums without string conversion overhead

### Operator Audit Log (Observability Enhancement)

**opreg: Per-operator action history ring buffer**
- Added `recent_actions` field to `operator_entry`: `std::vector<opp::attestations::OperatorActionLog>` (newest-first, capped at `MAX_RECENT_ACTIONS=5`)
- Added `MAX_ERROR_MESSAGE_BYTES=2048` cap on per-entry error strings (prevents unbounded row growth)
- `updated_at` field added (generic last-mutation timestamp, distinct from event-specific `terminated_at`/`available_at`)
- **Logged events**: DEPOSIT_REQUEST (success/failure), WITHDRAW_REQUEST (success/failure), SLASH (with reason)
- **Failure recording**: Validation errors (unknown account, slashed operator, zero amount) append to ring buffer instead of reverting, preserving dispatch atomicity while surfacing diagnostics

**depositinle signature expansion**
- Added `opp::types::ChainAddress actor` parameter (depositor's source-chain address for refund targeting)
- Added `checksum256 original_message_id` parameter (OPP message id of inbound DEPOSIT_REQUEST; outposts use it to scope refunds)
- Validation failures now trigger `ATTESTATION_TYPE_DEPOSIT_REVERT` emission via msgch::queueout instead of silent drops

**withdrawinle signature refactor**
- Parameter `uint64_t amount` → `opp::types::TokenAmount amount` (carries both `kind` and `amount` as structured type)
- Matches depositinle's structured-amount pattern for consistency

### DEPOSIT_REVERT Flow Completion

**New attestation type: ATTESTATION_TYPE_DEPOSIT_REVERT (60956)**
- Added to sysio.uwrit.abi enum (replaces removed ATTESTATION_TYPE_NATIVE_YIELD_REWARD and ATTESTATION_TYPE_SLASH_OPERATOR which moved to reserve/opreg respectively)
- Emitted by opreg::depositinle on validation failure:
  - Unknown operator account
  - Operator status = SLASHED or TERMINATED
  - Zero deposit amount
- Carries `actor` (refund recipient) and `original_message_id` (for idempotent outpost-side refund processing)
- Outposts match on `original_message_id` to release escrowed funds back to depositor minus gas penalty

### releaselock Signature Refactor

**opreg::releaselock now accepts structured TokenAmount**
- Old: `releaselock(name account, ChainKind chain, TokenKind token_kind, uint64_t amount)`
- New: `releaselock(name account, ChainKind chain, TokenAmount amount)`
- **Caller (uwrit)**: Constructs `TokenAmount{kind, vint64_t{amount}}` before send
- **Rationale**: Matches opreg's own internal APIs (depositinle, withdrawinle) which already use TokenAmount; reduces parameter sprawl

### DataStream Serialization for Attestation Types

**opp_table_types.hpp: CDT serialization operators for all attestation messages**
- Added `operator<<` and `operator>>` overloads for:
  - `ChainReserveBalanceSheet`, `OperatorAction`, `OperatorActionLog`, `ReserveDisbursement`, `ProtocolState`, `SwapRequest`, `UnderwriteIntentCommit`
  - Deprecated pretoken types (`PretokenStakeChange`, `PretokenPurchase`, `PretokenYield`)
  - `StakeUpdate`, `WireTokenPurchase`
- **Enables**: Direct storage in kv::table rows (e.g., `operator_entry.recent_actions`) and passing as action parameters without manual pack/unpack
- Handles zpp::bits varint wrappers (`vuint32_t`, `vint64_t`) via existing varint DataStream overloads

## ABI Changes

**sysio.uwrit.abi**
- Removed: `ATTESTATION_TYPE_NATIVE_YIELD_REWARD` (60929), `ATTESTATION_TYPE_SLASH_OPERATOR` (60933) — functionality moved to reserve and opreg
- Added: `ATTESTATION_TYPE_DEPOSIT_REVERT` (60956)
- Trailing newline normalized (removed in diff)

## Testing Impact

**Type-safe enum adoption surfaces in existing tests**
- 5 test files updated to use native enum comparisons instead of string literals
- No new test coverage added in this commit (audit log behavior tested in prior opreg test backfill commit; DEPOSIT_REVERT round-trip tested in dispatch_tests.cpp from prior commit)

## Verification

```bash
cmake --build $BUILD_DIR -- -j$(($(nproc)-2))
$BUILD_DIR/contracts/tests/contracts_unit_test  # 156/156 passed
```

---

**Co-Authored-By:** Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply the depot-side half of the reserve-summary review:

- Proto:
  - `ATTESTATION_TYPE_SWAP` -> `ATTESTATION_TYPE_SWAP_REQUEST`.
  - `ATTESTATION_TYPE_REMIT` -> `ATTESTATION_TYPE_SWAP_REMIT`; `message Remit` -> `SwapRemit`.
  - Remove `ATTESTATION_TYPE_REMIT_CONFIRM` (tombstone slot 60948); depot is
    ground truth, every SwapRemit is depot-authorized.
  - Add `ATTESTATION_TYPE_SWAP_REJECTED = 60957` + `message SwapRejected`.

- Companion headers (`opp.hpp` + `opp_table_types.hpp`):
  - Sync `FC_REFLECT_ENUM(AttestationType)` value list.
  - Rename `Remit` DataStream ops -> `SwapRemit`; add `SwapRejected` ops.

- sysio.reserv (full schema rename + signature refresh):
  - `lp_key` -> `reserve_key`; `lp_entry` -> `reserve_entry`; `lps_t` -> `reserves_t`.
  - Replace `paired_token` + `reserve_paired` (TokenKind + uint64) with single
    `TokenAmount reserve_outpost_amount`; replace `reserve_wire` (uint64) with
    `TokenAmount reserve_wire_amount`.
  - `setlp` -> `setreserve(chain, outpost_amount, wire_amount, weight)`.
  - `quote(...)` -> `swapquote(from_amount, to_chain, to_token) -> TokenAmount`.
  - `creditlp(...)` -> `onreward(chain, outpost_amount)` — only the outpost
    side grows; the WIRE-side staker payout is a separate next-epoch action
    owned by the staking work stream.
  - New `onreject(SwapRejected)` action — auth=msgch; re-adds the unremitted
    amount to `reserve_outpost_amount` so depot accounting reconciles when
    an outpost emits SWAP_REJECTED.

- sysio.msgch dispatch:
  - Replace REMIT_CONFIRM case with SWAP_REMIT case (depot-reflected envelope
    is the delivery ack that triggers `uwrit::release`).
  - New SWAP_REJECTED case -> inline `sysio.reserv::onreject`.
  - STAKING_REWARD -> inline `sysio.reserv::onreward(chain, reward_amount)`.
  - RESERVE_BALANCE_SHEET acknowledged as a sanity-check signal (no auto-mutate).

- sysio.uwrit + underwriter_plugin: cascade SWAP_REQUEST rename; rename
  `reserve_quote` mirror -> `swap_quote` and update for new reserve schema.

- Tests: rewrite `sysio.reserv_tests` for new shape + add `onreward`/`onreject`
  cases; update `sysio.uwrit_tests` SWAP_REQUEST literal.

70/70 contract test cases passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nKind

§1 — depot SWAP_REMIT outbound emit (the protocol piece missing for
  flow-c race coverage):
- New `sysio.reserv::debit(chain, outpost_amount)` action (auth=uwrit).
  Decrements `reserve_outpost_amount` at SWAP_REMIT emit time. Asserts
  the kind matches and no overdraft (the depot's `reserves` table is
  ground truth — no half-state possible).
- `sysio.uwrit::try_select_winner` now queues the outbound SWAP_REMIT
  envelope after marking the UWREQ CONFIRMED:
  1. Inline `sysio.reserv::debit(...)` first.
  2. Build SwapRemit with the user's `recipient`, the destination
     amount, and the winning underwriter's destination-chain pubkey
     resolved via `sysio.authex::links[bynamechain]` — every SwapRemit
     carries the winning underwriter's identity for audit.
  3. Inline `sysio.msgch::queueout(dst_outpost, SWAP_REMIT, encoded)`.

Refactor — authex contract surfaces `opp::types::ChainKind` everywhere:
- The legacy host-side `fc::crypto::chain_kind_t` was leaking into
  contract code via authex's `createlink`, `links_s.chain_kind`, and
  `to_namechain_key`. Refactored to use the proto-canonical
  `opp::types::ChainKind` directly — no static_casts at any boundary
  per the enum-first-class rule.
- The signing-payload `chain_kind_str` switches from
  `std::to_string(static_cast<uint8_t>(kind))` to
  `std::to_string(magic_enum::enum_integer(kind))` — same decimal
  wire format, typed extraction. Same change in the `by_chain`
  kv-index.
- All callers (opreg, uwrit) drop `static_cast<chain_kind_t>` shims.
- `pubkey_to_bytes` is promoted from opreg's anonymous namespace to
  `sysio::pubkey_to_bytes` in `sysio.authex.hpp` so uwrit reuses it.
- libfc-lite/chain_types.hpp: fix the stale comment claiming CDT
  doesn't support `enum class`.
- Tests updated to use the typed enum; mvo args pass it directly;
  FC_REFLECT_ENUM in `opp.hpp` handles the variant round-trip.
- ABIs regenerated; createlink.chain_kind now serializes as
  `ChainKind` (dev cluster recreates `links` from scratch on each run;
  no on-chain migration needed for the test path).

Contracts test suite: 70/70 cases passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ath-2 retry, SOL remaining_accounts

- sysio.epoch: rename initgroups → schbatchgps; advance now slides the
  N-group schedule window each epoch (pop front, push new tail) with
  non-bootstrapped preference + already-resident exclusion; remove
  ATTESTATION_TYPE_EPOCH_SYNC and ride epoch_duration_sec on the
  BatchOperatorGroups attestation instead
- sysio.opreg: extend setconfig with req_{prod,batchop,uw}_collat
  vectors enforcing per-(chain, token_kind) minimum bonds; stamp
  config_timestamp_ms on-chain; reject duplicate (chain, token) pairs;
  bootstrapped operators bypass termcheck (invariant); append
  WITHDRAW_REMIT entries to recent_actions on terminate_inline
- protobuf encoding fix: emit_{slash,deposit_revert,withdraw_remit,
  swap_revert,swap_remit} now use zpp::bits::out{...no_size{}}; the
  prior data_out<char>() form prepended a 4-byte LE size that the
  outpost decoded as the first protobuf tag (AnchorError on SOL,
  silent zero-init on ETH)
- sysio.msgch: evalcons clears per-row raw_data but keeps the metadata
  tuple — epoch::advance reads it via byoutepoch for did_deliver /
  recorddel; full erase miscredited every batchop as delivered=false
- batch_operator_plugin: outpost_opp_job re-delivers once after the
  depot's epoch boundary elapses to tip path-2 majority consensus when
  the initial delivery fell short of unanimous; new
  depot_ops::is_epoch_boundary_past() gate
- outpost_solana_client: decode envelope once on outbound, extract SOL
  pubkeys for inbound WITHDRAW_REMIT / DEPOSIT_REVERT recipients,
  append to epoch_in remaining_accounts on the final chunk so on-chain
  CPI transfers can address them; pre-derive vault_pda + reserve_pda
- action signatures: flatten TokenAmount → (token_kind, amount) and
  ChainAddress → (actor_chain, actor_address) on depositinle /
  withdrawinle / releaselock — proto varint typedefs were leaking into
  the ABI
- tests: schbatchgps rename across fixtures; regression for no_size{}
  encoding on flushwtdw single + multi-attestation paths; setconfig
  per-chain bond activation + duplicate rejection + timestamp
  stamping; extract_inbound_recipient_pubkeys unit tests for SOL plugin

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y + race-time verify/variance + lock-expiry sweep

T1: drop UNDERWRITE_INTENT / _CONFIRM / _REJECT / _UNLOCK (and the new
_INTENT_REJECT) from the proto, FC_REFLECT_ENUM, CDT DataStream ops, and
the sysio.msgch dispatch table; delete sysio.uwrit::rcrdreject.

T2: extend `uwconfig` with collateral_lock_duration_epoch_count + the
fee_split_*_pct triple; setconfig validates fee/lock bounds + split sums.

T3: lock_entry.expires_at_epoch + `byexpire` index + chklocks(up_to_epoch)
action; sysio.epoch::advance inlines chklocks at every epoch boundary to
sweep expired locks.

T4: uw_request_t.variance_tolerance_bps copied from SwapRequest.

T5: commit_entry stores per-leg outpost_id + verbatim UIC bytes so the
depot can reconstruct the signed digest. rcrdcommit takes uic_bytes;
sysio.msgch::dispatch_underwrite_commit forwards the payload bytes.

T6: try_select_winner re-runs the LP variance check at race resolution
time and emits SWAP_REVERT on drift instead of writing locks.

T7: try_select_winner verifies both legs' signatures via the chain's
`get_permission_lower_bound` enumeration — the depot trusts ANY of the
underwriter account's permission keys. On verification failure: print +
TODO + skip (per `feedback_opp_handlers_never_throw.md`; pre-launch
slashing decision pending).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…() mirror + signed commit submission

T10: unconditional pre-flight check at plugin_startup. Verifies the
account is registered & ACTIVE in sysio.opreg, has sysio.authex links
covering every outpost in sysio.epoch::outposts, and has non-zero
balance on each outpost chain. Any failure logs a structured `elog` and
prevents cron registration — no `--strict=false` dev fallback (per
`feedback_no_dev_escape_hatches.md`). Adds signature_provider_manager_plugin
to APPBASE_PLUGIN_REQUIRES.

T11: replace raw `balances` read in read_credit_lines() with the
sysio.opreg::available() mirror — subtract uwrit::locks + opreg::wtdwqueue
entries from balance per (chain, token_kind). Adds `has_credit(chain,
token_kind)` predicate; replaces static_cast with magic_enum::enum_integer.

T14: refactor commit submission to the opaque-relay shape. New
`build_signed_uic_bytes(uwreq_id, outpost_id)` helper constructs the
UnderwriteIntentCommit proto, blanks the signature field, serializes,
sha256s the result, signs via signature_provider_manager_plugin (WIRE K1),
fc::raw::pack's the signature back into the proto's signature field,
re-serializes. submit_commit_eth / submit_commit_sol now pass those bytes
verbatim to the outpost's commit endpoint (commit(bytes) / commit_underwrite).
Drops the empty_sig stub and the wlog IDL-missing fallback.

T16: removed the unused push_action() helper. The signature_provider_manager_plugin
dependency stays — build_signed_uic_bytes uses it.

T19 (partial): underwriter_plugin/README.md refreshed to describe the new
flow + the deferred follow-ups (knapsack, source-deposit verification,
outstanding-commits retry, diagnostic clio endpoint). The bootstrap doc
at /data/shared/code/wire/CLAUDE-WIRE-OVERVIEW-BOOTSTRAP-CONTEXT.md
and the gap-plan at /data/shared/code/wire/wire-docs/claude-underwriter-gap-plan.md
were also updated in-place (those paths live outside any git repo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…verify, raw-balance preflight, T13 source-deposit (Option A), T12 knapsack, T15 confirm-before-record, T17 HTTP diagnostics, same-chain swap fix

Comment 1 — verify_uic_signature only checks `active`/`owner` permissions
(via sysio::get_permission lookup, not full get_permission_lower_bound
enumeration). Custom permissions are rejected: the plugin's
signature_provider_manager_plugin config is pinned to one of those two.

Comment 2 — preflight reads RAW balance from opreg::operators[].balances,
NOT the locks-deducted available() math. A fully-locked underwriter
still passes preflight; they pick up new commits when locks expire.

Comment 4 (T13) — source-deposit verification via derived id:
* `SwapRequest.source_tx_id: bytes` added to proto (Option A).
* `uw_request_t.source_tx_id` plumbed through createuwreq.
* Plugin `verify_source_deposit` reads source_tx_id, queries the source
  chain's SwapRequested event log (eth_getLogs filter via libfc's
  abi::to_event_topic), and verifies existence. Empty source_tx_id ->
  staged-rollout warning + pass-through. Adds
  --underwriter-eth-source-contract-addr opt.

Comment 5 (T12) — knapsack-style select_coverable. Depth-first branch-
and-bound maximizing total committed value subject to per-(chain,
token_kind) credit constraints, with suffix-sum upper-bound prune.
MAX_CANDIDATES = 64; greedy fallback above the cap.

Comment 6 (T15) — outstanding-commits with confirm-before-record:
submit_commit_eth chains execute_contract_tx_fn -> wait_for_confirmation;
submit_commit_sol uses execute_tx_and_confirm. Tracks per-leg
std::set<commit_key>; prunes on each scan against the live PENDING set.

Comment 7 (T17) — /v1/underwriter/stats + /v1/underwriter/commits HTTP
diagnostics on the plugin. Adds http_plugin dep; stats_mutex protects
shared state. The matching `clio opp uw …` CLI is a separate diff.

Comment 9 (T18) — E2E test deferred with full context at
wire-docs/future/claude-e2e-underwriter-race-flow.md.

Lazy-name fix — UnderwriteIntentCommit.token_kind (was leg_token_kind).

Same-chain swap support — rcrdcommit now takes from_token_kind; the
depot routes per-leg by (from_chain, from_token_kind) so e.g. USDC ->
ETH-native on a single outpost lands in the correct source/dest slot.

Enum-typing cleanup — every untyped enum field / cast introduced this
session replaced with the typed enum + magic_enum::enum_integer or the
proto-generated <EnumName>_Name / _Parse helpers. A new rule at
.claude/rules/enums-are-first-class.md (filesystem) captures the
requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to the wire-cdt change adding the recover_key_nothrow C-API +
WASM import. The host wraps the existing recover_key implementation in
a try/catch and returns -1 on any exception path (malformed bytes,
unactivated sig type, recovery math failure, subjective-size limit) so
CDT contracts compiled with -fno-exceptions can recover keys from
attacker-controlled signature bytes without halting the WASM.

Wiring:
- libraries/chain/webassembly/crypto.cpp — interface::recover_key_nothrow
  delegates to recover_key inside a try/catch. argument_proxy params are
  non-copyable but movable; the digest/sig/pub spans are forwarded via
  std::move.
- libraries/chain/include/sysio/chain/webassembly/interface.hpp —
  declaration on webassembly::interface.
- libraries/chain/webassembly/runtimes/sys-vm.cpp — register in the
  legacy runtime via REGISTER_LEGACY_CF_HOST_FUNCTION.
- libraries/chain/include/sysio/chain/webassembly/sys-vm-oc/intrinsic_mapping.hpp
  — append env.recover_key_nothrow to the index-ordered OC mapping
  table.
- libraries/chain/genesis_intrinsics.cpp — add to the whitelist so the
  chain accepts WASMs that import the symbol at deploy time.

This is the launch-time fix the underwriter race needs: the depot's
sysio.uwrit::verify_uic_signature path now uses the nothrow variant so
a malformed commit blob from an attacker can't stall evalcons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…urce deposit

Round-2 comments on the gap-plan summary doc drove a verify-everything
pass on the underwriter race:

- Depot (sysio.uwrit::createuwreq): reject any SwapRequest with an
  empty `source_tx_id` by emitting a SwapRevert back to the source
  outpost. No SwapRequest may exist on the depot without a populated
  source-chain transaction id. (Per feedback_opp_handlers_never_throw —
  emit the revert, do not throw.)
- Depot (uw_request_t): add a `depositor: vector<char>` field populated
  from `SwapRequest.actor.address`. Used by the plugin's verify path as
  the authoritative depositor address to cross-reference against the
  source-chain tx's `from` (ETH) / fee-payer (SOL).
- Plugin (verify_source_deposit): remove the staged-rollout
  silent-pass on empty source_tx_id — hard-fail instead. Adds full
  argument-match on both chains:
    ETH: tx exists + tx.to == configured contract + tx.from ==
         req.depositor + tx.input[0..4] == configured selector +
         receipt status != 0x0 + receipt depth >= ETH_MIN_CONFIRMATIONS.
    SOL: tx exists + meta.err == null + program-id in accountKeys +
         accountKeys[0] == base58(req.depositor) + program-targeted
         instruction's data starts with configured discriminator. Uses
         explicit commitment=SOL_COMMITMENT instead of the JSON-RPC
         default.
- Plugin (preflight): three new gates — required ETH+SOL CLI options
  for the verify path; exactly one WIRE K1 signature provider (no
  silent .front() pick); signature self-test that signs a known digest
  and confirms the recovered pubkey is on the underwriter account's
  owner or active permission via authorization_manager.
- Plugin (new options): --underwriter-eth-source-deposit-selector
  (4-byte hex), --underwriter-sol-source-deposit-discriminator
  (8-byte hex). Both required at preflight.
- New shared header source_deposit_constants.hpp:
  ETH_MIN_CONFIRMATIONS=12, SOL_COMMITMENT=confirmed. Single point of
  config for the verify path's chain-side thresholds.

Tests:
- New uwrit cases: rcrdcommit_same_chain_swap_auth (B6 — same-chain swap
  shape doesn't bypass auth), rcrdcommit_malformed_uic_does_not_halt
  (B4 — recover_key_nothrow no-throw guarantee end-to-end through the
  dispatch chain).
- New plugin tests: preflight_required_options_are_registered (B1 —
  guards the verify-path option surface against typos); placeholder
  stubs for preflight_fails_on_* / knapsack_fallback_threshold /
  http_endpoints_registered_at_startup pending fixture standup.

All 14 uwrit + 9 msgch + 9 epoch + 9 plugin tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tions

Round-2 comments drove the elimination of every duplicate-truth surface
between the underwriter_plugin's verify path and the outpost client
plugins' ABI / IDL configuration. The verify path now sources every
chain-side identifier (contract address, function selector, program ID,
instruction discriminator) from the same ABI / IDL files the outpost
client plugins already load via --ethereum-abi-file / --solana-idl-file.

libfc:
- solana_idl::program gains an `address` field, populated by the IDL
  parser from either top-level `address` (Anchor IDL v2) or
  `metadata.address` (older shape). Lets consumers identify a program
  by name and recover its deployed address from the same JSON, instead
  of duplicating the program ID in a separate config knob.

underwriter_plugin:
- Drop: --underwriter-eth-source-contract-addr,
        --underwriter-eth-source-deposit-selector,
        --underwriter-sol-program-id,
        --underwriter-sol-source-deposit-discriminator.
- Add:  --underwriter-eth-source-deposit-function=<name>,
        --underwriter-sol-source-deposit-instruction=<name>.
- Preflight resolution: walks eth_plug->get_abi_files() for a function
  contract whose name matches; takes contract_address verbatim and
  derives the 4-byte selector via to_contract_function_selector(c).
  Walks sol_plug->get_idl_files() for the matching instruction; takes
  the 8-byte discriminator directly and the program ID from the IDL's
  address field. Fails preflight if any lookup fails or if the ABI's
  contract_address / IDL's address is empty.
- Sig-provider preflight check broadened per user comment "at minimum
  3 signature providers - 2 active outposts and 1 for WIRE K1": now
  requires exactly 1 (chain=wire, key-type=wire) PLUS at least 1
  (chain=ethereum, key-type=ethereum) PLUS at least 1 (chain=solana,
  key-type=solana).

Tests:
- Update preflight_required_options_are_registered to reference the new
  option names; the dropped options no longer exist to be guarded.

All 14 uwrit + 9 msgch + 9 epoch + 9 plugin tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A pairing of `ChainId` + `TokenAmount` for per-(chain, token) config
surfaces. First consumer is wire-tools-ts/test-cluster-tool's
`--underwriter-collateral-json-file`, which parses one or more
ChainTokenAmount values out of the file via @protobuf-ts/runtime
JSON serdes; future config surfaces that need the same shape get
it for free.

No code change to existing messages — `ChainId` and `TokenAmount`
shapes are unchanged. Regenerated TS/Solidity/Solana bundles via
generate-opp-bundles.fish; C++ host headers regenerated on the
next wire-sysio build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…recover_key`

Removed all the references to the `recover_key_nothrow` intrinsic as well as removing the symbol itself everywhere.
…e rename

Brings the depot side of the v6 data-model refactor up: ChainKind/TokenKind
collapse to VM-family / standards enums, identity moves onto the new
slug_name 8-byte packed type (`sysio::slug_name` in sysio.opp.common,
`fc::slug_name` in libfc), and Chain/Token/ChainToken/Reserve are first-
class registry entities on the new `sysio.chains` + `sysio.tokens` contracts.
`sysio.opreg`, `sysio.uwrit`, `sysio.msgch`, `sysio.epoch`, `sysio.reserv`
are rekeyed by (chain_code, token_code) slug_name pairs; `sysio.epoch::
outposts` is removed in favor of `sysio.chains::chains` (consulted via
direct cross-contract read by `epoch::advance` and the host plugins).

Reserve gets a 3-state status enum (PENDING/ACTIVE/CANCELLED) with the
create→match→ready handshake and four new OPP attestation types
(RESERVE_CREATE / RESERVE_CREATE_CANCEL / RESERVE_CREATE_CANCELLED /
RESERVE_READY). Operator-action dispatch on msgch is rewritten to split
TokenAmount/ChainAddress into scalar action args per the
no-proto-messages-in-actions rule.

batch_operator_plugin now reads chain registry from sysio.chains (replacing
the removed sysio.epoch::outposts table); the read is wrapped to tolerate
cold-start replay races where the local node hasn't yet replayed the block
that creates the sysio.chains account. Operator-table read switched to
all-rows + in-plugin filter because the v6 KV PK encoding rejects the
v5-style bare-name lower/upper bound the plugin used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jglanz jglanz changed the title opp v6: Token / Chain / Reserve data-model refactor + slug_name identity type opp-part-3: Operator and Reserve Management May 20, 2026
Closes the full bidirectional swap flow. flow-swap-with-underwriting now
runs 11/11 green with Phase A (ETH→SOL) and Phase B (SOL→ETH) settling
end-to-end through underwriter race + dest-chain payout.

## Outpost-client SPI

- outpost_client base: `uw_commit(uw_request_id, uic_bytes, deadline)`
  pure-virtual added so underwriter_plugin routes commits chain-agnostically
- ETH concrete: typed operator_registry_contract_client wrapper, hex-encodes
  uic_bytes as the Solidity `bytes` arg
- SOL concrete: typed commit_underwrite wrapper with PDA overrides for
  operator_registry + outbound_message_buffer

## Underwriter source-deposit verification (Solana)

Mirrors the ETH `SwapDeposit(uint64 indexed id, bytes32 hash)` event-scan
pattern step-for-step:

- `req.source_tx_id` is 8-byte BE deposit_id (same wire shape as ETH)
- `getSignaturesForAddress(sol_program_id, limit=50)` enumerates recent
  program sigs
- per-sig `getTransaction` → `meta.logMessages[]` scanned for the canonical
  marker `Program log: opp_outpost: SwapDeposit id=<id> hash=<64hex>`
- hash recomputed from UWREQ flat fields (depositor[32] + 7×u64 BE + u32 BE
  = 92 bytes packed), bit-compared
- `fc::task::retry_until` with 15s poll interval / 120s total budget
  (`SOL_SWAP_DEPOSIT_POLL_INTERVAL` + `SOL_SWAP_DEPOSIT_TOTAL_TIMEOUT`
  constexpr in outpost_solana_client_plugin.hpp)

## sysio.msgch: monotonic att_seq singleton

`buildenv`'s outbound-bundle cleanup erases all `ATTESTATION_STATUS_PROCESSED`
rows for the destination `chain_code`. Inbound `deliver()` rows also carry
PROCESSED, so the cleanup drains the table and `available_primary_key()`
resets to 0. Phase N+1's inbound SwapRequest then inherits Phase 1's
attestation_id, and `sysio.uwrit::createuwreq`'s idempotency guard
short-circuits the new swap silently.

Fix: new `attseq` singleton holding `next` — survives the cleanup. All
`atts.emplace` sites now mint via `mint_att_id(get_self())`.

## sysio.uwrit

- createuwreq: silent no-op on duplicate UWREQ (per
  feedback_opp_handlers_never_throw.md — a throw inside evalcons stalls
  the chain)
- createuwreq: hard-fail SwapRequests with empty `source_tx_id` via
  SwapRevert (no underwriter can verify without it)
- emit_swap_remit: override `recipient.kind` from the destination chain
  (SOL cranker filters on `kind == CHAIN_KIND_SVM`; UNKNOWN passes through
  and the recipient is dropped)
- try_select_winner: signature verification on both legs of the UIC

## sysio.msgch: depot evalcons idempotency

After consensus is reached, `evalcons` was re-dispatching attestations on
every post-quorum delivery, queueing duplicate SWAP_REMITs (the 1000×
Phase A overshoot observed during diagnostics). Idempotency guard now
checks the byepoch index for prior INBOUND dispatch on `(chain_code,
epoch_index)`.

## Proto: SwapRequest target_* fields

Renamed `quoted_destination_amount` / `quote_tolerance_bps` /
`quote_timestamp_ms` → `target_amount` / `target_tolerance_bps` /
`target_timestamp_ms`. Users may submit a swap without ever fetching an
off-chain quote — they're expressing the target they accept.

## Min-id-1 invariant

`available_primary_key()` returns 0 from an empty table; std::max wrap at
every id-mint site (msgch, chalg) ensures we never assign id=0 (the proto
enum maps 0 to UNKNOWN for every entity type).

## Outpost-client SPI rule

Added `.claude/rules/outpost-client-spi.md` documenting the abstraction
and capabilities exposed on the SPI. Plugins must never reach into
ethereum_client / solana_client directly; all chain-specific operations
go through the SPI virtual.

## zpp::bits is CDT-only rule

Added `.claude/rules/zpp-bits-is-cdt-only.md`. Host code uses standard
protobuf C++ (`SerializeToString`/`ParseFromString`); CDT contracts use
zpp::bits via the `.pb.hpp` headers. Inbound population must set every
field (including default values) on the host side so the wire bytes
round-trip identically across the two serializers.

## cmake build all rule

Added `.claude/rules/cmake-build-all-for-contract-changes.md`. Any
contract `.cpp` edit requires the default `all` target; narrowing to
`--target sysio.<X>` builds the new `.wasm` in `build/` but doesn't
propagate the artifact back to the source tree, so the cluster harness
deploys the stale source-tree `.wasm` and the bug appears un-fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant