Ascend Protocol · spec-driven development

Segregated tier — build specifications

Six workstreams with acceptance criteria — client-owned segregated subaccounts for regulated onchain repo. Each client subaccount is an onchain margin account owned by a per-subaccount client wallet; the intermediary operates the book as a scoped, non-owning agent.

July 3, 2026 Confidential — internal distribution
Deterministic acceptance scenarios 88 A 10 · B 14 · C 19 · E 13 · F 14 · R 18 — counted across six workstreams
Workstreams6A · B · C · E · F · R — D referenced, not in this pack
Locked decisions2enforced by default · scoped operator — binary gates in every workstream
New contracts10wallet · porting · KYC rule · vault · escrow · intraday line · reserve pool · migrator · resolution · migration
Ship orderB + A → C · E · R → FB and A first, parallel · then C, E, and R (R needs only A) · migration F done last
02

Two locked decisions

Locked across every workstream and asserted as binary acceptance gates — a book that violates either one fails acceptance, not review.

Locked decision 1 — enforced by default
Segregated-tier compliance and rehypothecation rules ship and deploy with enforced = true. A segregated book that deploys with any required rule missing, not installed in the compliance hook chain, or not enforced fails acceptance. There is no deployment path that leaves the segregation or 140% gates off. This reverses the current enforced = false default for the segregated recipe and is asserted at deploy and in acceptance.
Binary gate in all six workstreams · every acceptance suite ends on it
Locked decision 2 — scoped operator
The intermediary is never an ERC-721 operator. An operator inherits every owner power — withdraw to any address, transfer the NFT, borrow — so an intermediary approved as a plain operator could drain a client before any default. It is authorized only on the client wallet's curated, scoped methods; the wallet forwards to the facility as owner, and every beneficiary and payout is forced to the client.
Binary gate wherever the account is touched · isApprovedForAll(wallet, intermediary) is always false
03

Ship order and dependencies

B and A ship first (independent, parallelizable), then C, E, and F, with R after A. Migration (F) is done last. Workstream D — repo-module v2 — is referenced as a dependency but is deliberately not specced in this pack.

Solid — hard dependency on A or B · dashed — repo accounts migrate only after D lands · dotted — soft: E's default-facility routing sits on D's beneficial-owner re-keying · R additionally reconciles with B (payout screening) and C (recall before payout), soft

04

Workstream A — client margin wallet, scoped operator, and identity hierarchy

Foundation · ships first, parallel with B · everything else depends on this.

What is being built and why

The segregated tier holds each client subaccount as an ERC-721 margin-account NFT owned by a per-subaccount smart-contract wallet, with the intermediary operating the book. This workstream builds that wallet, the identity hierarchy it carries, and the porting controller — the foundation every later workstream depends on. Both locked decisions land here first: the wallet is the sole owner and controller with the intermediary confined to a curated method surface, and segregated-tier rules deploy enforced = true with a deploy-time invariant asserting it.

Design
  • One ClientMarginWallet per subaccount — the sole owner and ERC-721 controller of that subaccount's margin-account NFT. The facility resolves control through _checkControllerOf (authorized · canCall · not frozen), so a call the wallet makes as owner passes all three and every authority, compliance, and freeze check keys on the client.
  • The wallet carries the subaccount's ONCHAINID, so the wallet address is the KYC'd compliance subject.
  • Scoped method surface for the intermediary: deposit · borrow (beneficiary forced to the client) · repay · withdraw (receiver forced to the client) · open and manage repo draws · transfer collateral between the same client's own accounts. No NFT transfer, no setApprovalForAll, no withdrawal to an arbitrary address.
  • New claim topics SUBACCOUNT_OF and OMNIBUS_OPERATOR with trusted issuers — the parent link is signed by a trusted issuer registered for the topic, not self-issuable.
  • resolveBeneficialOwner — the single shared, level-aware resolver that walks a subaccount's SUBACCOUNT_OF claim to the parent client identity. Rules declare the level they check; every later workstream reuses this resolver and never re-derives it.
  • PortingController — a neutral, pre-approved controller. On an attested intermediary default, port(old, new) rotates the operator authorization with ownerOf unchanged — no cooperation from the defaulter, not blocked by freezing it.
  • Narrow, purpose-specific ERC-721 approvals only to the porting controller and DistressedDefaultFacility — never a blanket approval to the intermediary.

