Skip to content

Latest commit

 

History

History
485 lines (391 loc) · 24.3 KB

File metadata and controls

485 lines (391 loc) · 24.3 KB

Contract event schema

This document lists all events emitted by the FluxoraStream contract, the exact topics used, and the data schema (field names and Rust/Soroban types). Use this as the canonical source of truth for indexers and backend parsers. The schemas below are derived directly from the contract source contracts/stream/src/lib.rs.

Notes:

  • Soroban events contain an ordered list of topics and a single data payload.
  • Topics shown below are the literal values used in env.events().publish(...).
  • Types use the contract's Rust types (e.g. u64, i128, Address).
  • Keep this file in sync with the contract when event shapes change.

Event list

Event name Topic(s) Data (shape & types) When emitted
StreamCreated ["created", stream_id: u64] StreamCreated { stream_id: u64, sender: Address, recipient: Address, deposit_amount: i128, rate_per_second: i128, start_time: u64, cliff_time: u64, end_time: u64, memo: Option<Bytes> } After a stream is successfully created and deposit tokens transferred. Not emitted on any validation failure.
Withdrawal ["withdrew", stream_id: u64] Withdrawal { stream_id: u64, recipient: Address, amount: i128 } When a recipient successfully withdraws accrued tokens. Only emitted when amount > 0.
WithdrawalTo ["wdraw_to", stream_id: u64] WithdrawalTo { stream_id: u64, recipient: Address, destination: Address, amount: i128 } When a recipient calls withdraw_to or batch_withdraw_to and amount > 0. Destination may differ from recipient.
StreamPaused ["paused", stream_id: u64] StreamPaused { stream_id: u64, reason: PauseReason } When a stream is paused by the sender (pause_stream) or admin (pause_stream_as_admin). The reason field carries the operational context code.
StreamResumed ["resumed", stream_id: u64] StreamEvent::Resumed(stream_id: u64) When a paused stream is resumed by the sender (resume_stream) or admin (resume_stream_as_admin).
StreamCancelled ["cancelled", stream_id: u64] StreamEvent::StreamCancelled(stream_id: u64) When a stream is cancelled by the sender (cancel_stream) or admin (cancel_stream_as_admin). status is persisted as Cancelled and cancelled_at is set before this event is emitted.
StreamCloned ["cloned", stream_id: u64] StreamCloned { new_stream_id: u64, source_stream_id: u64, sender: Address, recipient: Address, deposit_amount: i128, rate_per_second: i128, start_time: u64, cliff_time: u64, end_time: u64 } When clone_stream creates a new stream from an existing source stream.
KeeperCancelled ["kp_cncl", stream_id: u64] KeeperCancelled { stream_id: u64, keeper: Address, keeper_fee: i128, recipient_amount: i128, sender_refund: i128 } When keeper_cancel cancels an eligible expired stream after the keeper grace period.
StreamCompleted ["completed", stream_id: u64] StreamEvent::StreamCompleted(stream_id: u64) When withdrawn_amount reaches deposit_amount during a withdraw or batch_withdraw call. Emitted after Withdrawal.
StreamClosed ["closed", stream_id: u64] StreamEvent::StreamClosed(stream_id: u64) When a completed stream's storage is removed via close_completed_stream. Emitted before the storage entry is deleted.
RateUpdated ["rate_upd", stream_id: u64] RateUpdated { stream_id: u64, old_rate_per_second: i128, new_rate_per_second: i128, effective_time: u64 } When update_rate_per_second successfully changes a stream's rate.
RateCapEnforced ["rate_cap", stream_id: u64] RateCapEnforced { stream_id: u64, attempted_rate: i128, max_rate_per_second: i128 } When a rate update is rejected due to exceeding the governance-controlled maximum rate per second cap.
StreamEndShortened ["end_shrt", stream_id: u64] StreamEndShortened { stream_id: u64, old_end_time: u64, new_end_time: u64, refund_amount: i128 } When shorten_stream_end_time successfully shortens a stream.
StreamEndExtended ["end_ext", stream_id: u64] StreamEndExtended { stream_id: u64, old_end_time: u64, new_end_time: u64 } When extend_stream_end_time successfully extends a stream.
StreamToppedUp ["top_up", stream_id: u64] StreamToppedUp { stream_id: u64, top_up_amount: i128, new_deposit_amount: i128 } When top_up_stream successfully increases a stream's deposit.
RecipientUpdated ["recp_upd", stream_id: u64] RecipientUpdated { stream_id: u64, old_recipient: Address, new_recipient: Address } When update_recipient successfully rotates a stream's receiving address.
AdminUpdated ["AdminUpdated"] (old_admin: Address, new_admin: Address) When the contract admin is rotated via set_admin.
ContractPaused ["paused_ctl"] bool When the global contract pause state is toggled via set_contract_paused.
ProtocolPaused ["pr_pause", admin: Address] ProtocolPaused { reason: String, paused_at: u64 } When pause_protocol successfully pauses the protocol. Not emitted on idempotent calls.
ProtocolResumed ["pr_resume", admin: Address] ProtocolResumed { resumed_at: u64 } When resume_protocol successfully resumes the protocol. Not emitted on idempotent calls.
SenderTransferred ["sndr_xfr", stream_id: u64] SenderTransferred { stream_id: u64, old_sender: Address, new_sender: Address } When transfer_sender successfully rotates the stream sender. Emitted after state is persisted. Not emitted on failure.
DelegatedWithdrawal ["dlg_wdraw", stream_id: u64] DelegatedWithdrawal { stream_id: u64, recipient: Address, destination: Address, relayer: Address, amount: i128 } When a relayer successfully executes a recipient-signed delegated withdrawal via delegated_withdraw_to. Only emitted when amount > 0.
StreamHealthChanged ["hlth_chg", stream_id: u64] StreamHealthChanged { stream_id: u64, is_underfunded: bool, remaining_balance: i128, seconds_remaining: u64 } When a stream transitions between adequately funded and underfunded. Emitted by decrease_rate_per_second, shorten_stream_end_time, top_up_stream, and cancel_stream. Only emitted on actual health transitions, not on every mutation.
ExcessSwept ["ex_swept", recipient: Address] ExcessSwept { to: Address, amount: i128 } When the admin recovers tokens that exceed total stream liabilities via sweep_excess.
AutoClaimSet ["ac_set", stream_id: u64] AutoClaimSet { stream_id: u64, destination: Address } When a recipient configures or changes a permissionless final-claim destination via set_auto_claim.
AutoClaimRevoked ["ac_revoke", stream_id: u64] AutoClaimRevoked { stream_id: u64 } When a recipient revokes auto-claim configuration via revoke_auto_claim.
AutoClaimTriggered ["ac_trig", stream_id: u64] AutoClaimTriggered { stream_id: u64, destination: Address, amount: i128 } When a third party successfully executes a configured final claim via trigger_auto_claim.
MigrationCheckpoint ["migrated"] (from_version: u32, to_version: u32, timestamp: u64) When migration_v5_to_v6 is called as an auditable deployment checkpoint.

