ICRC-72 Extension - ICRC-77 Event Message Replay Interface
Reserved by the Event WG.
| ICRC | Title | Author | Discussions | Status | Type | Category | Created |
|---|---|---|---|---|---|---|---|
| 77 | Event Replay for ICRC‑72 | Austin Fatheree (@skilesare), IC Events WG | https://github.com/dfinity/ICRC/issues/77 | Draft | Standards Track | Protocols | 2025-09-08 |
ICRC‑77: Event Replay for ICRC‑72 (Full Draft)
ICRC‑77 defines a minimal, interoperable mechanism for replaying ICRC‑72 events for subscribers that missed notifications. It standardizes how a requester specifies a namespace and range, how systems procure historical events (potentially from external archives), and how events are resent through existing ICRC‑72 delivery paths. Payment for replay is supported.
The design keeps complexity low by:
- Reusing ICRC‑72 notifications as the data plane (no new delivery endpoint).
- Allowing a control plane via ICRC‑72 system namespaces or optional simple APIs.
- Assigning archival responsibility to Publishers (with hooks to external storage such as ICRC‑3 archives), while Orchestrators coordinate and Broadcasters deliver.
Data Representations
Type definitions
// common
type Namespace = text;
type Timestamp = nat; // UTC nanos
type EventIdentifier = nat; // ICRC‑72 eventId
// replay ids
type MessageFulfillmentId = nat; // ICRC‑77 replay fulfillment identifier
// ICRC16 shapes
type ICRC16Property = record { name : text; immutable : bool; };
type ICRC16MapItem = record { text; ICRC16 };
type ICRC16ValueMapItem = record { ICRC16; ICRC16 };
type ICRC16Map = vec ICRC16MapItem;
type ICRC16 = variant { /* see ICRC‑16 */ };
// Request to replay a range of events in a namespace
// Range is closed‑open [from, opt to]
// If to=null, implementer returns the terminal event id in the first notification header icrc77:replay:end
// so the client can detect completion deterministically.
type ReplayRegistration = record {
namespace : text;
range : record { nat; opt nat };
config : ICRC16Map; // filters, skip, payment hints
memo : blob; // optional 32 bytes
};
// Result for registering one replay request
type ReplayRegisterError = variant {
Unauthorized; // caller not authorized
UnauthorizedSubscriber : record { namespace : Namespace };
ImproperConfig : text; // invalid range/filter/etc.
PaymentRequired : text; // pricing policy requires payment
RangeTooLarge : text; // exceeds capability/limits
GenericError : record { error_code : nat; message : text };
GenericBatchError : text;
PublicationNotFound;
SubscriptionNotFound;
};
type ReplayRegisterResult = opt variant {
Ok : ReplayId;
Err : ReplayRegisterError;
};
// Optional status query type
type ReplayState = variant { pending; running; completed : nat; canceled : nat; errored : text };
type ReplayStatus = record {
replayId : ReplayId;
namespace : text;
config : ICRC16Map;
stats : ICRC16Map;
range : (nat; opt nat;)
status: ReplayState
};
type ReplayStatusUpdate = record{
replayId: ReplayId;
status: ReplayState;
}
type CancelReplayError = variant {
Unauthorized; // caller not authorized
NotFound ;
CannotCancel : text;
GenericError : record { error_code : nat; message : text };
GenericBatchError : text;
}
Replay Headers (on resent notifications)
Replay delivery uses ICRC‑72 EventNotifications. Implementers MUST append (not overwrite) headers:
Publication Registration Configuration
- icrc77:replay = #Bool(true)
Published Event Headers
- icrc77:replay:id = #Nat(replayId)
- icrc77:replay:end_id = #Nat(eventId) - Last expected EventID(optional - only required when a request did not include the max ID)
Methods
Implementations MUST expose a thin API at the Orchestrator and MUST route control over ICRC‑72 system namespaces (see Control Plane). Minimal APIs:
// Register new replay requests; returns IDs or per‑item errors
icrc77_register_replay : (vec ReplayRegistration) -> (vec ReplayRegisterResult);
// Cancel fulfillments; returns the IDs that were accepted for cancel
icrc77_cancel_replay : (vec ReplayId) -> (vec CancelReplayResult);
// Alert orchestrator of replay fulfillment or error
icrc77_replay_status : (vec ReplayStatusUpdate) -> (vec ReplayStatusUpdateResult);
// Optional: inquire status of fulfillments
icrc77_get_replays : ({
prev: opt principal;
take: opt nat;
filter: opt OrchestrationFilter;
}) -> (vec opt ReplayStatus) query;
Control Plane via ICRC‑72 System Namespaces
All orchestration MAY be conducted using ICRC‑72 system messages to keep the surface minimal and auditable. Recommended deterministic namespaces:
-
Orchestrator → Broadcaster:
- icrc72:broadcaster:sys:<broadcaster_principal> - Broadcasters and Broadcaster relays MUST be alerted to their responsibilities before the Publisher is notified of the request.
- Message Data to Assigned Broadcaster:
icrc77:broadcaster:replay:add: #Array[#Array[#Nat, #Text, #Blob, #Blob, (#Option(null) or #Text(filter)), (#Option(null) or #Array(#Nat(skipStart), #Nat(skipEnd))), #Blob]] - ReplayID, Namespace, Relay Broadcaster Principal as blob, Subscriber Principal as blob, Filter (optional), Skip (optional), Publisher Principal as blob(optional). - Message Data to Assigned Relayer:
icrc77:broadcaster:replay_relay:add: #Array[#Array[#Nat, #Text, #Blob, #Blob, (#Option(null) or #Text(filter)), (#Option(null) or #Array(#Nat(skipStart), #Nat(skipEnd)))]] - ReplayID, Namespace, Target Broadcaster Principal as blob, Publisher Principal as blob, Filter (optional), Skip (optional). - Message Data to Assigned Relayer:
icrc77:broadcaster:replay_relayer:add: #Array[#Nat, #Text, #Blob] - ReplayID, Namespace, relay Principal as blob. - Message Data to Assigned Broadcaster:
icrc77:broadcaster:replay:remove: #Array[#Array[#Nat]] - ReplayID, Namespace, Subscriber Principal as blob, Publisher principle as blob. - Message Data to Assigned Relayer:
icrc77:broadcaster:replay_relay:remove: #Array[#Nat] - ReplayID, Namespace, relay Principal as blob, publisher Principal as blob. - Message Data to Assigned Relayer:
icrc77:broadcaster:replay_relayer:remove: #Array[#Nat] - ReplayID, Namespace, relay Principal as blob, publisher Principal as blob.
- Message Data to Assigned Broadcaster:
- icrc72:broadcaster:sys:<broadcaster_principal> - Broadcasters and Broadcaster relays MUST be alerted to their responsibilities before the Publisher is notified of the request.
-
Orchestrator/Broadcaster → Publisher:
- icrc72:publisher:sys:<publisher_principal>
- Message Data:
icrc77:publisher:replay:add- #Array[#Array[#Nat, #Text, #Nat, #Opt(#Nat), #Blob]] - ReplayId, Namespace, Start, Optional End, Broadcaster. - Message Data:
icrc77:publisher:replay:remove- #Array[#Nat] - ReplayId
- Message Data:
- icrc72:publisher:sys:<publisher_principal>
-
Broadcaster → Subscriber
- icrc72:publisher:sys:<publisher_principal>
- Message Data:
icrc77:subscriber:replay:add- #Array[#Array[#Nat, #Blob]] - ReplayId, Broadcaster. - Message Data:
icrc77:subscriber:replay:remove- #Array[#Array[#Nat]] - ReplayId.
- Message Data:
- icrc72:publisher:sys:<publisher_principal>
Replay Life Cycle
- Subscriber registers a replay via icrc77_register_replay (or via a higher‑level call that emits the control message). The Subscriber MUST be authorized for the namespace (e.g., have a valid subscription).
- Orchestrator validates the request (authorization, publication existence, range bounds, optional payment). On success, it assigns a capable Broadcaster, Publisher, and a MessageFulfillmentId.
- Orchestrator emits an ICRC‑72 system message to the assigned Broadcaster/Broadcaster Replay.
- Orchestrator emits an ICRC‑72 system message to the assigned Publisher Replay.
- Publisher procures historical events (via async archival hooks such as ICRC‑3 archives) and re‑publishes them via ICRC‑72 publish into the existing data plane. Replay headers are appended.
- Broadcasters fan‑out to the Subscriber per normal ICRC‑72 routing. Optional deterministic replay streams MAY be used if isolation from live traffic is desired.
- Publisher reports progress and completion via icrc77_replay_status endpoint.
- A cancel request stops enqueuing further events; sent notifications are not recalled.
Authorization and Policy
- Requesters MUST be authorized to receive the target namespace (i.e., must be Subscribers under ICRC‑72 or otherwise permitted by publication policy).
- Orchestrators MUST select a Publisher that advertises ICRC‑77 capability for the namespace and respect publisher‑declared limits.
Filters and Skip
- Implementations SHOULD accept filters and skip policies via ICRC‑16 config entries. Minimal profile:
- filter: #Opt(#Text("…")) – grammar implementation‑defined; SHOULD reference ICRC‑16 path notation if available.
- skip: #Opt(#Array([#Nat(seed), #Nat(offset?)])).
- Unsupported filter/skip MUST result in ImproperConfig.
Payment
- Implementations MAY require payment for replay.
- Publication metadata SHOULD advertise pricing policy and accepted payment schemes.
- Requests without required payment MUST return PaymentRequired.
Publisher Capabilities Metadata
Publishers SHOULD advertise capabilities either via a metadata endpoint or via Orchestrator aggregation:
Suggested ICRC‑16 keys:
- icrc77:capable = #Bool(true)
- icrc77:pricing = #Class([{name="perEvent"; value=#Nat}, {name="currency"; value=#Text("icrcX:…")}])
Error Handling
Errors SHOULD follow the patterns in ICRC‑7 and use itemized results for batch requests. Core errors include:
- Unauthorized, UnauthorizedSubscriber, ImproperConfig, PaymentRequired, RangeTooLarge, GenericError, GenericBatchError.
Security Considerations
- Idempotency: Subscribers SHOULD deduplicate by (namespace, eventId) since replay generates new notificationIds.
- Abuse prevention: Orchestrators SHOULD enforce quotas/rate limits and size caps; Publishers SHOULD implement back‑pressure.
- Data integrity: Implementers MAY include icrc72:eventData:hash headers and verify during archival fetch.
Compatibility with ICRC‑72
- Delivery uses the existing notification path; no change to icrc72_publish wire format.
- Headers are append‑only, preserving ICRC‑72 rules.
- Confirmation flow is unchanged; replayed notifications require normal confirmation.