Trap — CreditFacility is at the EIP-170 contract-size limit, so scoping is structural: the wallet's curated method surface, not new facility bytecode.

Acceptance criteria · 10 scenarios
GroupScenariosGate
Ownership and control1
Scoped operator (Decision 2)3Binary gate
Identity hierarchy3
Porting1
Enforcement (Decision 1)1Binary gate
Regression — existing non-segregated books unaffected1Must stay green
05

Workstream B — recipient-KYC on transfers and exits

Exit screening · ships first, parallel with A · hard prerequisite of migration (F).

What is being built and why

Today the facility verifies the sender on an exit, never the recipient — the documented from-only gap; the facility's own NatSpec asks for a recipient-KYC hook that does not exist. This workstream installs a single recipient-KYC compliance rule that gates the recipient of every way value or ownership leaves a segregated account, verifying the required identity claim before the exit is allowed. It is the one gap that blocks porting a client subaccount to a successor intermediary: moving an account is only safe if the party receiving it is verified.

Design
  • A single IComplianceRule module composed into the existing ModularComplianceHook chain via runtime addModule. It binds to selectors that already exist — the facility is at the EIP-170 limit and its supportsHook allowlist is closed, so no new hookable entrypoint can be added.
  • Four exit surfaces, one claim: transferFrom · both safeTransferFrom overloads (3-arg and 4-arg — the classic bypass) · withdrawCollateral(..., to) · transferCollateral, whose destination the compliance context drops today and which must be resolved into the counterparty before it can be screened.
  • Destination resolution, decided here: extend the transfer-collateral decode so the destination account's beneficial owner is set as the counterparty, then screen it; the same-beneficial-owner-only gate is the fallback if the decode change proves infeasible.
  • Liquidation carve-out — re-homing a seized NFT to a last-resort or backstop buyer is a protocol-internal recovery action, not a client transfer, and stays open with the rule enforced. The from-owner sender check is likewise untouched.
  • Inert while enforced = false — the incremental-build convenience applies only to landing code without disturbing other books; a deployed segregated book is always fully enforced.
  • Preserved invariant: an NFT transfer clears per-token deposit authorizations on _update; the rule adds a pre-transfer recipient check and does not disturb that behavior.

Traps — a rule that gates only transferFrom is silently evaded through either safeTransferFrom overload; and the compliance context drops the transferCollateral destination today, so an operator could move collateral between beneficial owners with zero recipient screening.

Acceptance criteria · 14 scenarios
GroupScenariosGate
Recipient screening on NFT transfer2
Both safeTransferFrom overloads route through the same gate3Binary gate
withdrawCollateral recipient2
transferCollateral destination2Counterparty-resolution gate
Liquidation carve-out2Must never be blocked
Enforcement (locked decision)2Binary gate
Regression — books and the liquidation integration path unaffected1Must stay green
06

Workstream C — segregated rehypothecation vault and intraday credit line

The core capability · rehypothecation funds intraday credit, per-client bounded · Dennis's north star for the tier.

What is being built and why

Rehypothecate a client's margin to fund intraday credit — done the way the survivors of the last crisis did it (Lehman US: per-client bounded, segregation enforced in fact) rather than the way the casualties did (Lehman Europe's unlimited rehypothecation, MF Global's operational misuse). Four deliverables: a vault subclass that reuses collateral only up to a per-client ceiling and never beyond what customers collectively owe; a wrap-versus-raw deposit discipline that decides physically which tokens can ever reach a venue; an intraday credit line whose end-of-day cure is a hard contract invariant; and a pinned enablement order that turns the limit rule on only against a fresh keeper feed.