Additional topics (validator): cloned, kp_cncl, gl_pause, gl_resume, rate_dec, tmpl_def, hlth_chg, ex_swept, ac_set, ac_revoke, ac_trig, migrated.


Event name Topic(s) Data (shape & types) When emitted
StreamCreated ["created", stream_id] StreamCreated { stream_id: u64, sender: Address, recipient: Address, deposit_amount: i128, rate_per_second: i128, start_time: u64, cliff_time: u64, end_time: u64 } When a stream is successfully created (after tokens transferred). The stream_id is the newly assigned stream id (u64). The event is published in persist_new_stream. Not emitted on failed creation (e.g., StartTimeInPast).
Withdrawal ["withdrew", stream_id] Withdrawal { stream_id: u64, recipient: Address, amount: i128 } When a recipient successfully withdraws accrued tokens. Only emitted when amount > 0.
StreamPaused ["paused", stream_id] StreamEvent::Paused(stream_id) — enum wrapper containing the u64 stream id When a stream is paused by the sender or admin.
StreamResumed ["resumed", stream_id] StreamEvent::Resumed(stream_id) — enum wrapper containing the u64 stream id When a paused stream is resumed by the sender or admin.
StreamCancelled ["cancelled", stream_id] StreamEvent::StreamCancelled(stream_id) — enum wrapper containing the u64 stream id When a stream is cancelled by the sender or admin.
AdminUpdated ["admin", "updated"] (old_admin: Address, new_admin: Address) When contract admin is rotated via set_admin.
ContractPaused ["paused_ctl"] bool When global pause is set to true or false.

