Engineering scope · committed

June-30 committed scope

This document is the engineering scope for the 30 June 2026 Sepolia testnet milestone. It defines what we need delivered for a working demo: the borrower / credit story end-to-end (deposit ERC-3643 collateral, onboard via ONCHAINID, borrow stablecoins, see the effects in the admin app), plus the admin and operator controls that govern it. The June-30 scope is firm: it is the committed delivery target for this milestone, not a starting point for expansion.

Committed milestone 30 June 2026 · Sepolia testnet Borrower / credit story end-to-end, with admin and operator controls — a firm ceiling, not a starting point
Contract items (committed) 6 A1A4, A7, A8-subset committed; A5, A6, A8-rest, A9, A10 beyond
Platform items 7 B1, B2, B3, B4, B9, B10, B11
Floor items 1 A7 deposit-pause
Named cut-line 1 Country rule module (A3)
Platform demo data is simulated · seed and mock figures only · kept behind a flag so demo data never leaks into real flows
v1.1 patch (2026-06-09) — what changed since v1.0.0
Amendments following the 2026-06-08 architecture call and debrief.
01

Executive summary

The June-30 build moves Ascend from a demonstrative model toward a compliant, securities-only institutional credit platform. The committed scope for this milestone is organized around three themes.

Identity and compliance at the contract layer

Adopt the ERC-3643 (T-REX) stack — ONCHAINID, the Identity Registry and its ancillary registries, and a modular Compliance contract with rule modules — wired into the Credit Facility.

Borrow-side identity and compliance gating

Borrowers are gated by the facility's ONCHAINID / Identity Registry and compliance on the borrower-lifecycle path. The complementary lender-gated Liquidity Module (KYC + Qualified-Lender on deposit/withdraw) is captured as forward direction in the Beyond June-30 section, where we invite OZ's proposed approach.

Collateral and operator controls, plus the platform slice

Collateral deposit-pause and safe de-listing, plus an Admin/Operator role model with per-account pause, user and token freeze, and global pause/shutdown — surfaced through a curated platform slice (securities-only data, fixed navigation and real client state, populated DDF and Reserve pages, a compliance-rules Wizard catalog, and the supporting taxonomy and stablecoin data).

Two further themes — rehypothecation via a vToken escrow, and DeFi exercise destinations — are architectural direction only and are captured in the Beyond June-30 section; they are not in this milestone's build.

One engineering correction threaded throughout
The Credit Facility is deployed as a deterministic clone via the Hub and configured through initialize(name, symbol, authority, modules[]); its constructor only calls _disableInitializers(). Where a requirement reads “register X in the constructor,” that is not possible for a clone — those addresses (Identity Registry, Compliance, ONCHAINID resolution) must be wired through initialize / module configuration, or installed as hooks. We call this out per affected item. It is a build correction, not a scope change.
What ships by 30 June (on Sepolia)
DeliverableDetail
ONCHAINID, Identity Registry, and Compliance integrated into the Credit Facilityvia initialize / hooks
Borrow-side gating on the borrower-lifecycle path (identity + compliance via the facility)lender-gated Liquidity Module is forward direction, not committed
Collateral deposit-pause and safe de-listing as the first Operator controlsplus the Admin/Operator role model
The borrower flow demonstrable end-to-end on Sepolia testnetwith effects surfacing in the admin app
A prioritized slice of platform UInavigation and real state, healthy-majority accounts (one distressed retained for the auction demo), DDF and Reserve data, compliance Wizard catalog
02

How to read this document

The document is layered so you can stop at the depth you need.

LayerWhat it covers
Scope at a glanceThis executive summary plus the June-30 milestone section (acceptance table, cadence, cut-lines).
Build detailThe Part A (contracts) and Part B (platform) item blocks — each gives a one-line “what” and a short “why it matters / acceptance.”
Cross-cuttingThe engineering-correction and security notes that apply across items.
Beyond June-30Forward-looking direction, clearly separated from the committed scope.

Each committed item is written as a requirement with an acceptance criterion. Identity and KYC are presented at the requirements level against the commodity ERC-3643 / ONCHAINID stack OZ already knows, not as deep implementation.

03

Current state and the June-30 gap

Requirements are written against the code as it stands today. The ERC-3643 and ONCHAINID dependencies are already present in ascend-contracts (@erc3643org/erc-3643, @onchain-id/solidity), and AuthorityWithIdentityRegistry already integrates ERC-3643 by forwarding canCall to a primary authority with a per-(target, selector) Identity Registry fallback — the natural home for a single registry instance. The platform side has an indexing pipeline (subgraph → ingester → sink → event-relay → platform-service → projections → API → frontend) and React frontends (apps/admin, apps/client) where a Wizard, DDF, Reserve, margin-account, and credit-facility views already exist. The dependencies are already installed, so the gap is deployment, wiring, and the UI slice, not new dependencies.

Today → June-30 gap
CapabilityTodayJune-30 gap to close
ERC-3643 / ONCHAINID depsInstalledDeploy and wire (via initialize / hooks, not constructor)
Identity RegistryFallback path in AuthorityWithIdentityRegistryOne registry instance + KYC / Qualified-Lender claims model
Compliance modulesImplemented as configurable compliance hooks (pre/post-action checks); base rule hooks (rate-limit, sanctioned-address) present in ascend-contracts, not yet wired into the facilityIntegrate ModularCompliance + rule modules (Trading Hours, Max/Min Size, Frequency; catalog also covers Rate-limit, Sanctions-blacklist, Credit-draw, Jurisdiction) and bind them to the Credit Facility via the hook framework
Lender gatingNone on deposit / withdrawDirection only — beyond June-30 (OZ to propose approach)
Borrow-side gatingIdentity-registry fallback onlyBorrower-lifecycle ops gated by identity + compliance
Collateral pause / safe de-listadd / removeCollateralToken onlyDeposit-pause + pledged-guard de-list
Margin-account controlsGlobal pause + FreezerModuleAdmin/Operator roles, per-account pause, user/token freeze
Mock collateral assetsGeneric ERC-20-style mocksFour identity-gated ERC-3643 mock assets
Platform UI (nav, DDF/Reserve, Wizard)Largely mock; pages render blankReal client state, populated pages, compliance Wizard catalog
vToken / escrow / rehypothecationNoneDirection only — beyond June-30
04