Design
  • The deployed RehypothecationVault (ERC-4626 wrapper) caps reuse against one keeper-fed aggregate debit scalar and pools every pledger's collateral indistinguishably — funding intraday credit off that aggregate would reuse one client's collateral against another client's debt.
  • SegregatedRehypothecationVault subclass, base vault untouched and byte-identical — the one place the design deliberately subclasses rather than composes a hook (the vault is not size-constrained; only CreditFacility is at EIP-170).
  • _allowedOut becomes a three-way minimum: the buffer-floor term · a per-client sum-of-mins ceiling Σi min(pledgedi, 1.4·debiti) · an aggregate-indebtedness cap totalPledged ≤ Σi debiti. Two distinct regulatory caps, kept distinct — the base vault conflated both into one scalar.
  • The per-client result is surfaced through the same views the limit rule reads (allowedRehypothecation() / rehypothecated()) — a structural requirement, not a nicety.
  • Wrap-versus-raw: the reusable on-margin slice is wrapped into vault shares and can reach a venue; fully-paid spot collateral is posted raw into a per-client RawCollateralEscrow, bears zero venue write-down, and wrapped plus raw reconciles with the facility custody record every valuation tick.
  • IntradayCreditLine: a draw funds atomically against the reused collateral in one transaction — no morning unwind, no bridge credit. The end-of-day cure recalls and repays in the same transaction; the vault buffer floor is the backstop substitute for the clearing bank, sized against the intraday exposure.
  • Pinned enablement order that never reverses: corrected per-client cap → fresh keeper feed → enforced = true. A keeper freshness heartbeat makes staleness detectable; the vault clamps rather than reverts, so any residual excess-rehypothecated amount raises an alert.
  • Hook install-order invariant: the reserve observing policy runs before the enforce gate on every shared selector, and any intraday collateral-mutating hook installs before the enforce gate and never between the two — asserted at deploy and re-verified after any maintenance uninstall.

Traps — the base vault's single aggregate scalar is the Lehman-Europe failure mode; if the tighter per-client caps are not surfaced through the exact views the limit rule reads, the rule keeps enforcing the old aggregate 140% cap and over-limit reuse passes silently; enabling the rule against a stale or zero ceiling would enforce a cap that permits arbitrary reuse; and hook uninstall is swap-and-pop, which silently reorders the chain.

Acceptance criteria · 19 scenarios
GroupScenariosGate
Per-client reuse cap4
Surfaced through the limit rule's views1Breakage guard
Wrap-versus-raw discipline3
Intraday credit line and end-of-day cure3
Enablement order and freshness (Decision 1)3Binary gates
Scoped operator (Decision 2)2Binary gates
Hook install order2Invariant
Regression — base vault byte-identical, prime recipe unchanged1Must stay green
07

Workstream E — per-beneficiary customer reserve sub-ledger

Customer reserve · defaulter-pays, bounded by each client's own funded slice.

What is being built and why

The segregated tier promises that one client's shortfall can never consume another client's protection — and today it cannot keep that promise on the customer reserve, which is ring-fenced only at the class level and bounds every draw on the aggregate pool balance. A run of disbursements against one client consumes the balance a second client is relying on, even though the second client funded its own slice. The guaranty fund already solves the equivalent problem with a per-member contribution record and defaulter-pays draws; this workstream gives the customer reserve the same per-beneficiary sub-ledger.

Design
  • SegregatedFirstLossReservePool keeps a per-client credit sub-ledger: fundFor(beneficiary, amount) credits a named slice, and the slice — not an aggregate with reporting attribution — is the authoritative bound on that beneficiary's draws.
  • coverBeneficiary(beneficiary, amount, to) pays out bounded by the beneficiary's remaining credit, mirroring the guaranty fund's defaulter-pays draw against a member's own contribution.
  • The inherited aggregate cover() is disabled — it reverts with a dedicated beneficiary-draw-only error. There is deliberately no path that pays a client from the pooled balance instead of its own slice.
  • A new BeneficiaryScoped tier class is appended by value to the segmented reserve registry enum and excluded from all four computations: borrow-gate coverage total, coverage target, loss-cover walk, default-cover walk. Its coverage target is zero. Classifying it via setTierClass is a deploy-time step and a binary gate.
  • The default facility routes the account's pre-seizure owner — resolved to the beneficial owner through A's shared resolver — into coverBeneficiary behind a beneficiary-segregated flag, so a liquidation draws only against the defaulting client's own reserve credit.
  • Key alignment: the reserve fundFor key, the compliance rule key, and the default-facility key must resolve identically for one client; a cross-module check asserts all three agree.
  • Segregation is prospective from the first per-client contribution — pre-migration pooled reserve capital stays mutualized, and the operator must disclose that boundary. The pool is privileged and ships behind a kill-switch.