Exact Soroban event structure

Soroban events are represented as JSON in test snapshots; the general shape is:

  • topics: array of topic items (symbols or values)
  • data: a value (single item) which can be a primitive, a struct, or a tuple

1) StreamCreated

Emitted by persist_new_stream after a successful create_stream, create_streams, or create_streams_partial call.

topics: ["created", <stream_id: u64>]
data:   StreamCreated {
          stream_id:       u64,
          sender:          Address,
          recipient:       Address,
          deposit_amount:  i128,
          rate_per_second: i128,
          start_time:      u64,
          cliff_time:      u64,
          end_time:        u64,
          memo:            Option<Bytes>,  // None when not supplied; max 64 bytes
        }

Example JSON (illustrative):

{
  "topics": ["created", 0],
  "data": {
    "stream_id": 0,
    "sender": "G...SENDER...",
    "recipient": "G...RECIPIENT...",
    "deposit_amount": 1000,
    "rate_per_second": 1,
    "start_time": 0,
    "cliff_time": 0,
    "end_time": 1000
  }
}

2) Withdrawal

Emitted by withdraw and each stream in batch_withdraw when withdrawable > 0.

topics: ["withdrew", <stream_id: u64>]
data:   Withdrawal {
          stream_id: u64,
          recipient: Address,
          amount:    i128,
        }

Example:

{
  "topics": ["withdrew", 0],
  "data": { "stream_id": 0, "recipient": "G...RECIPIENT...", "amount": 300 }
}

3) WithdrawalTo

Emitted by withdraw_to when withdrawable > 0. The destination field holds the address that actually receives the tokens; recipient is the stream's registered recipient (the authorised caller).

topics: ["wdraw_to", <stream_id: u64>]
data:   WithdrawalTo {
          stream_id:   u64,
          recipient:   Address,
          destination: Address,
          amount:      i128,
        }

4) StreamPaused / StreamResumed / StreamCancelled / StreamCompleted / StreamClosed

StreamPaused uses the new StreamPaused struct (introduced in CONTRACT_VERSION = 3):

#[contracttype]
pub struct StreamPaused {
    pub stream_id: u64,
    pub reason: PauseReason,
}

#[contracttype]
pub enum PauseReason {
    Operational   = 0,  // Routine sender-initiated pause
    Emergency     = 1,  // Security-related pause
    Compliance    = 2,  // Regulatory/compliance hold
    Administrative = 3, // Admin-initiated pause
}
Function(s) Topic Data
pause_stream, pause_stream_as_admin "paused" StreamPaused { stream_id, reason }
resume_stream, resume_stream_as_admin "resumed" StreamEvent::Resumed(id)
cancel_stream, cancel_stream_as_admin "cancelled" StreamEvent::StreamCancelled(id)
withdraw, batch_withdraw (final drain on Active streams) "completed" StreamEvent::StreamCompleted(id)
close_completed_stream "closed" StreamEvent::StreamClosed(id)

Breaking change (v3): The "paused" event data changed from StreamEvent::Paused(stream_id) to StreamPaused { stream_id, reason }. Indexers must update their pause event parsers. CONTRACT_VERSION was bumped to 3 to signal this incompatibility.

Example (paused with reason):

{
  "topics": ["paused", 0],
  "data": { "stream_id": 0, "reason": "Operational" }
}

