Sylox Docs

Contract reference

Contract addresses, functions, types, and error codes, generated from the deployed testnet contracts.

Contract reference

Testnet only. Every address, function, type, and error code below is read from the actual deployed testnet contracts and the SDK's own source at generation time. Nothing on this page is hand-written.

RiskOracle

Testnet contract address: CCENSA5ANAJAFBFOEB7AFVXYLNFORH55IKAPJLZL2W37G6S3QKHYQOON

Sample/testnet deployment only. This address changes on every redeploy. Always read it from this page (regenerated at build time from deployments/testnet.json) rather than copying it into your own code as a literal.

Functions

band

band(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<Band, Error>>>

live

live(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<[SubEpoch, SignalSet, SlotState] | null>>

technical-doc.md Section 5.9 S5, 12.1 (v1.5): read-only, no auth. The newest posted sub-epoch and its effective state. Unlike latest, which keeps returning the newest HOUR's SignalSet (Series's require_holding valuation and every existing integration already depend on that exact meaning, Section 5.9 S5), live surfaces Pending, challengeable sub-epoch data; the app shows it as "Live" next to latest()/score()'s "Confirmed" values. Never used for require_holding or any other payout-adjacent valuation.

ring

ring(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Array<RingSlot>>>

technical-doc.md Section 5.8, 12.1: one storage read, oldest slot first. See storage::get_ring's doc comment for exactly what "oldest first" means once the buffer has wrapped.

score

score(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<RiskScore, Error>>>

technical-doc.md Section 12.1, 6. Review item C1: a PURE read. Returns whatever RiskScore is currently stored, with stale recomputed fresh against the clock (so a score that was fresh when last written but has since gone quiet is reported stale without needing a write to say so). Never calls storage::set_score or any other write; the actual computation happens in recompute_score, called from post_signals, the finality sweep, and finalize_endpoint (see those for exactly when).

Review decision D1: the stored band is always the plain hysteresis band, computed with no knowledge of the event-in- progress flag (see recompute_score), so that flipping the flag off is never confused with a genuine new epoch of evidence for the hysteresis streak. The "forced to at least Distress while in progress" override is applied here instead, on every read, which also makes it take effect immediately on set_event_in_progress with no recompute required.

assets

assets(options?: MethodOptions): Promise<AssembledTransaction<Array<string>>>

latest

latest(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<SignalSet | null>>

migrate

migrate(options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

Issue #12. Storage migration hook for a future upgrade(): runs at most once per CONTRACT_VERSION. Takes no argument - always migrates to this code's OWN current version, never a caller-chosen one (a governor could otherwise mark a future version "already migrated" before the code ever reaches it, permanently blocking that migration). Empty for now - no storage shape change ships with this PR - but every future upgrade that DOES change a stored type's shape migrates existing data here, gated the same way, with its own storage test (existing keys decode under the new types) alongside it.

signals

signals(args: { asset: string | Address; epoch: bigint }, options?: MethodOptions): Promise<AssembledTransaction<SignalSet | null>>

upgrade

upgrade(args: { new_wasm_hash: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 17.3: "Core contracts expose upgrade(wasm_hash), callable only by the governor." Does NOT carry the 14 day timelock Section 17.2 assigns to Upgrade actions - that belongs to Governor (Section 17), which is not built yet (known gap, see deploy/README.md). This is the same direct, immediate governor-gated call every other call here already uses.

version

version(options?: MethodOptions): Promise<AssembledTransaction<number>>

Issue #12. The currently deployed code's own version, bumped by hand alongside each upgrade() this contract ships (see deploy/ README.md's upgrade procedure). Lets anyone confirm what is actually deployed without guessing from a wasm hash.

is_final

is_final(args: { asset: string | Address; epoch: bigint }, options?: MethodOptions): Promise<AssembledTransaction<boolean>>

Review item C5: whether epoch epoch is EFFECTIVELY final right now: its stored state is Final, or it is Pending and pending_until has already passed. false for a missing epoch.

is_stale

is_stale(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<boolean>>

add_asset

add_asset(args: { cfg: AssetConfig }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1. Rejects an asset that is already registered (update_asset is for changing one). Review decision D3: also rejects Reference::Asset (no USD rate is defined anywhere in the spec for an asset pegged reference; see the PR's "Spec deviations").

build_hour

build_hour(args: { asset: string | Address; hour: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 5.9 S4 (v1.5): permissionless. Builds hour if every one of its sub-epochs is Final, permanently missing, or rejected; post_sub_signals already attempts this inline on every post, so this exists for anyone to trigger a build that is ready but that no further posting has happened to trigger automatically (the same role finalize_endpoint plays for the hourly backward finality scan). Returns SubEpochNotReady if at least one sub-epoch is still Pending or Disputed; a never-posted hour (no sub-epoch at all, not even a missing one within an active interval) is equally not ready, since there is nothing to build from.

check_stale

check_stale(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<boolean, Error>>>

Re-review item C7 (lead decision): asset_stale now means "the asset TRANSITIONED into stale", not "a newly observed epoch happened to already be stale on arrival" (the old design's condition, which never fired at all for the realistic "keepers stopped posting" case, since nothing new ever arrives to trigger it). Permissionless: evaluates staleness against the epoch of the STORED score (never newest_final, which can run ahead of the stored score when recompute_score short circuits without writing, e.g. a sticky Event band or not enough history yet), emits asset_stale once if the asset is stale and stale_announced is not already set, sets the flag, and returns the current stale state either way. Calling this again while still stale emits nothing. Every state changing call that touches an asset runs the same check internally (see check_stale_internal), so a late backfill that is stale on arrival also emits, once, without needing anyone to call this explicitly; this function exists for a monitor tha

first_epoch

first_epoch(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<bigint | null>>

PR #25 review: read-only, no auth. The first epoch ever successfully posted for this asset. EventRegistry's own Tier 1 Depeg and IssuerFreeze history baselines need this for the same reason RiskOracle's own score and median_liquidity do: the newest epoch's own absolute number is always far larger than any window length on a real network, so it alone can never tell a brand-new asset from one with years of history. None before the asset's first posting.

set_formula

set_formula(args: { version: number; weights: Array<number>; params: Map<string, bigint> }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 6.2. set_formula rejects any weight set that does not sum to 10,000.

asset_config

asset_config(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<AssetConfig | null>>

newest_epoch

newest_epoch(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<bigint | null>>

PR #15 review, finding F4: read-only, no auth. Wraps the existing storage::get_newest_epoch_pub, which reads the dedicated RingNewest key directly rather than the newest ring POSITION's own stored epoch. The two disagree exactly when the newest position is Empty (its epoch was overturned and not yet reposted): the position's slot reports epoch 0 (empty_slot()'s default), while RingNewest still correctly reports the real newest epoch ever written. EventRegistry needs the latter for its own slot_for_epoch arithmetic (ring()'s layout is "oldest first, ending at RingNewest"); reading the former made every subsequent slot_for_epoch call miss, misclassifying every cure-window epoch as permanently missing.

post_signals

post_signals(args: { keeper: string | Address; asset: string | Address; s: SignalSet }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 5.2, 5.3, 11.3.

Checks keeper eligibility against Staking, applies the full Section 11.3 sanity bounds, cross checks configured PriceAdapters, and sources endpoint only from Staking.aggregate (never the keeper's payload). s.endpoint is only ever a placeholder on input.

update_asset

update_asset(args: { asset: string | Address; cfg: AssetConfig }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1: "rejects a change to cfg.reference". Review decision D3: also rejects Reference::Asset, same as add_asset (an existing asset can never have had this reference in the first place, since add_asset rejects it, but the check is repeated here rather than relying on that invariant holding forever).

disable_asset

disable_asset(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1.

reference_rate

reference_rate(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<bigint, Error>>>

technical-doc.md Section 12.1: reference to USD, SCALE 1e7. Usd is always SCALE; Fiat reads the asset's FxAdapter on the reference's recorded basis and fails closed on a stale or missing rate; Asset has no USD rate defined anywhere in the spec, so this fails rather than guessing one (see the PR's "Spec deviations").

set_event_band

set_event_band(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 8.5, 8.8. Auth: the registry contract itself (the registry address RiskOracle was initialized with), per Section 16.1's contract-to-contract auth pattern.

sub_peg_ratios

sub_peg_ratios(args: { asset: string | Address; hour: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Array<bigint | null>>>

technical-doc.md Section 5.9 S5 (v1.5): read-only, no auth. Each of hour's sub-epochs' own peg_ratio, in sub order, None for a sub-epoch that is not posted or not yet effectively Final or Pending (the same "Pending or Final" convention EventRegistry.cover_gate's existing RecentDepeg check already reads from Ring(asset), extended to Sub(asset) for an hour that has not built yet). EventRegistry's own cover_gate calls this only for the current, not-yet-built hour (or hours, in the up-to-2h window an hour can stay waiting): every already-built hour inside its own trailing window is still read through ring() exactly as before this revision.

dispute_signals

dispute_signals(args: { disputer: string | Address; asset: string | Address; epoch: bigint; alt_hash: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 5.4, 7.8.

Records the dispute and locks the disputer's bond in Staking. Only the signal dispute STATE lives here; the bond itself is held and later split by Staking on instruction from resolve_signal_dispute.

clear_event_band

clear_event_band(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 8.8.

effective_window

effective_window(args: { asset: string | Address; start_epoch: bigint; count: number }, options?: MethodOptions): Promise<AssembledTransaction<Array<SlotState | null>>>

Review item C5: a window read of effective state per epoch, for EventRegistry's Tier 1 checks (Section 8.2), so it can learn which epochs in a window are final without one call per epoch. Missing epochs read None, the same convention as ring's underlying storage.

median_liquidity

median_liquidity(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<bigint>>

technical-doc.md Section 11.1: median of the newest 168 slots' liquidity_2pct, for the asset wide cover cap. Missing slots in the window are excluded, not treated as zero liquidity, so a short run of missing epochs does not crater the cap the way real zero liquidity would.

post_sub_signals

post_sub_signals(args: { keeper: string | Address; asset: string | Address; hour: bigint; sub: number; s: SignalSet }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 5.9 S2 (v1.5). Posts a sub-epoch, keyed by (hour, sub) instead of a single epoch. Mirrors post_signals's own checks (keeper eligibility, sanity bounds, AMM cross check), disputable for signal_dispute_secs exactly as the hourly path already specifies. Recomputes the hour's own provisional roll-up on every call, and attempts the hour's build inline, the same way post_signals already inline-triggers the hourly finality scan.

sub_epoch_config

sub_epoch_config(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<SubEpochConfig | null>>

technical-doc.md Section 5.9 S1 (v1.5): read-only, no auth. The asset's sub-epoch configuration, including a pending change not yet in effect.

event_in_progress

event_in_progress(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<boolean>>

technical-doc.md Section 12.1 (feat/event-registry design note, review item D6): read-only, no auth. Lets EventRegistry assert invariant E4 (its own active-event count agrees with this flag) directly, rather than inferring the flag only through its one visible effect on band()'s own Distress floor.

finalize_endpoint

finalize_endpoint(args: { asset: string | Address; epoch: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 7.4. Reads Staking.aggregate and books it into the epoch's ring slot and Signals entry if post_signals had not already resolved one by the time it closed; then calls Staking.settle_probes exactly once regardless, per Section 7.4's "booked exactly once" requirement.

overturned_signals

overturned_signals(args: { asset: string | Address; epoch: bigint }, options?: MethodOptions): Promise<AssembledTransaction<SignalSet | null>>

Review item C4: the SignalSet an overturned epoch's posting had before resolve_signal_dispute moved it out of signals's live key, kept for audit. None if epoch was never overturned (or was overturned, reposted, and overturned again, which keeps only the most recent overturn, not a full history of every attempt).

set_sub_epoch_secs

set_sub_epoch_secs(args: { asset: string | Address; value: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 5.9 S1, 16, 17.4 (v1.5). Auth: governor, the same SetParam-style path every other parameter in Section 23 already uses; no new role or action type is needed. Takes effect from the NEXT hour boundary only (never the current, possibly-in-progress hour), so no sub-epoch already posted, or postable before that boundary, is ever reinterpreted under a different length.

dispute_sub_signals

dispute_sub_signals(args: { disputer: string | Address; asset: string | Address; hour: bigint; sub: number; alt_hash: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 5.9 S2, S3 (v1.5). Mirrors dispute_signals exactly, against Sub(asset)'s own ring: on dispute, the sub-epoch's record is copied out to SubDispute(asset, hour, sub) (so Sub(asset)'s fixed 60 slot ring can keep rotating underneath a ruling that takes up to SIGNAL_DISPUTE_RULING_SECS), and the HOUR's own Ring(asset) slot moves to Disputed via refresh_waiting_hour.

set_event_in_progress

set_event_in_progress(args: { asset: string | Address; in_progress: boolean }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

Review decision D1, not in the original Section 12.1 list. Auth: the registry contract, same pattern as set_event_band / clear_event_band (Section 16.1). While true, score() floors the band at Distress on every read (Section 6.3's "forced to Distress if a credit event... is Proposed, Challenged or Escalated"), independent of the hysteresis-protected band actually stored; RiskOracle never calls into EventRegistry to check this itself, matching the instruction that "the oracle never calls the registry" - this is pushed in, the same direction set_event_band already works.

resolve_signal_dispute

resolve_signal_dispute(args: { asset: string | Address; epoch: bigint; keeper_wins: boolean; reason: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 12.1, 5.4, 7.8. Auth: the committee address, read fresh from Governor.committee() on every call (the committee can rotate; RiskOracle never caches it).

resolve_sub_signal_dispute

resolve_sub_signal_dispute(args: { asset: string | Address; hour: bigint; sub: number; keeper_wins: boolean; reason: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 5.9 S2, S4 (v1.5). Mirrors resolve_signal_dispute exactly, against Sub(asset). On keeper_wins, the sub-epoch's slot becomes Final; on the disputer winning, it clears back to Empty (the overturned sub-epoch's own SubSignals entry is left in place for audit, the same way Signals(asset, epoch) is kept but no longer live after an hourly overturn) and can be reposted. Either way, refresh_waiting_hour re-derives the HOUR's own Ring(asset) state from every sub-epoch's current disposition, which may move the hour out of Disputed (if no other sub-epoch in it is still disputed) and, if this was the last undecided sub-epoch, trigger the hour's build.

resolve_sub_dispute_timeout

resolve_sub_dispute_timeout(args: { asset: string | Address; hour: bigint; sub: number }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 5.9 S2 (v1.5). Mirrors resolve_signal_dispute_timeout exactly, against SubDispute(asset, hour, sub): the committee's silence is read the same way for a sub-epoch dispute as for an hourly one, the keeper's posting stands.

sub_peg_ratios_in_span_batch

sub_peg_ratios_in_span_batch(args: { asset: string | Address; hours: Array<bigint> }, options?: MethodOptions): Promise<AssembledTransaction<Array<Array<bigint | null>>>>

Section 5.9 S5 (v1.5, footprint-fix revision): EventRegistry. cover_gate's own RecentDepeg check, for unbuilt hours still INSIDE Sub(asset)'s own 5-hour span (SUB_RING_SLOTS * SUB_EPOCH_GRID_SECS), never calling this for an hour outside it (the caller's own job to tell apart, using newest_epoch and those same constants: this function trusts its caller completely and does not re-check). Unlike sub_peg_ratios / the since-removed sub_peg_ratios_batch, this NEVER reads HeldHour: every hour it is asked about is assumed still physically present in Sub(asset)'s own ring, so HeldHour (a write-path, build/dispute-only concern, never a gate concern, per the Section 5.9 S5 footprint-fix review) plays no part here. One cross-contract call for every hour the gate needs, touching exactly ONE key (Sub(asset), read once regardless of how many hours or sub-epochs are requested) rather than one key per hour: this is what makes the gate's own footprint constant regardless of how many hours are unbuilt (R10

resolve_signal_dispute_timeout

resolve_signal_dispute_timeout(args: { asset: string | Address; epoch: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

ADR-010 (issue #4 fix). Permissionless, callable once SIGNAL_DISPUTE_RULING_SECS has passed since dispute_signals opened this dispute, if the committee still has not ruled via resolve_signal_dispute. Default outcome mirrors ADR-002's rule for data backed claims: the keeper's posting stands (slot Final, same as a keeper_wins: true committee ruling). "Both bonds released in full, nobody slashed" (the task's own wording): there is only one actual BondKey::SignalDispute lock to release, the disputer's (technical-doc.md Section 24.2 notes a keeper has no symmetric per-signal bond in v1; only slash, by address, ever touches a keeper's stake), so in practice this means the disputer's bond is refunded (release_bond, not forfeit_bond) AND the keeper's stake is left untouched (no slash call either) - both parties come out exactly as they would from a keeper_wins: true ruling, which is the committee's silence being read as "no evidence the posting was wrong", not as a loss for either side.

Types

HourBuiltEvent

Struct.

technical-doc.md Section 5.9 S4, Section 13.

AssetStaleEvent

Struct.

Event: AssetStale

BandChangedEvent

Struct.

Event: BandChanged

ScoreUpdatedEvent

Struct.

Event: ScoreUpdated

SignalsFinalEvent

Struct.

Event: SignalsFinal

SignalsPostedEvent

Struct.

Event: SignalsPosted

SignalsDisputedEvent

Struct.

Event: SignalsDisputed

SignalsResolvedEvent

Struct.

Event: SignalsResolved

SubSignalsFinalEvent

Struct.

Event: SubSignalsFinal

SubSignalsPostedEvent

Struct.

technical-doc.md Section 5.9 (v1.5), Section 13.

EndpointFinalizedEvent

Struct.

Event: EndpointFinalized

SubSignalsDisputedEvent

Struct.

Event: SubSignalsDisputed

SubSignalsResolvedEvent

Struct.

Event: SubSignalsResolved

SubEpochSecsChangedEvent

Struct.

technical-doc.md Section 5.9 S1, Section 13.

SignalDisputeTimedOutEvent

Struct.

ADR-010 (feat/staking, issue #4 fix).

SubSignalDisputeTimedOutEvent

Struct.

Event: SubSignalDisputeTimedOut

Band

Union/type alias.

Union: Band

RiskScore

Struct.

The latest computed risk score for an asset. technical-doc.md Section 4.2.

RingSlot

Struct.

One epoch slot of the per asset ring buffer that Tier 1 checks, the cover gate and the 24 hour and 7 day aggregates read in a single entry, instead of separate Signals(asset, epoch) entries. technical-doc.md Section 5.8, ADR-005.

SubEpoch

Struct.

Identifies one sub-epoch at the posting/dispute API boundary: hour, and sub, its position within that hour under whichever sub_epoch_secs governed it. technical-doc.md Section 5.9 S2. Sub(asset)'s own ring stores a different identity internally (sub_start, the sub-epoch's absolute start time, Section 5.9 S3), since what sub means depends on an interval that can later change; SubEpoch is the human-meaningful pair a keeper posts against, converted internally to sub_start.

Reference

Union/type alias.

Union: Reference

SignalSet

Struct.

One epoch's measured signals for one asset. technical-doc.md Section 4.1.

SlotState

Union/type alias.

Finality of one ring buffer slot. technical-doc.md Section 5.8.

AssetConfig

Struct.

Configuration for one issued asset covered by the RiskOracle. technical-doc.md Section 4.1.

IssuerFlags

Struct.

The classic issuer flags that make an issuer freeze possible. An IssuerFreeze definition can be registered only if at least one is set. technical-doc.md Section 4.1, ADR-006.

FxRateSource

Union/type alias.

Which FX rate a Reference::Fiat is priced against. Matters wherever an official rate and a market (parallel) rate diverge, for example ARS. technical-doc.md Section 4.1, ADR-006.

IssuerActions

Struct.

Struct: IssuerActions

EndpointStatus

Union/type alias.

Union: EndpointStatus

SubEpochConfig

Struct.

Per asset sub-epoch configuration. technical-doc.md Section 15.1 SubEpochConfig(asset), Section 5.9 S1. sub_epoch_secs is the value currently in effect; pending_sub_epoch_secs and effective_from_hour describe a queued change that has not yet taken effect (both None when no change is pending). A change never applies before effective_from_hour, so no sub-epoch already posted, or postable before that boundary, is ever reinterpreted under a different length.

UpgradedEvent

Struct.

technical-doc.md Section 17.3. contract is env.current_contract_ address(), so an indexer watching this topic across every Sylox contract can tell which one just upgraded without decoding the event's own emitting address separately.

Contract error codes

Raw #[contracterror] codes as returned by the contract itself (its own doc-comment text, not the plain-English copy a caller sees. See "SDK error messages" below for that).

CodeNameNotes
1AlreadyInitialized-
2NotInitialized-
3UnauthorizedNever actually returned by any call in this contract: every authorization check (add_asset, update_asset, set_formula, post_signals, dispute_signals, resolve_signal_dispute, set_event_band, clear_event_band, set_event_in_progress) goes through Soroban's native Address::require_auth(), which traps the host call directly rather than returning a Result this contract could wrap in Error::Unauthorized. Kept for technical-doc.md Section 14 code-number compatibility; see missing_auth_traps_natively_rather_than_returning_unauthorized in test.rs and the PR's "Review fixes" section for why this is documented as unreachable rather than tested as reachable.
4Paused-
5MathOverflow-
100UnknownAsset-
101KeeperNotActive-
102WrongEpoch-
103EpochAlreadyPosted-
104SanityBoundFailed-
105AmmCrossCheckFailed-
106DisputeWindowClosed-
107WeightsInvalid-
108ReferenceImmutable-
109ReferenceRateUnavailable-
110AggregationFailedDistinct from SanityBoundFailed (104), which is about one posted SignalSet's fields; this is about computing the score from the ring (not enough history, or a required window read came back empty), a different failure mode a caller may want to handle differently (for example: retry later vs. a permanently bad posting). Review item "Aggregation failures must not reuse SanityBoundFailed."
111ReferenceNotSupportedadd_asset / update_asset reject Reference::Asset in v1 (review decision D3): no USD rate is defined anywhere in the spec for an asset pegged reference (see the PR's "Spec deviations").
112RulingDeadlineNotReachedADR-010 (feat/staking, issue #4 fix): resolve_signal_dispute_timeout called before SIGNAL_DISPUTE_RULING_SECS has passed since the dispute opened. Named to match ADR-002's RulingDeadlineNotReached precedent for the analogous event-ruling timeout.
113InvalidSubEpochIntervaltechnical-doc.md Section 5.9 S1: sub_epoch_secs set to a value outside the allowed set, or one that does not divide 3,600 evenly.
114SubEpochNotReadytechnical-doc.md Section 5.9 S4: build_hour called while at least one of the hour's sub-epochs is still Pending or Disputed, i.e. not yet Final, permanently missing, or rejected.
115HourAlreadyPostedtechnical-doc.md Section 5.9 S2: an hour is posted through exactly one path, sub-epoch or hourly fallback, never both. A sub-epoch post against an hour already posted through the fallback path, or a fallback post against an hour that already has a sub-epoch posted, both return this.
116AlreadyMigratedIssue #12: migrate called with a target_version already applied (or an older one). Each storage-shape migration runs at most once; a second call for the same (or an earlier) version is a no-op request, not a retry of something that failed.

EventRegistry

Testnet contract address: CBKLNYRCZJXMKZUPZRFEZU4SALCOUGXF7MX766ZHVFRH5UN5WXLVDPGG

Sample/testnet deployment only. This address changes on every redeploy. Always read it from this page (regenerated at build time from deployments/testnet.json) rather than copying it into your own code as a literal.

Functions

rule

rule(args: { event_id: bigint; declare: boolean; reason: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

event

event(args: { event_id: bigint }, options?: MethodOptions): Promise<AssembledTransaction<EventRecord | null>>

covers

covers(args: { event_id: bigint; def_version: number; start: bigint; expiry: bigint }, options?: MethodOptions): Promise<AssembledTransaction<boolean>>

technical-doc.md Section 8.6.

migrate

migrate(options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

Issue #12. Storage migration hook for a future upgrade(): runs at most once per CONTRACT_VERSION. Takes no argument - always migrates to this code's OWN current version, never a caller-chosen one (a governor could otherwise mark a future version "already migrated" before the code ever reaches it, permanently blocking that migration). Empty for now - no storage shape change ships with this PR - but every future upgrade that DOES change a stored type's shape migrates existing data here, gated the same way, with its own storage test (existing keys decode under the new types) alongside it.

upgrade

upgrade(args: { new_wasm_hash: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 17.3: "Core contracts expose upgrade(wasm_hash), callable only by the governor." After 12 months on mainnet, Section 17.3 also notes governance plans to remove upgrade from EventRegistry entirely (deploying v2 alongside instead); not relevant yet on testnet.

version

version(options?: MethodOptions): Promise<AssembledTransaction<number>>

Issue #12. The currently deployed code's own version, bumped by hand alongside each upgrade() this contract ships (see deploy/ README.md's upgrade procedure). Lets anyone confirm what is actually deployed without guessing from a wasm hash.

finalize

finalize(args: { event_id: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

technical-doc.md Section 8.1, as corrected by design review items D1 (cure window), D2/R2/R3 (three-way epoch state) and R1 (cure strictness). See docs/design/event-registry.md Section 5.

challenge

challenge(args: { challenger: string | Address; event_id: bigint; evidence: Uint8Array }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

cover_gate

cover_gate(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<Result<CoverGate, Error>>>

technical-doc.md Section 9.4 step 2, ADR-003. First-match order. Thresholds and windows come from the asset's own canonical Depeg definition where one is registered; the Section 23 default applies otherwise (matching Section 9.4's own stated fallback rule verbatim). WithdrawalHalt is out of scope for this build (no canonical definition of that kind can exist yet), so its own window always uses the Section 23 default.

definition

definition(args: { asset: string | Address; kind: EventKind; version: number }, options?: MethodOptions): Promise<AssembledTransaction<EventDefinition | null>>

in_progress

in_progress(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<boolean>>

event_status

event_status(args: { asset: string | Address; kind: EventKind }, options?: MethodOptions): Promise<AssembledTransaction<AssetEventStatus>>

has_declared

has_declared(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<boolean>>

propose_tier1

propose_tier1(args: { caller: string | Address; asset: string | Address; kind: EventKind; version: number }, options?: MethodOptions): Promise<AssembledTransaction<Result<bigint, Error>>>

technical-doc.md Section 8.2, as amended by this build's lead decision (design note Section 2) and design review item D3.

checkpoint_cure

checkpoint_cure(args: { event_id: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<CureProgress, Error>>>

PR #15 review, finding F2. Permissionless (no require_auth): anyone, including the keeper service, may call this to record a Depeg event's cure-window progress before an epoch that is already decidable rotates out of RiskOracle's own ring. Unlike finalize, this never errors on "not ready" - recording whatever is currently decidable, and keeping that record, is itself the successful outcome; there being more to record later is not a failure. Calling it twice in a row is a no-op the second time (record_cure_progress's own recorded-bit check).

current_version

current_version(args: { asset: string | Address; kind: EventKind }, options?: MethodOptions): Promise<AssembledTransaction<number>>

resolve_timeout

resolve_timeout(args: { event_id: bigint }, options?: MethodOptions): Promise<AssembledTransaction<Result<null, Error>>>

ruling_deadline

ruling_deadline(args: { event_id: bigint }, options?: MethodOptions): Promise<AssembledTransaction<bigint | null>>

committee_misses

committee_misses(args: { committee: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<number>>

active_event_count

active_event_count(args: { asset: string | Address }, options?: MethodOptions): Promise<AssembledTransaction<number>>

register_definition

register_definition(args: { def: EventDefinition }, options?: MethodOptions): Promise<AssembledTransaction<Result<number, Error>>>

technical-doc.md Section 8.8. def.version is ignored on input (the caller cannot pick a version number); the stored version is always the previous canonical version plus one, starting at 1.

Types

EventCuredEvent

Struct.

Event: EventCured

EventDeclaredEvent

Struct.

Event: EventDeclared

EventProposedEvent

Struct.

Event: EventProposed

EventRejectedEvent

Struct.

Event: EventRejected

EventEscalatedEvent

Struct.

Event: EventEscalated

RulingTimedOutEvent

Struct.

Event: RulingTimedOut

EventChallengedEvent

Struct.

Event: EventChallenged

DefinitionRegisteredEvent

Struct.

Event: DefinitionRegistered

CureProgress

Struct.

PR #15 review, finding F2. Bit i of recorded is set once the cure-window epoch first_cure_epoch + i has been observed Final or PermanentlyMissing at least once; any_below_threshold/ any_missing are the OR of every such observation so far. A u128 bitmap comfortably covers the up-to-72 cure epochs challenge_secs can specify (finding F3's own bound), with headroom to spare. Deliberately never clears a bit once set: a disposition, once observed Final or PermanentlyMissing, cannot change (an epoch that is genuinely Final never reverts to Pending or Disputed, and PermanentlyMissing is a statement about elapsed time, which never un-elapses), so recording is monotonic and checkpoint_cure is naturally idempotent.

Reference

Union/type alias.

Union: Reference

FxRateSource

Union/type alias.

Which FX rate a Reference::Fiat is priced against. Matters wherever an official rate and a market (parallel) rate diverge, for example ARS. technical-doc.md Section 4.1, ADR-006.

CoverGate

Union/type alias.

Result of EventRegistry::cover_gate(asset): whether new cover may be bought on an asset right now (ADR-003). technical-doc.md Section 9.4.

EventKind

Union/type alias.

Union: EventKind

EventState

Union/type alias.

Union: EventState

EventRecord

Struct.

One proposed credit event, keyed by (asset, kind, def_version) (ADR-001). technical-doc.md Section 4.3.

EventDefinition

Struct.

The canonical, versioned rules for one event kind on one asset. Stored by (asset, kind, version); exactly one version per (asset, kind) is canonical at a time (ADR-001). technical-doc.md Section 4.3.

Parameters that do not apply to kind must be zero; register_definition rejects a definition that sets them.

AssetEventStatus

Union/type alias.

Returned by EventRegistry::event_status(asset, kind), which is per (asset, kind) for the canonical version (ADR-001). technical-doc.md Section 12.2.

UpgradedEvent

Struct.

technical-doc.md Section 17.3. contract is env.current_contract_ address(), so an indexer watching this topic across every Sylox contract can tell which one just upgraded without decoding the event's own emitting address separately.

Contract error codes

Raw #[contracterror] codes as returned by the contract itself (its own doc-comment text, not the plain-English copy a caller sees. See "SDK error messages" below for that).

CodeNameNotes
1AlreadyInitialized-
2NotInitialized-
3UnauthorizedNever actually returned: every authorization check goes through Soroban's native Address::require_auth(), which traps the host call directly rather than returning a Result this contract could wrap. Kept for cross-contract error code compatibility, the same reasoning RiskOracle/Staking/ Treasury document for their own copies of this code.
4PausedNot currently reachable: this contract has no pause integration in this build (Section 16.2 lists no EventRegistry-specific pause scope). Kept for code number compatibility.
5MathOverflowChecked arithmetic failed: the IssuerFreeze check's own running clawback-amount and revocation-count sums over its 7 day window, and the clawback ratio's own scale-up before dividing by supply. Bounded by realistic USDC amounts in practice (each term already passed RiskOracle.post_signals's own sanity bounds), but checked rather than assumed, matching Staking's/Treasury's own convention.
200UnknownDefinitionNo EventDefinition stored under the requested (asset, kind, version).
201EventInProgresspropose_tier1 while a non-terminal event already exists for this (asset, kind, version) (invariant E3).
202Tier1CheckFailedA Tier 1 proposal's ring-buffer check did not meet the definition: too many missing epochs, a present epoch past threshold, or a liquidity baseline below min_liquidity (design note Section 3).
203InsufficientBondchallenge's bond lock in Staking failed. Not reachable as built: challenge calls Staking.lock_bond directly (not a try_* variant), so a real failure on Staking's own side (for example, the challenger's USDC balance too low for the transfer) traps the call rather than returning a Result this contract could translate into its own error code, the same reasoning every other cross-contract call in this workspace follows. Kept for code number compatibility.
204ChallengeWindowOpenfinalize called before challenge_secs has elapsed since proposed_at.
205ChallengeWindowClosedchallenge called after challenge_secs has elapsed, or against an event no longer Proposed.
206WrongStateA function's own required state (Proposed, Escalated, ...) does not match the event's current one.
207CooldownActiveUnreachable as built (design note review item D4): a fixed post-resolution cooldown was in the original spec design but is replaced here by a new-data test (window_start > left_at), which propose_tier1 enforces itself rather than returning a distinct "still cooling down" code. Kept for code number compatibility.
208InvalidDefinitionregister_definition's own validation failed: asset unknown, reference mismatch, a parameter unused by kind is non zero, or the window plus baseline does not fit the ring buffer.
209FreezeImpossibleAn IssuerFreeze definition for an asset whose issuer_flags allow neither revocation nor clawback.
210DefinitionInUseA live series still pins the version register_definition would supersede. Unreachable in this build (no MarketFactory to report a live series at all, so this check never finds one to object to); kept for code number compatibility and for the day MarketFactory exists.
211RulingDeadlinePassedrule called after the ruling deadline; use resolve_timeout.
212RulingDeadlineNotReachedresolve_timeout called before the ruling deadline.
213DataNotFinalNew (feat/event-registry, design note review item D2/R2/R3): finalize on a Depeg proposal whose cure window still has an epoch that is neither effectively Final nor permanently missing (still Pending before its own pending_until, or Disputed and unresolved, or Empty but still inside its own backfill window). Not a failure: callable again once the data settles one way or the other.
214VersionNotCoveredNew (feat/event-registry, design note review item D3): version is registered but not acceptable: neither the current canonical version for (asset, kind) nor pinned by any live series (the latter check is a stand-in returning false until MarketFactory exists, Section 2 of the design note).
215UnknownEventNew (feat/event-registry): challenge, finalize, rule or resolve_timeout against an event_id with no stored EventRecord. Kept distinct from UnknownDefinition (no canonical definition, or no such version), since these are different questions: one is about a DEFINITION, the other about a specific PROPOSAL.
216AlreadyMigratedIssue #12: migrate called with a target_version already applied (or an older one). Each storage-shape migration runs at most once; a second call for the same (or an earlier) version is a no-op request, not a retry of something that failed.

SDK error messages

@sylox/sdk (packages/sdk/src/errors.ts) wraps each contract call with a plain-English message, keyed by the same numeric code, in a thrown SyloxError. These are the messages an app built on the SDK actually shows, not the raw contract error name above.

finalizeError (EventRegistry.finalize)

CodeContract error nameMessage shown to a caller
2NotInitializedThe protocol isn't ready yet. Try again in a moment.
200UnknownDefinitionThis event's rules (its definition) couldn't be found.
204ChallengeWindowOpenThe challenge window for this event is still open.
206WrongStateThis event has already moved past this step.
213DataNotFinalThis event's price data isn't fully settled yet. Try again once the pending hours finalize.
215UnknownEventThis event no longer exists.

checkpointCureError (EventRegistry.checkpoint_cure)

CodeContract error nameMessage shown to a caller
2NotInitializedThe protocol isn't ready yet. Try again in a moment.
200UnknownDefinitionThis event's rules (its definition) couldn't be found.
206WrongStateThis event has already moved past this step, or doesn't use a cure check.
215UnknownEventThis event no longer exists.

scoreError (RiskOracle.score)

CodeContract error nameMessage shown to a caller
100UnknownAssetThis asset isn't tracked by the oracle.

coverGateError (EventRegistry.cover_gate)

CodeContract error nameMessage shown to a caller
2NotInitializedThe protocol isn't ready yet. Try again in a moment.

proposeTier1Error (EventRegistry.propose_tier1)

CodeContract error nameMessage shown to a caller
2NotInitializedThe protocol isn't ready yet. Try again in a moment.
200UnknownDefinitionThis asset has no canonical Depeg definition registered yet.
201EventInProgressAn event is already open for this asset and kind.
202Tier1CheckFailedThe ring buffer data doesn't meet the definition's own threshold for this proposal.
214VersionNotCoveredThis definition version isn't the current canonical one and isn't pinned by any live cover.

challengeError (EventRegistry.challenge)

CodeContract error nameMessage shown to a caller
2NotInitializedThe protocol isn't ready yet. Try again in a moment.
205ChallengeWindowClosedThe challenge window for this event has already closed.
206WrongStateThis event can no longer be challenged - it has moved past that step.
215UnknownEventThis event no longer exists.

On this page

Contract referenceRiskOracleFunctionsbandliveringscoreassetslatestmigratesignalsupgradeversionis_finalis_staleadd_assetbuild_hourcheck_stalefirst_epochset_formulaasset_confignewest_epochpost_signalsupdate_assetdisable_assetreference_rateset_event_bandsub_peg_ratiosdispute_signalsclear_event_bandeffective_windowmedian_liquiditypost_sub_signalssub_epoch_configevent_in_progressfinalize_endpointoverturned_signalsset_sub_epoch_secsdispute_sub_signalsset_event_in_progressresolve_signal_disputeresolve_sub_signal_disputeresolve_sub_dispute_timeoutsub_peg_ratios_in_span_batchresolve_signal_dispute_timeoutTypesHourBuiltEventAssetStaleEventBandChangedEventScoreUpdatedEventSignalsFinalEventSignalsPostedEventSignalsDisputedEventSignalsResolvedEventSubSignalsFinalEventSubSignalsPostedEventEndpointFinalizedEventSubSignalsDisputedEventSubSignalsResolvedEventSubEpochSecsChangedEventSignalDisputeTimedOutEventSubSignalDisputeTimedOutEventBandRiskScoreRingSlotSubEpochReferenceSignalSetSlotStateAssetConfigIssuerFlagsFxRateSourceIssuerActionsEndpointStatusSubEpochConfigUpgradedEventContract error codesEventRegistryFunctionsruleeventcoversmigrateupgradeversionfinalizechallengecover_gatedefinitionin_progressevent_statushas_declaredpropose_tier1checkpoint_curecurrent_versionresolve_timeoutruling_deadlinecommittee_missesactive_event_countregister_definitionTypesEventCuredEventEventDeclaredEventEventProposedEventEventRejectedEventEventEscalatedEventRulingTimedOutEventEventChallengedEventDefinitionRegisteredEventCureProgressReferenceFxRateSourceCoverGateEventKindEventStateEventRecordEventDefinitionAssetEventStatusUpgradedEventContract error codesSDK error messagesfinalizeError (EventRegistry.finalize)checkpointCureError (EventRegistry.checkpoint_cure)scoreError (RiskOracle.score)coverGateError (EventRegistry.cover_gate)proposeTier1Error (EventRegistry.propose_tier1)challengeError (EventRegistry.challenge)