Traps — an appended-but-unclassified tier reads as Mutualized (enum value zero): the borrow gate would count ring-fenced customer money as firm capital and under-gate borrowing, and the waterfalls would cross-credit one client's capital to plug another's default — the exact failure this workstream exists to prevent. A prior stale-key defect, where two keys that should have matched silently diverged, motivates the key-alignment check.

Acceptance criteria · 13 scenarios
GroupScenariosGate
Per-beneficiary draw bound (Decision 1)3Binary gates
BeneficiaryScoped tier exclusion (Decision 2)5Binary gates
Default-facility routing1
Key alignment1
Prospective segregation1
Enforcement (enablement hygiene)1Binary gate
Regression — cover signatures unchanged, legacy paths byte-identical1Must stay green
08

Workstream F — migration of existing accounts to client-owned

Cutover · done last · one account at a time, never against the frozen address book.

What is being built and why

Every margin account opened before the segregated tier is owned by the intermediary — a prima facie estate asset in the intermediary's insolvency, with compliance, freeze, and interest all keyed on the intermediary rather than the client. New accounts already open under a ClientMarginWallet (A); this workstream moves the accounts that already exist onto client-owned wallets, one at a time, without ever bricking an account or touching the frozen demo and live address book. The move is a real ERC-721 transfer of the margin-account NFT, gated by the recipient-KYC rule (B) so an account can never land on a non-KYC client.

Design
  • MarginAccountOwnershipMigrator — a MANAGER-gated AccessManaged sidecar plus its operations script. migrate(account, wallet) transfers the NFT and, in the same transaction, restores the per-token deposit authorizations the transfer wipes on _update; it reverts on a non-account input.
  • Atomic pre-flight before the NFT moves: the target wallet is registered in the identity registry, its ONCHAINID carries the required claim topics, and it resolves through canCall for every owner-gated controller selector on the real wired facility. Any failure reverts the whole call with ownership unchanged.
  • rollback(account) mirrors migrate — returns ownership to the original intermediary and re-restores the authorizations the return transfer wipes. The escape hatch for a migration a human decides to unwind, not a substitute for the pre-flight.
  • Repo accounts with open draws migrate only after D lands: once the client is the owner, interest pulls natively from the client and rolls run through the scoped operator. The pre-flight additionally confirms the wallet can fund and has approved the interest collector, so no false-default cascade.
  • Cutover discipline: new accounts open under client wallets first · per-account opt-in, no big-bang re-owning · idempotent over a multi-account intermediary · per-account freeze after migration, never per-wallet · never against the frozen demo or live-deploy address book.
  • The migrated account lands on a book whose recipient-KYC, segregation, and 140% gates are installed and enforced; the wallet grants the migrator narrow, purpose-specific authorization — never a blanket approval.

Trap — the bricked-account trap: migrating before the client wallet is a verified owner would transfer the NFT successfully and then have every controller operation revert on the owner identity check, stranding the account until a rollback. The atomic pre-flight makes owned-but-inoperable unreachable.

Acceptance criteria · 14 scenarios
GroupScenariosGate
Re-owning an existing account2
Deposit authorizations restored in the same transaction1
The bricked-account trap (atomic pre-flight)2Binary gate
Rollback2
Recipient-KYC gate1Binary gate
Repo accounts with open draws1Gated on D
Cutover discipline3Binary gates
Enforcement (locked decision)1Binary gate
Regression — non-migrated books unaffected, frozen address book untouched1Must stay green
09