StreamCancelled does not embed refund or timestamp fields in the payload. Indexers should read get_stream_state(stream_id) to obtain cancelled_at and derive refund from state plus accrual (refund = deposit_amount - accrued_at_cancelled_at).

Example (completed — emitted after the Withdrawal event on the same call):

{
  "topics": ["completed", 0],
  "data": { "StreamCompleted": 0 }
}

Indexers: the stream_id appears both as the second topic and inside the enum payload. Read it from the topic for efficiency; use the payload only for cross-checking.

5) RateUpdated

topics: ["rate_upd", <stream_id: u64>]
data:   RateUpdated {
          stream_id:           u64,
          old_rate_per_second: i128,
          new_rate_per_second: i128,
          effective_time:      u64,
        }

6) StreamEndShortened

topics: ["end_shrt", <stream_id: u64>]
data:   StreamEndShortened {
          stream_id:     u64,
          old_end_time:  u64,
          new_end_time:  u64,
          refund_amount: i128,
        }

Emission guarantees:

  • Emitted exactly once on successful shorten_stream_end_time.
  • Not emitted on failed shorten calls (InvalidParams, InvalidState, auth failure).

7) StreamEndExtended

topics: ["end_ext", <stream_id: u64>]
data:   StreamEndExtended {
          stream_id:    u64,
          old_end_time: u64,
          new_end_time: u64,
        }

8) StreamToppedUp

This event is emitted only after the top-up has succeeded. Validation failures, authorization failures, arithmetic overflow, or failed token pulls emit no top_up contract event.

topics: ["top_up", <stream_id: u64>]
data:   StreamToppedUp {
          stream_id:          u64,
          top_up_amount:      i128,
          new_deposit_amount: i128,
        }

9) AdminUpdated

Emitted by set_admin.

topics: ["AdminUpdated"]
data:   (old_admin: Address, new_admin: Address)

Example:

{
  "topics": ["AdminUpdated"],
  "data": ["G...OLD_ADDRESS...", "G...NEW_ADDRESS..."]
}

10) ProtocolPaused

Emitted by pause_protocol when the protocol is successfully paused. Not emitted on idempotent calls (when already paused).

topics: ["pr_pause", admin: Address]
data:   ProtocolPaused {
          reason: String,
          paused_at: u64,
        }

Example:

{
  "topics": ["pr_pause", "G...ADMIN_ADDRESS..."],
  "data": {
    "reason": "security incident",
    "paused_at": 1234567
  }
}

11) ProtocolResumed

Emitted by resume_protocol when the protocol is successfully resumed. Not emitted on idempotent calls (when not paused).

topics: ["pr_resume", admin: Address]
data:   ProtocolResumed {
          resumed_at: u64,
        }

Example:

{
  "topics": ["pr_resume", "G...ADMIN_ADDRESS..."],
  "data": {
    "resumed_at": 2345678
  }
}

12) SenderTransferred

Emitted by transfer_sender when the stream sender is successfully rotated.

topics: ["sndr_xfr", <stream_id: u64>]
data:   SenderTransferred {
          stream_id:  u64,
          old_sender: Address,
          new_sender: Address,
        }

Example JSON:

{
  "topics": ["sndr_xfr", 0],
  "data": {
    "stream_id": 0,
    "old_sender": "G...OLD_SENDER...",
    "new_sender": "G...NEW_SENDER..."
  }
}

On-chain Pause Audit Trail

In addition to events, the contract maintains an on-chain audit trail of the last pause action for each pause kind. This is queryable via get_last_pause_record(kind: PauseKind).

PauseKind

  • GlobalEmergency: Toggled via set_global_emergency_paused.
  • Protocol: Toggled via pause_protocol.
  • Stream: Toggled via pause_stream_as_admin.

PauseRecord

pub struct PauseRecord {
    pub actor: Address,
    pub timestamp: u64,
    pub reason: String,
}

14) StreamHealthChanged

Emitted by decrease_rate_per_second, shorten_stream_end_time, top_up_stream, and cancel_stream when the stream's funding health status transitions between adequately funded and underfunded.

