From bb1cb9a41720ac5960f16296c8898a8feb41bbc3 Mon Sep 17 00:00:00 2001 From: Eric Conner Date: Mon, 24 Aug 2026 07:01:57 -0700 Subject: [PATCH] Add ERC-8391: Asset Status Interface for Tokenized Assets --- ERCS/erc-8391.md | 431 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 ERCS/erc-8391.md diff --git a/ERCS/erc-8391.md b/ERCS/erc-8391.md new file mode 100644 index 00000000000..2c05f2cbdad --- /dev/null +++ b/ERCS/erc-8391.md @@ -0,0 +1,431 @@ +--- +eip: 8391 +title: Asset Status Interface for Tokenized Assets +description: Token-level lifecycle, reference-market, valuation, and primary-window status discovery for tokenized real-world assets +author: Eric Conner (@econoar) +discussions-to: https://ethereum-magicians.org/t/erc-8391-asset-status-interface-for-tokenized-assets/29489 +status: Draft +type: Standards Track +category: ERC +created: 2026-08-23 +requires: 20, 165 +--- + +## Abstract + +This ERC defines a minimal, [ERC-165](./eip-165.md)-discoverable set of interfaces through which an +[ERC-20](./eip-20.md) token representing a single reference asset (a tokenized stock, ETF, commodity, +or fund share) exposes its current operational status onchain: the lifecycle state of +the token program, the session and interruption state of its reference market, the +condition of its designated valuation source, and the availability of primary issuance +and redemption. It standardizes the *questions* an integrator can ask of any such token +given only its address. It does not prescribe how issuers answer them, and no status +value asserts safety, liquidity, or suitability for any consumer purpose. + +## Motivation + +Tokenized equities transfer onchain continuously while their reference markets operate +roughly 32.5 hours per week. State that determines whether such a token can be safely +valued or used as collateral — sessions, halts, valuation-feed condition, redemption +availability — is not observable through the [ERC-20](./eip-20.md) interface, and as of August 2026 is +exposed inconsistently per issuer: + +- Chainlink exposes market status at the feed-report level and directs integrators to + implement session-aware staleness handling themselves, warning against inferring + market hours from `updatedAt`. +- Ondo Global Markets publishes session and per-asset pause semantics via an offchain + HTTP API that contracts cannot read. +- Robinhood Chain stock tokens expose a proprietary `oraclePaused()` boolean. +- Backed, Securitize, and Dinari tokens expose only generic `paused()` mechanisms. +- Lending markets listing tokenized equities encode this state in per-listing curator + configuration bound to an oracle address, not to the asset. + +Consequently every integrator (lending market, vault, liquidation agent, account- +abstraction policy module) either special-cases each issuer or ignores this state. +Human curators can absorb that cost for a handful of listings; automated consumers — +liquidation infrastructure, agent policy modules, programmatic listing systems — +cannot, and they are becoming the dominant consumers of these tokens. + +Issuers also benefit directly from implementing this interface. Integration cost is +the practical ceiling on an RWA token's utility: bespoke status handling means slower +collateral listings, lower loan-to-value ratios, and larger haircuts. A standard +status surface lowers that cost for every integrator at once. It also converts +operational state into documented, machine-readable disclosure: an issuer that +reported `CLOSED` and `EXPECTED_NO_UPDATE` throughout a weekend gap event has a +materially better position than one whose integrators had no way to ask. + +[ERC-8056](./eip-8056.md) standardizes split multipliers; [ERC-7943](./eip-7943.md) and +[ERC-3643](./eip-3643.md) standardize compliance enforcement. None exposes market +session, valuation condition, or primary-window availability. This ERC fills that gap +and composes with all three. + +## Specification + +The key words "MUST", "MUST NOT", "SHOULD", "MAY" are to be interpreted as described in +RFC 2119. + +### Scope + +These interfaces apply to [ERC-20](./eip-20.md) tokens representing exposure to a single reference +asset. Multi-asset wrappers, index vaults, and NFTs are out of scope. + +Tokens whose reference asset has no continuous secondary market (e.g. NAV-based fund +shares) SHOULD implement the core interface plus `IReferenceValuationStatus` and +`IAssetPrimaryStatus`, and SHOULD omit `IReferenceMarketStatus` rather than report +degenerate session values. + +### General requirements + +1. **`UNKNOWN` is zero.** Every enum in this ERC assigns `UNKNOWN = 0`, so that zeroed + proxy storage, uninitialized implementations, and malformed delegated responses + decode as `UNKNOWN`, never as an operational state. +2. **No reverts.** All view functions defined here MUST NOT revert. If a value cannot be + determined, implementations MUST return `UNKNOWN` (and `0` for timestamps). +3. **Caller independence.** Return values MUST NOT depend on `msg.sender`. +4. **Status timestamps.** Each interface returns `AsOf`-style timestamps: the unix time + at which the reported status was last affirmed. For statuses that an issuer or + provider pushes onchain, this is the time of the last push. For statuses derived + from stored data as a function of `block.timestamp` (e.g. a session computed from an + onchain trading calendar), the `AsOf` value MUST be the time the underlying data was + last written — not `block.timestamp` — so that consumers can detect stale schedules. + Consumers MUST treat stale `AsOf` values as reducing confidence in the reported + status itself. +5. **Delegation isolation.** A token MAY answer these views by delegating to a shared + status provider (e.g. one venue-calendar contract shared by many tokens). The token + address MUST remain the query surface, and a failure in the delegated provider MUST + surface as `UNKNOWN`, not as a revert. Implementations SHOULD bound the gas + forwarded to delegated providers. +6. **Advisory semantics.** No status value defined in this ERC implies transferability, + liquidation executability, secondary-market liquidity, hedge availability, caller + authorization, or settlement. Statuses are issuer-asserted facts, not verdicts. + +### Core interface (mandatory): `IAssetStatus` + +```solidity +interface IAssetStatus { + /// @dev Lifecycle of the token program itself. + enum Lifecycle { + UNKNOWN, // 0 — status unavailable + PRE_ACTIVE, // program announced/deployed, not yet operational + ACTIVE, // normal operation + SETTLEMENT_PENDING, // terminal event in progress (merger cash-out, wind-down) + TERMINATED // program concluded; token is a claim residue at most + } + + /// @dev Operational status of the program, orthogonal to lifecycle. + enum ProgramStatus { + UNKNOWN, // 0 + NORMAL, // issuer operations functioning as designed + SUSPENDED // issuer has suspended normal operations (ops/legal/technical) + } + + /// @notice Current program status. MUST NOT revert; MUST NOT depend on msg.sender. + /// @return lifecycle lifecycle state of the token program + /// @return programStatus operational status + /// @return lifecycleAsOf unix time lifecycle was last affirmed (0 = unknown) + /// @return programAsOf unix time programStatus was last affirmed (0 = unknown) + function assetStatus() + external + view + returns ( + Lifecycle lifecycle, + ProgramStatus programStatus, + uint64 lifecycleAsOf, + uint64 programAsOf + ); +} +``` + +### Optional extension: `IReferenceMarketStatus` + +```solidity +interface IReferenceMarketStatus { + /// @dev Scheduled session state of the reference market. + enum Session { + UNKNOWN, // 0 + REGULAR, // continuous trading in the venue's primary session + EXTENDED, // any scheduled non-primary trading session + AUCTION, // scheduled or triggered auction/call phase; orders accepted, + // matching deferred to an uncrossing + CLOSED // any scheduled non-trading period, including intraday breaks, + // nights, weekends, and holidays + } + + /// @dev Unscheduled interruption state, orthogonal to Session. + enum Interruption { + UNKNOWN, // 0 + NONE, // no known interruption + PRICE_CONSTRAINED, // trading continues but is materially constrained by a + // price-limit mechanism (limit-up/limit-down lock, special + // quote, or partial constraint such as a program-trading + // sidecar) + ASSET_HALTED, // this asset specifically halted by the venue + VENUE_HALTED // the venue/market as a whole halted + } + + /// @notice Session and interruption state of the reference market. + /// @return session scheduled session state + /// @return interruption unscheduled interruption state + /// @return sessionAsOf unix time session state last affirmed + /// @return interruptionAsOf unix time interruption state last affirmed + /// @return nextScheduledTransition unix time of next scheduled session change + /// (0 = unknown); covers scheduled transitions + /// only — halt lifts are unscheduled and are not + /// represented here + /// @return marketId identifier of the designated reference market: + /// the ISO 10383 MIC as uppercase ASCII, + /// right-padded with zero bytes (bytes32(0) = + /// unknown or no listed venue) + function referenceMarketStatus() + external + view + returns ( + Session session, + Interruption interruption, + uint64 sessionAsOf, + uint64 interruptionAsOf, + uint64 nextScheduledTransition, + bytes32 marketId + ); +} +``` + +Session and interruption are deliberately separate dimensions: a halt during regular +hours (`REGULAR` + `ASSET_HALTED`) and a scheduled close (`CLOSED` + `NONE`) require +different integrator responses, and a single flattened enum forces implementers to lose +one dimension whenever both apply. + +Session semantics: + +- Intraday scheduled breaks (e.g. midday breaks on Asian venues) are `CLOSED`; the + break's duration is discoverable via `nextScheduledTransition`. +- Auction phases that are part of the primary session's open or close, standalone + periodic auctions, and triggered volatility auctions all report `AUCTION`. + Securities that trade only via periodic auctions report `AUCTION` during + order-acceptance windows and `CLOSED` otherwise. +- For venues with near-continuous trading (e.g. 23-hour commodity sessions), issuers + SHOULD report `REGULAR` whenever the venue is open for continuous trading and + `CLOSED` during maintenance windows, and SHOULD NOT use `EXTENDED` unless the venue + itself designates a non-primary session. +- Cross-listed assets have exactly one designated reference market, identified by + `marketId`; all session values refer to it. +- If the reference market permanently ceases (delisting, merger completion), + implementations SHOULD report `UNKNOWN` for session and interruption; the terminal + signal is carried by `Lifecycle` (`SETTLEMENT_PENDING` or `TERMINATED`), not by this + interface. + +### Optional extension: `IReferenceValuationStatus` + +```solidity +interface IReferenceValuationStatus { + /// @dev Condition of the designated valuation source. + enum Condition { + UNKNOWN, // 0 + UPDATING, // updates arriving per the source's own declared schedule + EXPECTED_NO_UPDATE, // no newer valuation currently due (e.g. market closed) + DELAYED, // an expected update has not arrived + DEGRADED, // source operating outside its declared quality bounds + SUSPENDED, // source deliberately paused by issuer/provider + DISPUTED // source value is contested by issuer or provider + } + + struct ValuationStatus { + Condition condition; + uint64 valueAsOf; // unix time of the last valuation value + uint64 statusAsOf; // unix time this condition was last affirmed + uint64 nextExpectedUpdate; // unix time next update is due (0 = unknown) + address source; // designated valuation source contract (0 = offchain) + bytes32 sourceId; // stable id of the valuation source; see below + } + + /// @notice Condition of the designated valuation source for this token. + function referenceValuationStatus() external view returns (ValuationStatus memory); +} +``` + +Required semantics: + +- `UPDATING` means updates expected under the designated source's own schedule are + arriving according to its declared policy. It does not assert safety or suitability. +- `EXPECTED_NO_UPDATE` means no newer designated valuation is currently due. It does + not assert that the existing value remains realizable. +- When more than one condition could apply, implementations MUST report the first + matching condition in this order: `SUSPENDED` (deliberate), `DISPUTED` (contested), + `DEGRADED` (quality bounds breached), `DELAYED` (overdue), then + `UPDATING`/`EXPECTED_NO_UPDATE`. +- `sourceId` MUST stably identify the instrument, quote currency, source, and + valuation methodology. It is RECOMMENDED to derive it as `keccak256` of a canonical + UTF-8 string `"|||"`; identifiers + derived otherwise are issuer-local, and consumers MUST NOT assume comparability + across issuers unless the derivation is known. Consumers MUST NOT apply the returned + condition to any other feed, stream, oracle, or methodology. + +The distinction between `EXPECTED_NO_UPDATE` (stale because no update is due) and +`DELAYED`/`DEGRADED` (stale for an unexpected reason) is the load-bearing distinction a +raw feed `updatedAt` cannot express. + +### Optional extension: `IAssetPrimaryStatus` + +```solidity +interface IAssetPrimaryStatus { + /// @dev Availability of a primary-market leg. + enum RequestState { + UNKNOWN, // 0 + NOT_APPLICABLE, // this leg does not exist for this program + ACCEPTING, // requests currently accepted + RESTRICTED, // accepted with additional constraints in force + CLOSED, // outside scheduled window + SUSPENDED // deliberately suspended + } + + struct LegStatus { + RequestState state; + uint64 statusAsOf; + uint64 nextScheduledChange; // 0 = unknown + uint64 nextCutoff; // next request cutoff (e.g. NAV cutoff; 0 = n/a) + } + + /// @notice Availability of primary issuance and redemption. + /// @dev A view of availability, not entitlement: the reported state reflects the + /// program's most-permissive authorized participant class (which may be a + /// restricted set such as authorized participants only). ACCEPTING does not + /// imply the caller — or any retail holder — is authorized (KYC/AP gating is + /// out of scope; see ERC-7943/3643). + function primaryStatus() + external + view + returns (LegStatus memory issuance, LegStatus memory redemption); +} +``` + +### Interface Detection + +Compliant tokens MUST implement [ERC-165](./eip-165.md) and return `true` for `IAssetStatus`. Tokens +implementing optional extensions MUST return `true` for their interface IDs. + +Each interface declares exactly one parameterless function, so each interface ID equals +that function's selector (verified with `type(I).interfaceId`): + +```text +IAssetStatus 0xfecd6b9b +IReferenceMarketStatus 0xfe1d1980 +IReferenceValuationStatus 0x7d9b41ad +IAssetPrimaryStatus 0x4ba96385 +``` + +### Proxies and upgrades + +For upgradeable tokens, [ERC-165](./eip-165.md) answers and interface behavior MUST reflect the active +implementation. An upgrade that removes an interface MUST stop advertising it. Freshly +initialized or migrated storage MUST decode as `UNKNOWN` states (guaranteed by the +`UNKNOWN = 0` rule), never as `ACTIVE`/`NORMAL`/`REGULAR`. + +### Events + +This ERC defines no normative events. Session transitions occur on a schedule without +accompanying transactions, so event emission at transition time cannot be required of +any implementation. Implementations MAY emit issuer-defined events on lifecycle, +program, valuation, or primary-window changes; consumers MUST NOT rely on events for +status and MUST query the views. + +### Consumer guidance (non-normative) + +**Account abstraction.** Under [ERC-4337](./eip-4337.md) validation rules, reading +these views from a validation-phase module touches storage not associated with the +account and will cause bundler simulation to reject the operation. Policy modules +SHOULD query these views in execution-phase hooks, or preflight offchain against a +single block tag and re-check in execution. + +**Joint readings.** These interfaces are designed to be read together. Canonical +encodings for common scenarios: + +| Real-world state | Session | Interruption | Valuation | Lifecycle | +|---|---|---|---|---| +| US equity, weekend | CLOSED | NONE | EXPECTED_NO_UPDATE | ACTIVE | +| Asian venue, lunch break | CLOSED | NONE | EXPECTED_NO_UPDATE | ACTIVE | +| Opening/closing auction | AUCTION | NONE | UPDATING or EXPECTED_NO_UPDATE | ACTIVE | +| Single-stock news halt | REGULAR | ASSET_HALTED | EXPECTED_NO_UPDATE | ACTIVE | +| Limit-down lock, no bids | REGULAR | PRICE_CONSTRAINED | UPDATING | ACTIVE | +| Market-wide circuit breaker | REGULAR | VENUE_HALTED | EXPECTED_NO_UPDATE | ACTIVE | +| Delisting wind-down | UNKNOWN | UNKNOWN | SUSPENDED | SETTLEMENT_PENDING | +| NAV fund between cutoffs | (omit) | (omit) | EXPECTED_NO_UPDATE | ACTIVE | +| Broken/stale feed, market open | REGULAR | NONE | DELAYED or DEGRADED | ACTIVE | + +The joint tuple is where interoperability lives: implementations SHOULD follow these +encodings so that identical real-world states are represented identically across +issuers. + +## Rationale + +- **Views on the token, not a feed or API.** Consumers hold a token address. Requiring + them to locate a proprietary oracle registry or offchain API per issuer is the status + quo this ERC removes. Delegation lets issuers keep one calendar contract per venue. +- **`UNKNOWN = 0` everywhere.** The zero value is what uninitialized storage, failed + delegatecalls, and ABI-decode defaults produce. Making zero mean "no information" + converts several classes of implementation bugs from silent mis-signals into explicit + unknowns. +- **Session/interruption and lifecycle/program are orthogonal pairs.** Flattened enums + force lossy encodings when two dimensions apply simultaneously — the + halted-during-regular-hours case is precisely the case integrators most need to see + clearly. +- **`AUCTION` and `PRICE_CONSTRAINED` earn their place.** Auction phases occur daily on + major venues (opening/closing auctions, volatility interruptions, periodic-auction + securities), and price-limit locks can persist for hours on venues with daily limit + mechanisms; without these values, both states are indistinguishable from healthy + continuous trading — the most dangerous possible misreading. +- **Generalized valuation conditions.** `EXPECTED_NO_UPDATE`/`DELAYED` generalize to + NAV-cutoff funds, commodities, and any source with a declared update policy, rather + than hard-coding equity market hours. +- **No aggregate verdict.** A single traffic-light summary was considered and removed: + safety is a function of the consumer's use case. The ERC supplies facts, not + verdicts. +- **No corporate-actions interface.** Corporate-action *economics* are ERC-8056's + domain (splits) and a future ERC's domain (mergers, spinoffs). This ERC exposes their + operational shadow (`SETTLEMENT_PENDING`, `SUSPENDED`) without duplicating + scheduling semantics. +- **Non-equivalence.** This ERC is not a compliance standard (ERC-7943, ERC-3643 govern + who may transfer), not a display standard (ERC-8056 governs split multipliers), and + not an oracle report format (feed-level market status may *source* an implementation + but is not token-level). + +## Backwards Compatibility + +Pure extension; no [ERC-20](./eip-20.md) behavior is modified. + +## Test Cases + +Reference implementations should include tests covering: zeroed proxy storage decoding +to `UNKNOWN`; exact session-transition boundaries; malformed or reverting delegated +providers surfacing as `UNKNOWN`; enum-range validation; [ERC-165](./eip-165.md) IDs and view gas +ceilings; and upgrade paths that add or remove extensions. + +## Security Considerations + +1. **Status is issuer-asserted.** A malicious or negligent issuer can report `NORMAL` + while suspended. Consumers MUST treat these views as advisory inputs to risk logic, + not proofs, and SHOULD bound trust with independent checks — `marketId` exists so + that session claims can be cross-checked against public venue calendars. +2. **Conservative `UNKNOWN` handling.** Consumers MUST map `UNKNOWN` and zero + timestamps to their most conservative path. +3. **Status staleness.** `AsOf` fields exist because the status itself can rot. + Consumers SHOULD apply their own maximum-age policies per field. +4. **Calendar drift.** Published schedules drift (DST, ad-hoc holidays, half days). A + wrong `nextScheduledTransition` is worse than `0`; issuers SHOULD return `0` rather + than guess. +5. **Re-query before protected actions.** Multiple offchain status queries SHOULD use + the same explicit block tag; a simulation result is not an execution precondition. + Risk-critical contracts SHOULD query status in the same transaction as the protected + action. +6. **Transition frontrunning.** Status transitions may be traded against. Issuers + SHOULD announce sensitive transitions via scheduled effective times rather than + instant flips. +7. **Enum-range validation.** Consumers decoding these views from low-level calls MUST + range-check enum values; out-of-range values MUST be treated as `UNKNOWN`. +8. **Shared-provider correlation.** A delegated status provider serving many tokens is + a correlated failure and compromise surface: one buggy or captured calendar contract + misreports for every token that delegates to it. Consumers weighting large RWA + portfolios SHOULD account for provider concentration. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md).