Workstream R — facility resolution (venue failure)

Venue failure · after A · the authority rotates control, never takes title.

What is being built and why

The protocol today has no resolution surface: pause() is a total freeze — every client exit path is gated whenNotPaused — so a paused facility traps even a fully-paid client sitting on excess collateral. That is backwards from a customer-property-first regime: when the venue itself fails, the client's own money should be the first thing that can leave, not the last. This workstream builds the neutral resolution layer — a governed authority, a resolution state machine distinct from pause, an escape hatch that guarantees a solvent client an exit, and a cross-facility migration controller. Every primitive is exercisable by the neutral authority alone.

Design
  • RESOLUTION_AUTHORITY — a governed, multi-party role (multisig or timelock), not the intermediary and not the facility operator. Deploy wiring asserts it holds only the resolution selectors and no capability to manage the access manager — a registry able to reach grantRole can rewrite roles.
  • ResolutionController — a state machine Solvent → Resolving → WoundDown/Migrated, advanced only by the authority. Resolving freezes new risk-taking (borrow, new account, new draw) but leaves the escape hatch and repayment open. An external special-controller contract, not new facility bytecode (CreditFacility is at EIP-170), built adjacent to A's porting controller.
  • resolutionWithdraw(id, token, receiver) — gated whenResolving, not whenNotPaused: the single change that turns "pause means trapped" into "resolution means a guaranteed customer exit". It reuses the facility's existing status-after-Healthy math, releasing only excess-above-debt, and cannot push the account unhealthy.
  • Customer-property-first payout: the client's rehypothecated collateral is recalled before the hatch pays, so the client is paid from its own recovered property ahead of the estate — reconciling with C's vault and reserve unwind. The recipient-KYC hook (B) still fires on the payout.
  • MigrationController — account ids embed the facility (keccak(facility, minter, salt)), so cross-facility migration re-mints in the successor for the same ClientMarginWallet, moves collateral atomically, carries the ONCHAINID and SUBACCOUNT_OF claim across, and emits an old-to-new id mapping for indexer continuity.
  • The client stays ownerOf throughout — control rotates, title never moves. A neutral-trustee hold would re-commingle the client's asset and is offered only as an escheatment-analog fallback for abandoned or unreachable clients, never the default path.

Traps — resolution is not pause; if the resolution authority could manage the access manager, the neutral resolver would become a single seizer — the payment-system failure mode it exists to prevent; rehypothecated collateral is recalled before the client is paid; and account ids embed the facility, so migration re-mints rather than transfers.

Acceptance criteria · 18 scenarios
GroupScenariosGate
Neutral authority3Binary gates
Resolution state machine (Resolving is not pause)3Binary gate
Escape hatch — resolutionWithdraw5Binary gates
Ownership during resolution2Binary gates
Cross-facility migration3Binary gates
Enforcement (Decision 1 — enablement hygiene)1Binary gate
Regression — books not in resolution unaffected, pause still a total freeze1Must stay green
10

Workstream D — repo-module v2 · referenced, not specced

Referenced — not specced in this pack

Repo-module v2 makes interest pull from a designated payer and pay to a designated payee, makes rolls tolerant of the scoped operator (both defaulting to the owner), and re-keys the repo default facility on the beneficial owner. It gates repo-account migration — repo accounts with open draws migrate (F) only after D lands — and the reserve default-routing step in E sits on top of its re-keying (soft dependency).

11

Cross-cutting gates

Enforcement gate6 of 6every workstream deploys its required rules with enforced = true — no path leaves a gate off
Regression gate6 of 6the full existing test suite must stay green when each workstream lands
Knowledge baseReviewed PRseach spec is promoted to the shared knowledge base through a reviewed pull request
Segregated tier — build specifications
Six workstreams, with acceptance criteria · 88 deterministic scenarios
July 3, 2026
Ascend Protocol · spec-driven development · July 3, 2026 · confidential — internal distribution.