Part A — core contracts (ascend-contracts)

Part A covers the onchain protocol work. The June-30 contract scope centers on four items: identity and compliance wired into the Credit Facility (A1, A2, A3) — which gates the borrower-lifecycle path — four ERC-3643 mock assets for end-to-end validation (A4), collateral deposit-pause controls (A7), and a focused subset of admin/operator controls (A8 subset). The lender-gated Liquidity Module (A5) and the remaining items (A6, A9, A10, and the rest of A8) are forward-looking direction, collected in the Beyond June-30 section; for A5 we invite OZ's proposed approach. All June-30 work targets the Sepolia testnet.

A note on clone-safe wiring (applies throughout Part A)
The Credit Facility is deployed as a deterministic clone via the Hub and configured through initialize(name, symbol, authority, modules[]); its constructor only calls _disableInitializers(). This matters for every item that registers an external address (Identity Registry, Compliance, ONCHAINID resolution): for a clone, those addresses cannot be set in the constructor. They are wired through initialize / module configuration, or installed as hooks. We call this out per affected item. It is a build correction, not a scope change. The dependencies are already in place: @erc3643org/erc-3643 and @onchain-id/solidity are present in package.json. The identity / compliance stack needs deployment and wiring, not new dependencies. AuthorityWithIdentityRegistry already integrates an ERC-3643 fallback path and is the natural home for a single registry instance — there should be one registry, not two.
June-30 committed scope
A1 ONCHAINID identity contracts Committed
What Deploy the ONCHAINID suite (Identity, IdFactory, ImplementationAuthority, ClaimIssuer) from the already-imported @onchain-id/solidity package — these are installed dependencies, so the work is deploy-and-wire, not author-from-scratch. Each participant gets an ONCHAINID carrying signed claims; the facility resolves identities through the Identity Registry (A2), not by storing ONCHAINIDs itself.
Why it matters / acceptance This is the identity root the whole compliance stack reads from. Use one identity model shared across collateral tokens (A4), the facility, and the permissioned Liquidity Module (A5), so verification never diverges. Clone-safe: pass the Identity Registry address through initialize / module config; ONCHAINIDs are resolved via the registry at call time, never the constructor. Accept when a participant with a valid ONCHAINID and required claims is accepted, one lacking them is rejected at deposit/borrow, and unit tests cover both paths.
A2 Identity Registry + claim topics Committed
What Deploy the ERC-3643 IdentityRegistry plus IdentityRegistryStorage, ClaimTopicsRegistry, and TrustedIssuersRegistry from @erc3643org/erc-3643. Make this the single identity source of truth; pass its address into facility initialize.
Why it matters / acceptance Defines and enforces the claim model. Claim topics for June-30: KYC and Qualified Lender at minimum (Country is enforced via Compliance, A3). Reconcile with the existing AuthorityWithIdentityRegistry so exactly one registry is referenced platform-wide — repoint its existing fallback wiring to the new IdentityRegistry instance (migrate, not parallel), so there is one verification path. Accept when the registry is deployed and bound, isVerified(address) reflects KYC + Qualified-Lender claims, the AuthorityWithIdentityRegistry fallback points at this instance, and both the facility and the liquidity module read from it. Depends on A1.
A3 ModularCompliance + rule modules Committed Country = cut-line
What Deploy ERC-3643 ModularCompliance and bind the rule-module catalog. Committed for June-30: Trading Hours, Max/Min Size, and Frequency (time-windowed limits). The fuller catalog the engine should be able to bind — confirmed at the architecture session — also includes Rate-limit (anti-structuring per-window caps), Sanctions / blacklist (transfer-time deny-list, distinct from the KYC claim), Credit-draw (borrow-side draw limits), Credit Rating (gates eligibility and limits by borrower / collateral rating), and Jurisdiction, which is the existing Country module (jurisdiction / residency) and remains the single named cut-line that can defer if the schedule tightens. Pass the compliance address into facility initialize.
Why it matters / acceptance This is the transfer-time and borrow-time rule layer. The facility already has a hook framework (withHook, statusBefore/After) and FreezerModule.canTransact — these overlap with transfer-time gating and should be coordinated, not duplicated (avoid double-enforcement between FreezerModule and Compliance). The decided mechanism is to expose compliance as an installable Credit Facility hook, enforced on margin-account operations through the existing installHook / withHook framework, so a single hook can carry the bound rule set across the borrower-lifecycle selectors. For June-30 the rule set is a fixed, hard-coded set bound at install time — not a fully dynamic, runtime-reconfigurable compliance engine; the configurable catalog is an offchain Wizard concern (B11), not a v1 onchain requirement. Ship safe defaults plus an admin override so a misconfigured rule cannot brick transfers or liquidation. Accept when each rule module independently blocks/permits per spec, a transfer/borrow passing all bound modules succeeds, and the liquidation path remains executable under compliance. Depends on A1, A2. The platform compliance Wizard (B11) is the offchain analog.
A4 Four ERC-3643 mock assets Committed
What Mint four permissioned (ERC-3643) mock collateral tokens — Treasury, US Equity, Private Credit, Private Equity (Fund) — each bound to the Identity Registry and Compliance, for realistic end-to-end testing and platform demos.
Why it matters / acceptance Today's collateral mocks are generic ERC-20-style tokens, not identity-gated securities. These four make the borrow flow exercise real ERC-3643 constraints. Each carries realistic decimals and a ValuationModule price feed; provide Ignition seeding and matching platform seed constants so the same four assets appear consistently onchain and in the UI (B9). Keep transfers obeying compliance in tests — no silently-unrestricted mocks. Accept when all four identity-gated, priced tokens are usable as collateral through a full borrow flow in tests. Depends on A2, A3, ValuationModule.
A5 Permissioned Liquidity Module Direction only
What Add a permissioned variant of LiquidityModuleCore that requires every lender to hold an ONCHAINID with KYC and Qualified-Lender claims (verified via the Identity Registry) to deposit or withdraw. Keep borrow-side gating symmetric so both sides are permissioned.
Direction (not June-30 committed scope) This is the lender-gating that would make the pool institutional rather than open. LiquidityModuleCore is an ERC-6909 multi-token vault, so a permissioned variant would extend the core and cover the multi-token deposit/withdraw and lender-facing IOU paths — gating across token ids, not a single asset — keyed on the same Identity Registry the facility reads (isVerified plus the KYC and Qualified-Lender topics), with a safe withdrawal path / admin remediation so claim expiry cannot trap a legitimate lender. We have deliberately left the design open: rather than prescribe the mechanism, we would like OZ to propose its approach for permissioning the liquidity side — how it extends LiquidityModuleCore, where verification is enforced, and how it stays clone-safe and consistent with the borrow-side gating. Borrow-side gating (borrowers gated by the facility's identity and compliance, via A1/A2/A3) remains committed for June-30; only the lender-gated Liquidity Module moves to direction. Relates to A1, A2 (claims); A3 optional.
A7 Collateral controls: deposit-pause and safe de-list Committed Floor item
What Add operator controls to (a) pause deposits of a given collateral token into margin accounts, and (b) de-list a collateral token from the whitelist only when no margin account currently has it pledged.
Why it matters / acceptance The first concrete operator safety control, and the single June-30 floor item. Add pauseCollateralDeposits(token) / unpause, consulted inside depositCollateral. Harden removeCollateralToken(token) to revert unless a maintained pledgedCount[token] == 0, incrementing/decrementing that counter in every collateral mutation path (deposit, withdraw, transfer, liquidate). Gate to Admin + Operator; emit events for the indexer. Crucially, keep withdrawals of a paused token allowed so users are never trapped. Accept when deposits of a paused token revert while withdrawals still succeed, de-listing reverts when any account holds the token and succeeds when none do, and events are indexed. Depends on the Operator role.
A8 (subset) Admin/Operator controls for June-30 Committed
What The June-30 admin-controls slice defines the Admin and Operator roles in the AccessManager and ships the protective, reversible controls: per-account pause, freeze user, freeze tokens in an account, and the existing global pause / shutdown.
Why it matters / acceptance These are the operator levers the admin app surfaces and the demo exercises. FreezerModule already freezes users/accounts and gates via canTransact — consulted on both controller operations and NFT (account) transfers, the same gate that governs the liquidation _update(liquidator, id, 0) path. Extend it with token-level freeze and a per-account pause flag consulted by the operation modifiers, taking care that compliance/freeze coordination never blocks the liquidation transfer. The facility is already globally Pausable. Role-gate every control and event-log it. Accept when each control is exercisable only by Admin/Operator, emits events, and negative tests cover unauthorized callers. Depends on the role model; FreezerModule extension. The heavier A8 powers — account migrate and cross-account clawback — are forward-looking and sit in the Beyond June-30 section.

Simplified contracts view: current → target

flowchart LR
  subgraph Current["Current — ascend-contracts"]
    H1["Hub"] --> CF1["CreditFacility (clone)"]
    CF1 --> M1["Modules: Liquidity, Valuation,
Risk, Fee, Metadata, Freezer"] CF1 --> DA1["DistressedAssetsAuction"] AWI1["AuthorityWithIdentityRegistry
(ERC-3643 fallback path)"] -.-> CF1 end subgraph Target["Target — June-30 additions"] H2["Hub"] --> CF2["CreditFacility (clone)"] CF2 --> M2["Modules (existing)"] CF2 --> DA2["DistressedAssetsAuction"] CF2 -. "initialize / hooks" .-> IR["A2: Identity Registry
+ claim topics (KYC,
Qualified Lender)"] CF2 -. "installHook" .-> CMP["A3: ModularCompliance
+ rule modules"] CF2 -. "direction" .-> PLM["A5: Permissioned Liquidity
Module (beyond June-30)"] IR --> OID["A1: ONCHAINID suite"] CMP --> IR PLM --> IR A4T["A4: 4 ERC-3643
mock assets"] --> IR A4T --> CMP AWI2["AuthorityWithIdentityRegistry
(single registry — reconciled)"] -.-> IR end Current ==>|"A1, A2, A3, A4 + A7/A8 controls"| Target classDef add stroke-width:2px class OID,IR,CMP,A4T add

The target keeps the existing Hub → CreditFacility → modules + DistressedAssetsAuction shape and adds the identity, compliance, and permissioned-liquidity layer on top. Consumers (the facility, the liquidity module, the mock assets) hold pointers to the Identity Registry and Compliance, and the registry resolves the ONCHAINID. The single-registry reconciliation (one IdentityRegistry, not two) is the connective change.

Identity and compliance — requirements summary

Stated as requirements and acceptance against the commodity ERC-3643 / ONCHAINID stack.

RequirementMechanismAccept when
Per-participant onchain identityONCHAINID carrying signed claims (A1)Identity deployed and resolvable via the registry
Single identity source of truthERC-3643 IdentityRegistry + storage (A2)isVerified(address) returns the correct claim state; one registry platform-wide
KYC required of all participantsKYC claim topicUnverified wallet rejected at deposit/borrow
Lender eligibilityQualified-Lender claim topicLender without the claim rejected in the permissioned Liquidity Module
Jurisdiction controlCountry rule module (A3; cut-line)Country rule blocks/permits per allow-list when enabled
Transfer/borrow-time rulesModularCompliance + the bound rule-module catalog: Trading Hours / Max-Min Size / Frequency (committed), plus Rate-limit / Sanctions-blacklist / Credit-draw / Credit Rating / Jurisdiction (catalog) (A3)Each bound module independently gates; liquidation stays executable

Claim topics in scope for June-30: KYC (identity verified, AML/sanctions cleared — required of all participants including every lender) and Qualified Lender (qualified/accredited or institutional status — the Qualified-Lender / accredited claim model is seeded for June-30, while lender-side enforcement via the Permissioned Liquidity Module (A5) is forward direction). Country (jurisdiction/residency, enforced via the A3 Country rule module) is the deferrable cut-line.

05

Part B — platform (ascend-platform)

Part B covers the platform's offchain services and UI: the indexing pipeline (subgraph to projections to API) and the two frontend apps (admin and client). For June-30, the focus is a curated UI slice that lets the end-to-end borrower / credit story render convincingly on Sepolia testnet, with the admin / operator controls surfaced, and the reserve and distressed-asset views populated. The forward-looking items (vToken, rehypothecation, the “Exercise” DeFi flows) are summarized in the Beyond June-30 section, as direction rather than committed scope.

June-30 UI slice B1 B2 B3 B4 B9 B10 B11 Each item is a one-line “what” plus what it takes to accept it. Effort labels (S, M, L) are pre-grooming.
B1 Securities-only curation Effort M
What Trim the seeded credit facilities to a small, curated securities-only set and remove the crypto / stablecoin-collateral mocks. Stablecoins remain only as the borrow / liquidity denomination (B10), never as collateral.
Why it matters / acceptance Aligns the demo with the institutional credit story and keeps onchain assets (A4) consistent with what the UI shows. Accept when only securities collateral appears, the facility list is curated, and no crypto mocks remain in the UI or the seed constants. Depends on A4, B9, B10.
B2 Navigation + real client state Effort M
What After a borrow / deposit / withdraw, “back” returns to the originating Credit Facility detail rather than the list page, and the action persists. Replace pure-mock navigation with real client state (the stack already carries Zustand and React Router 7).
Why it matters / acceptance The current frontend is largely a pure mock: back navigation pops to a list and actions do not persist, which breaks the borrower walkthrough. Accept when completing a borrow / deposit / withdraw lands on the facility detail with the action reflected, and browser back behaves intuitively across the admin and client apps. Depends on B3 / B4 (shared state model).
B3 Healthy-majority accounts, one distressed retained Effort S
What Tune the seed so most margin accounts show healthy health factors (roughly 70% healthy / 20% watch / 10% distress), keeping at least one scripted distressed account for the Dutch-auction / liquidation demo.
Why it matters / acceptance Current seed data skews many accounts into distress, which misrepresents a healthy book. The retained distressed account is what makes the liquidation flow demoable. Accept when the account list shows a healthy-majority distribution with at least one distressed account retained for the liquidation flow. Depends on B1 / B9; keep figures consistent with the reserve / DDF data (B4).
B4 Populate the DDF + Reserve pages Effort M Open: confirm “DDF”
What Fill the currently-blank DDF and Reserve pages with real (mock) data and computations.
Why it matters / acceptance Both pages render today but are empty. The Reserve page ties to the model (target / balance / Cover-1 and the income loop: interest plus fees, reserve cut, lender yield); the DDF page ties to distressed accounts, auction status, and recoveries. Accept when both pages show populated, internally-consistent figures that update with the mock state, and the numbers tie to the account view (B3) so the account, reserve, and DDF views agree. Depends on B3. Open item: confirm what “DDF” denotes before populating (working assumption: Distressed Debt Fund).
B9 Collateral asset taxonomy Effort S–M
What Standardize collateral asset classes to a fixed set: Equity, Treasury Bonds, Commercial Paper / Corporate Bonds, Private Equity, Private Debt, Asset-Backed Securities (ABS), and CLOs.
Why it matters / acceptance The current classes are mixed and ad-hoc. A4's four tokens map cleanly into this taxonomy (Treasury, US Equity, Private Credit → Private Debt, Private Equity Fund). Accept when all collateral pickers and tables show only this taxonomy, consistent across the frontend, seeds, and contract mocks. Depends on A4, B1.
B10 Stablecoins per denomination Effort S Open: which denominations
What Expand the stablecoin set to 5–6 per currency denomination, structured so a denomination groups multiple coins.
Why it matters / acceptance Today only USDC + USD1 exist (USD only). Borrow and Exercise pickers should let users select within a denomination. Accept when each denomination lists 5–6 coins, borrow / Exercise let users pick within a denomination, and USD-peg 1:1 assumptions stay explicit. Depends on B1. Open item: confirm which currency denominations (USD only, or USD + EUR + …) and which exact coins are in scope.
B11 Compliance-rules Wizard module Effort M–L
What Add a Wizard step that assembles compliance for a facility from a pre-populated catalog of rule modules — Trading Hours, Max/Min Size, Frequency, Country (jurisdiction), Rate-limit, Sanctions / blacklist, Credit-draw, and Credit Rating — plus Chainlink ACE: pick, order, and configure each. The catalog maps 1:1 to the onchain ModularCompliance modules (A3).
Why it matters / acceptance A Wizard exists but compliance configuration is thin. The rule-module catalog maps 1:1 to the onchain compliance modules (A3), so the Wizard and the contracts stay in step. Accept when (June-30 floor) the module catalog plus ordering is usable — a user can pick, order, and configure the rule modules and review the resulting compliance set, with ACE shown as an offchain entry alongside the onchain modules. Depends on A3 (parity).
Cross-view consistency
B3, B4, B9, and B10 share one mock dataset. The account view (B3), the Reserve and DDF views (B4), and the asset taxonomy (B9) must agree on every figure — collateral, drawn amounts, haircuts, prices, reserve target / balance, and recoveries — so the three views never contradict each other in a walkthrough. Where the seed changes, the offchain stack picks it up only after the deployment-address wiring and replay fork pin are updated together (see the deployment-wiring note in Cross-Cutting Concerns); otherwise the indexed projections point at stale data.
06

Beyond June-30 (direction only)

These items are architectural direction, not committed June-30 scope. They are recorded here so the June-30 build leaves clean extension points. None should be implemented or production-flagged without separate sign-off and external audit.

Contracts

A6 Hypothecation across margin accounts Direction only
A controlled capability letting collateral in one margin account back exposure in another, under operator policy and compliance. The cleaner substrate is vToken allocation (A10) rather than ad-hoc accounting links. Open design risks: double-counting shared collateral, and liquidation ordering across linked accounts. Design milestone only (ADR + interface spec + reviewed risk model); no production flag without audit.
A8 (remainder) Migrate and clawback Direction only
Beyond the June-30 protective controls, the heavier powers — migrate a margin account's full state to a new account/owner, and Admin/Operator-authorized clawback of collateral and/or debt between accounts for remediation — are centralizing and require governance treatment (timelock/multisig, disclosure) before production. They must preserve health-factor and accounting invariants. Direction noted; not in the June-30 slice.
A9 ERC-721 for margin accounts (rationale) Direction only
Margin accounts stay ERC-721: each account is a distinct, ownable, transferable position with its own collateral and debt, liquidation is modeled as a standard NFT ownership transfer (_update(liquidator, id, 0)), and per-account isolation namespaces collateral/debt by tokenId. Worth capturing as an ADR.
A10 vToken escrow Direction only
A tokenized vault share representing a margin account's collateral position, custodied in a dedicated escrow. This is the onchain substrate for controlled rehypothecation (A6) and for the platform “Exercise” DeFi flows. vTokens would themselves be permissioned (inheriting ERC-3643 constraints), with defined mint/redeem, custody, and liquidation handling. External audit required before any production use.

Platform

ItemDirection
B5 — Escrow / vToken mockA UI mock for the vToken escrow: collateral tokenized into vTokens, held in escrow, and allocated to accounts. Mirrors the contract-side vToken substrate (A10).
B6 — Margin-account controls mockA full admin controls panel per margin account for the broader account-control set (pause account, freeze user, freeze tokens, migrate, clawback), role-gated with an audit-log display. The June-30 admin subset (per-account pause, freeze) is already covered in Part A; this item is the full mocked panel.
B7 — Collateral controls mockA UI mock for the collateral controls: toggle deposit-pause per token and attempt de-list with a guard message when the token is pledged. The June-30 deposit-pause is covered in Part A; this is the surfacing layer for the full control.
B8 — Rehypothecation mockA UI mock that pledges one collateral token to multiple margin accounts within explicit utilization caps, with aggregate-utilization and double-pledge indicators so the UI never implies unlimited reuse.
B12 — “Exercise” post-borrow flow + DeFi destinationsRename the post-borrow flow to “Exercise” and add a destination picker that routes drawn stablecoin (or vToken) to DeFi destinations grouped by action type (stake / yield, swap, lend / borrow, bridge), including a Chainlink CCIP bridge option. Mock integrations, clearly labeled. Exact protocol names and destination chains remain to be confirmed and are out of June-30 scope.
07

June-30 milestone — what we need to see

This is the committed scope for the 30 June 2026 demo on Sepolia testnet. It is a ceiling, not a starting point: the borrower / credit story end-to-end (deposit ERC-3643 collateral → onboard via ONCHAINID → borrow stablecoins → effects surfaced in the admin app), plus the admin/operator controls and the supporting platform pages.

Four workstreams map to the contract and platform items above. The acceptance column is the heart of “demo-ready” — it is what we will check against on 30 June.

WorkstreamMaps toAcceptance (demo-ready, on Sepolia)
Identity + Compliance into the Credit Facility A1, A2, A3 A registered, KYC'd identity can transact; an unregistered one is blocked at deposit/borrow; at least one compliance rule module is enforced on a margin-account operation — all wired via initialize / hooks, never the constructor.
Borrow-side gating A1, A2, A3 Borrowers are gated by the facility's identity and compliance on the borrower-lifecycle path; an unverified or non-compliant borrower is blocked. The lender-gated Liquidity Module (A5) is forward direction (OZ to propose approach) and is not part of the June-30 acceptance.
Collateral deposit-pause (first Operator control) A7 (+ Operator role) An Operator pauses deposits of a collateral token; deposits revert while withdrawals still succeed; the event is indexed by the platform.
Platform UI slice B2, B3, B4, B11 (+ B9/B10 data) Navigation returns to the originating Credit Facility after an action; margin accounts show a healthy-majority distribution with one distressed account retained for the auction/liquidation demo; DDF and Reserve pages are populated and internally consistent; the compliance Wizard catalog is usable.

Suggested sequencing (dependency-ordered, indicative)

A light sequencing suggestion, not a fixed contract — sequence to your own dependency read.

PhaseWork
Phase 1 — registries + first controlsDeploy the identity/compliance registries and ONCHAINID factory (A1/A2); define the Operator role; ship collateral deposit-pause (A7); start platform nav/state (B2) and the account re-balance (B3).
Phase 2 — compliance bindingBind ModularCompliance + rule modules as a facility hook (A3); DDF/Reserve data (B4).
Phase 3 — mock assets + Wizard + integrationBorrow-side parity + integration tests; the four ERC-3643 mock assets for end-to-end runs (A4); compliance Wizard catalog (B11); B9/B10 data.
Final hardeningNegative tests, demo polish, seed and deployment-address wiring updates, and a go/no-go.

The floor — non-negotiables if a workstream slips

The floor is fixed
If the build runs behind, the floor is fixed. The non-negotiable is collateral deposit-pause (A7) — the first operator safety control — together with the committed borrow-side identity and compliance gating (A1, A2, A3) that is the credit-story spine. These hold even under pressure. The single named cut-line is the Country rule module (A3 — see Open Questions); it is the first thing to drop if the schedule tightens, while Trading Hours, Max/Min Size, and Frequency stay committed. A separate, lower-stakes scope-down is advanced Wizard rule-ordering (B11) — keep at least the module catalog. Everything else in the table above is targeted, but A7 deposit-pause and the committed borrow-side gating are the line below which the demo does not go.
08

Cross-cutting concerns

These apply across the contract items and are worth fixing once, centrally.

Clones, not constructors

The Credit Facility is deployed as a deterministic clone via the Hub and configured through initialize(name, symbol, authority, modules[]); its constructor only calls _disableInitializers(). Identity Registry, Compliance, and ONCHAINID resolution must therefore be wired through initialize / module config / hooks — never the constructor. Several requirements read as “register X in the constructor”; for a clone that is not possible, so we call out the clone-safe wiring per affected item (A1, A2, A3, A5).

Admin and Operator roles (one access model)

Define both in the AccessManager and make it the single authority every gated call resolves through — the same authority (AuthorityWithIdentityRegistry) that already lets an identity-registry-verified principal satisfy a gate as an alternative to holding a role. Admin and Operator are the gate for the deposit-pause (A7) and the broader account controls (A8 subset); per-selector gating (setTargetFunctionRole) is the mechanism, with a clear role-admin hierarchy so onboarding and operator powers are granted and revoked through defined admins rather than ad-hoc. The more powerful forward-looking powers (migrate, clawback) should additionally be timelocked or multisig-gated and always event-logged. Keep the role set coherent: the same Admin/Operator semantics referenced in A7 and the A8 subset, not divergent definitions per item. The confirmed roles-and-permissions list (the grants per selector and the role-admin assignments) is owed to OZ as the single source that finalizes these definitions.

Transfer-time enforcement — don't brick liquidation

ERC-3643 enforces at transfer time, and the facility's existing code already warns about hooks/restrictions interfering with liquidate(). FreezerModule.canTransact is already consulted on both controller operations and NFT (account) transfers — the same gate that governs the liquidation _update(liquidator, id, 0) path — so compliance/freeze coordination must never block that transfer. Ship safe defaults plus an admin override so a misconfigured compliance rule can never block the liquidation path. Reentrancy is already annotated in borrow/repay/liquidate; keep that posture.

Deployment-address wiring

After any protocol redeploy, update the platform's deployment-address wiring and replay fork pin — otherwise the indexed stack points at stale contracts and projections drift.

Test coverage

Unit + integration coverage (Hardhat Solidity + Node.js) for every gating path — accepted and rejected. Keep platform mocks behind a flag so demo data never leaks into real flows.

One identity registry

AuthorityWithIdentityRegistry already integrates an ERC-3643 fallback (forwards canCall to the primary IAuthority, falls back to a per-(target, selector) IIdentityRegistry). Migrate that fallback wiring to point at the new IdentityRegistry so there is exactly one registry instance platform-wide — one source of truth, not two divergent verification paths.

09

Open questions for the architecture session

These are the build-relevant points we would like to settle together before grooming tickets. None blocks starting the deploy/wiring work; all affect the exact shape of what ships.

#Question
1Country rule module — in or out for 30 June? ModularCompliance is in scope with Trading Hours, Max/Min Size, and Frequency. Country is the single named cut-line if time is tight (the first thing to drop in the floor above). Confirm whether Country is committed for the demo or deferred.
2Qualified-Lender claim topic + trusted issuer. Confirm the claim-topic definition for Qualified Lender and which trusted issuer signs it. This drives the TrustedIssuersRegistry seeding and the A5 gating logic.
3DDF page — intended contents. Confirm what “DDF” denotes (we read it as a distressed-debt view tied to distressed accounts, auction status, and recoveries) before B4 is populated.
4Stablecoin denominations. Confirm which currency denominations are in scope (USD only, or USD + EUR + …) and which coins per denomination, so the borrow-side picker and seeds are correct.
5Collateral taxonomy. Confirm the asset-class taxonomy for the securities-only set — in particular whether “commercial paper” and “corporate bonds” are one class or two — so enums/fixtures across contracts, seeds, and frontend agree.
10

Appendices

Appendix A — ERC-3643 (T-REX) component glossary

ComponentRole in Ascend
ONCHAINID (Identity)Per-participant onchain identity holding signed claims (KYC, Qualified Lender, Country).
ClaimTopicsRegistryThe set of claim topics a token/facility requires (e.g., KYC, Qualified Lender).
TrustedIssuersRegistryWhich issuers may sign each claim topic.
IdentityRegistryMaps wallet → ONCHAINID and answers isVerified; the single identity source of truth.
IdentityRegistryStorageBacking storage for the registry, shared across tokens.
ModularComplianceBinds rule modules; evaluated at transfer / borrow time.
Rule modulesTrading Hours, Max/Min Size, Frequency, Country (jurisdiction), Rate-limit, Sanctions / blacklist, Credit-draw, Credit Rating — composable transfer/borrow-time rules bound by ModularCompliance.
Chainlink ACEOffchain Automated Compliance Engine feeding/enforcing rules; surfaced as a Wizard module on the platform.

Appendix B — required claim topics (initial)

KYC

Identity verified and AML/sanctions cleared. Required of all participants, including every lender.

Qualified Lender

Attests qualified / accredited or institutional status. Required to deposit or withdraw in the Permissioned Liquidity Module (A5).

Country

Jurisdiction / residency. Enforced via the Country compliance rule module (A3 — see Open Questions for its 30 June scope).

Appendix C — key file references

For the engineering team, the existing code touchpoints each workstream builds on.

PathTouchpoint
ascend-contracts/contracts/CreditFacility.solinitialize, hooks, transferFrom / _update, liquidate, borrow/repay.
ascend-contracts/contracts/Hub.solcloneDeterministic + initialize.
ascend-contracts/contracts/access/AuthorityWithIdentityRegistry.solThe existing ERC-3643 fallback (reconcile to a single registry).
ascend-contracts/contracts/modules/LiquidityModuleCore, FreezerModule, ValuationModule, RiskModule, FeeModule, MetadataModule.
ascend-contracts/package.json@erc3643org/erc-3643, @onchain-id/solidity, @chainlink/contracts (identity/compliance dependencies already present).
ascend-platform/backend/scripts/replay-events/src/{constants,seed-constants}.tsSeeds.
ascend-platform/frontend/apps/{admin,client}Wizard, DDF, Reserve, and margin-account views.

Appendix D — contracts: current → target

The current ascend-contracts shape grows toward the target by adding the identity and compliance layer. The additions (ONCHAINID + Identity Registry, and ModularCompliance + rule modules) are the 30 June contract scope — A1/A2 and A3. The permissioned-liquidity layer (A5) is shown as forward direction, not committed June-30 scope.

flowchart TB
    subgraph current["Current — ascend-contracts"]
        Hub1["Hub
(cloneDeterministic + initialize)"] CF1["CreditFacility
(ERC-721 margin accounts)"] MOD1["Modules:
Liquidity, Valuation, Risk,
Fee, Metadata, Freezer"] DAA1["DistressedAssetsAuction"] AUTH1["AuthorityWithIdentityRegistry
(ERC-3643 fallback)"] Hub1 --> CF1 CF1 --> MOD1 CF1 --> DAA1 CF1 -.-> AUTH1 end subgraph target["Target — 30 June additions (highlighted)"] Hub2["Hub"] CF2["CreditFacility
(identity + compliance wired via initialize / hooks)"] MOD2["Modules
(existing)"] DAA2["DistressedAssetsAuction"] OID["ONCHAINID
(per-participant claims)"] IR["Identity Registry
(+ ClaimTopics, TrustedIssuers, Storage)"] MC["ModularCompliance
(rule-module catalog:
Trading Hours, Size, Frequency,
Country, Rate-limit, Sanctions,
Credit-draw, Credit Rating)"] PLM["Permissioned Liquidity Module
(direction — beyond June-30)"] Hub2 --> CF2 CF2 --> MOD2 CF2 --> DAA2 CF2 --> PLM CF2 --> MC CF2 --> IR IR --> OID MC --> IR PLM --> IR end current ==>|"A1/A2 · A3"| target classDef add stroke-width:2px class OID,IR,MC add

The target keeps the existing Hub → CreditFacility → modules + DistressedAssetsAuction shape and adds the identity, compliance, and permissioned-liquidity layer on top. Consumers hold pointers to the Identity Registry and Compliance, and the registry resolves the ONCHAINID. The single-registry reconciliation (one IdentityRegistry, not two) is the connective change.

11

Appendix E — Confirmed Roles & Permissions

This is the confirmed roles-and-permissions reference noted as pending against the abstract AccessManager implementation. It is written as a specification for the setTargetFunctionRole wiring and the role-admin graph in ascend-contracts — the roles the protocol needs and how they nest — not an implementation dump. Roles tied to the permissioned Liquidity Module (A5) are marked forward direction, consistent with the body descope.

E.1 — The access model: one authority, two onboarding paths

Ascend is an onchain tri-party agent: the neutral custodian-bank role of a classic tri-party arrangement is disintermediated into the Hub and its modules, while a licensed Clearing & Settlement Broker-Dealer carries the regulatory wrapper. Three economic parties — Borrower, Lender (cash), and the Agent / Operator (the broker-dealer running the Hub) — interact through one access authority.

Role and identity are interchangeable satisfactions of one gate
Every gated call resolves through AuthorityWithIdentityRegistry (an AccessManager subclass). canCall(caller, target, selector) grants if either the caller holds the AccessManager role bound to that selector (role path), or the caller is verified in the ERC-3643 Identity Registry bound to that (target, selector), resolved with zero delay (identity path). So an AccessManager role and an ONCHAINID claim are interchangeable satisfactions of the same gate. Binding a registry to a selector (setAuthorizedRegistry) is itself Operator-gated — roles govern the identity wiring, identities satisfy role-equivalent gates, one authority adjudicates both. FreezerModule overrides everything: a frozen principal fails regardless of which positive path would have granted. This is why ONCHAINID, the AccessManager roles, and the RBAC model are one concern, not three.

E.2 — Identity layer: the parties and their ONCHAINID claims

Each verified party holds an ONCHAINID in the Identity Registry, checked on every ERC-3643 transfer. Claims are issued offchain by a Trusted Issuer (ClaimIssuer). Claim topics for the product set: KYC · Qualified Lender · Country. Credit Rating is enforced as a Compliance rule module (A3), not an identity claim.

PartyRequired claimsGate
Borrower KYC (identity + AML / sanctions), Country (jurisdiction); collateral-eligibility and credit / risk limits enforced via Compliance. Borrower-lifecycle selectors — identity path, or the Investor role.
Lender (cash) KYC, Accredited / Qualified Lender, Country; plus the lender liquidity claim for the permissioned pool Forward · A5 Lender deposit / withdraw eligibility, enforced in the liquidity module.

The product model carries the fuller claim set above. A demo harness may stand in a coarser eligibility check for borrower verification and a single lender liquidity claim topic; the demo proves the mechanism, not the final claim schema.

E.3 — Operator / Agent roles (AccessManager)

The agent functions a tri-party custodian traditionally performs, encoded as roles in ascend-contracts. June-30 core unless marked forward. Granularity is per-selector (setTargetFunctionRole), but the role set is deliberately coarse — Operator absorbs collateral, pause, hooks, fees, freeze, and risk params rather than splitting into many granular roles.

RoleAgent functionGates (illustrative)Role admin
Admin Root of trust + governance. Configure the access graph (setTargetFunctionRole, grantRole, setRoleAdmin); reserve-tier + compliance registration. Self (under OWNERS — E.4).
Operator Day-to-day agent operations. Facility pause / unpause; collateral add / remove + pledge-pause; installHook / uninstallHook; fee + risk / valuation params; freeze (via FreezerModule); setAuthorizedRegistry. Admin
Onboarding Officer KYC / compliance onboarding. Role-admin of Investor + Liquidator; no direct facility powers. Operator
Investor = Borrower KYC'd account holder. createMarginAccount, withdrawCollateral, borrowLiquidity, transferCollateral, transfer, closeMarginAccount. Interchangeable with the identity path. Onboarding Officer
Liquidator Default management — seizure. liquidate(...); also held by the default and auction venues. Onboarding Officer
Hub Admin Provision a new tri-party book. Hub.createCreditFacility(...). Admin
Auction Manager Default management — auction venue config. Auction duration, bid params, repayer / backstop / fee-collector, pause. Admin
Verified Buyer KYC'd auction bidder. Auction bid(...). Auction Manager
DDF Operator Default management — recovery. Distressed-facility liquidate / recover / releaseTo. Admin
Reserve-Cover Reserve-waterfall draw authority. cover(...) on the reserve registry. Admin
PUBLIC Intentionally permissionless. depositCollateral and repayment paths.
FreezerModule override Kill-switch (not a role). Operator-gated freeze of user / account; overrides all positive paths. (Operator)

Margin-account ops gate on the NFT owner, not the raw caller — an approved operator acts only while the owner still qualifies under role-or-identity.

E.4 — Governance: OWNERS + timelock

Cross-cutting configuration is not a single unilateral key. The model is OpenZeppelin AccessManager with role separation, execution delays / timelocks, and two-owner OWNERS change control on cross-cutting config, with a full audit trail of configuration changes (the enforceable governance admin requirement). There is no single unilateral upgrade path — this is the answer to “who holds the keys / upgrade-rug risk.”

E.5 — Machine roles (contracts, not granted to humans)

Implementation wiring, not operator grants. The Hub itself is the facility factory.

RoleHeld byWhy
Liquidity Broker The CreditFacility contract Only the facility may move pool funds on a borrower's behalf.
Pool-Draw The ReserveRegistry contract Only the registry orchestrates per-tier reserve draws.

E.6 — Forward direction: permissioned liquidity Not June-30

These gate the permissioned Liquidity Module, which is forward direction, not June-30 committed scope (see A5 — OZ to propose its approach). Listed so the access graph is coherent end-to-end.

Liquidity Manager

Pool config (setToken, IOU-ratio caps, pause). Role admin: Admin. Forward direction — gates the permissioned variant of LiquidityModuleCore (A5).

Liquidity Broker (machine)

Pool borrow / repay, held by the facility (see E.5). The borrow-side machine wiring is committed; the lender-side permissioning it serves is forward.

Lender liquidity claim

ONCHAINID eligibility enforced inside the module: registered identity + a valid liquidity claim from a trusted issuer + compliance canTransfer. Forward direction (A5).

E.7 — Excluded: scaffolding, not product roles

For a clean spec boundary, the following are deploy-time test scaffolding, not roles to model in the product access graph:

ItemWhy it is not a product role
Latent enum entries An entry declared in the role enum but bound to no selector and granted to no one is latent / dead — not part of the permission model.
Test-fixture personas Additional deploy-time test accounts (a second borrower, a keeper, and similar) are fixtures for end-to-end runs, not a permission model.
Coarse eligibility stand-ins A coarse boolean eligibility check is a harness stand-in for the full ERC-3643 claim verification specified in E.2.

E.8 — Mapping to the June-30 scope

Mapping pointDetail
Roles to scope items Admin = the scope doc's Admin; Operator = the scope doc's Operator; Onboarding Officer = the KYC / compliance officer implied by the claim model (A2).
The A8 control slice Per-account pause, freeze user, freeze tokens, global pause / shutdown (A8) is Operator-gated via FreezerModule + the facility's Pausable.
Coarse-by-design role set The structurally separate operational roles are the default-management set (Liquidator / Auction Manager / DDF Operator / Reserve-Cover) and, forward, Liquidity Manager (A5).
Dennis O'Connell
Author Dennis O'Connell CEO & CTO, Ascend
Manny E. Reimi
Owner Manny E. Reimi CPO, Ascend · [email protected] · Slack @manymoneymanny
v1.1.0 · 2026-06-09
Ascend — June-30 committed scope (30 June 2026 Sepolia testnet). Engineering scope written at the level of requirements and acceptance criteria against the current state of ascend-contracts and ascend-platform. Platform figures are simulated demo data, not production.