A stream is underfunded when remaining_balance < rate_per_second × seconds_remaining. Terminal streams (Completed, Cancelled) have seconds_remaining = 0 and are never underfunded.

This event is only emitted when the is_underfunded flag actually changes, not on every mutation.

topics: ["hlth_chg", <stream_id: u64>]
data:   StreamHealthChanged {
          stream_id:         u64,
          is_underfunded:    bool,
          remaining_balance: i128,
          seconds_remaining: u64,
        }

Example (stream became underfunded after rate decrease):

{
  "topics": ["hlth_chg", 0],
  "data": {
    "stream_id": 0,
    "is_underfunded": true,
    "remaining_balance": 500,
    "seconds_remaining": 800
  }
}

Example (stream became adequately funded after top-up):

{
  "topics": ["hlth_chg", 0],
  "data": {
    "stream_id": 0,
    "is_underfunded": false,
    "remaining_balance": 1200,
    "seconds_remaining": 800
  }
}

Indexers should use this event to surface underfunded streams proactively. The remaining_balance and seconds_remaining fields allow precise monitoring dashboards.


Parsing recommendations for indexers

  • Use topics[0] to filter by event type; use topics[1] to get the stream_id for all stream-level events.
  • For Withdrawal and WithdrawalTo, the amount field is i128 — use a big-int library that supports 128-bit signed integers.
  • StreamCompleted is emitted on the same call as the final Withdrawal that drains an Active stream. Cancelled streams do not transition to Completed.
  • StreamClosed signals that the stream's on-chain storage has been removed. After this event, get_stream_state returns StreamNotFound for that ID.
  • AdminUpdated has a single-element topic list (no stream_id).

See docs/indexer-derivation.md for the complete specification of how to derive stream state from events, when to call get_stream_state, and worked examples for each lifecycle path (including cancellation, rate changes, and completion).


Keeping this doc in sync

This file is derived from contracts/stream/src/lib.rs emit calls:

  • persist_new_stream publishes (symbol_short!("created"), stream_id), StreamCreated { ... }
  • withdraw publishes (symbol_short!("withdrew"), stream_id), Withdrawal { stream_id, recipient, amount }
  • pause_stream / pause_stream_as_admin publish (symbol_short!("paused"), stream_id), StreamEvent::Paused(stream_id)
  • resume_stream / resume_stream_as_admin publish (symbol_short!("resumed"), stream_id), StreamEvent::Resumed(stream_id)
  • cancel_stream / cancel_stream_as_admin publish (symbol_short!("cancelled"), stream_id), StreamEvent::StreamCancelled(stream_id)
  • set_admin publishes (symbol_short!("admin"), symbol_short!("updated")), (old_admin, new_admin)

If you change event topics or payloads in the contract, please update this document to match and include example snapshots.


Commit message suggestion: docs: add event schema and topics for indexers

Source location Symbol emitted
persist_new_stream "created"
withdraw, batch_withdraw "withdrew"
withdraw_to, batch_withdraw_to "wdraw_to"
withdraw, batch_withdraw, batch_withdraw_to (completion) "completed"
pause_stream, pause_stream_as_admin "paused"
resume_stream, resume_stream_as_admin "resumed"
cancel_stream, cancel_stream_as_admin "cancelled"
close_completed_stream "closed"
update_rate_per_second "rate_upd"
shorten_stream_end_time "end_shrt"
extend_stream_end_time "end_ext"
top_up_stream "top_up"
set_admin "AdminUpdated"
set_contract_paused "paused_ctl"
pause_protocol "pr_pause"
resume_protocol "pr_resume"
update_recipient "recp_upd"
revoke_auto_claim "ac_revoke"
set_auto_claim "ac_set"
trigger_auto_claim "ac_trig"
sweep_excess "ex_swept"
migrate_recipient_index "migrated"
decrease_rate_per_second, shorten_stream_end_time, top_up_stream, cancel_stream "hlth_chg"

If you change event topics or payloads in the contract, update this document and include updated example snapshots in the PR.