Skip to main content

Bounty Escrow — Events Reference

Module: bounty_escrow/contracts/escrow/src/events.rs
Schema version: EVENT_VERSION_V2 (version: u32 = 2)


Overview

Every event emitted by BountyEscrowContract carries:

  1. A topic list — one or two Symbol values that indexers use for prefix-filtering without decoding the payload.
  2. A typed data payload — a #[contracttype] struct whose first field is always version: u32 = EVENT_VERSION_V2.
topics : (category_sym [, bounty_id: u64])
data : <EventStruct { version: 2, ... }>

The version field lives in the payload, not the topics, to preserve backwards-compatible topic-filter subscriptions when schemas evolve.


Event Catalogue

BountyEscrowInitialized

Emitted once by init() on successful contract initialization.

Topics("init",)
versionu32 = 2
adminAddress — initial admin
tokenAddress — reward token contract
timestampu64 — ledger time

Invariants checked before emission:

  • AlreadyInitialized guard prevents duplicate emission.
  • admin ≠ token — validated by validate_init_params.

FundsLocked

Emitted by lock_funds() and batch_lock_funds() (once per bounty).

Topics("f_lock", bounty_id: u64)
versionu32 = 2
bounty_idu64
amounti128gross deposit (before lock fee)
depositorAddress
deadlineu64 — claim cut-off timestamp

FundsReleased

Emitted by release_funds(), partial_release(), and release_with_capability().

Topics("f_rel", bounty_id: u64)
versionu32 = 2
bounty_idu64
amounti128 — net payout (after release fee)
recipientAddress — contributor wallet
timestampu64

For partial releases, this event is emitted on every call. Sum all FundsReleased events for a bounty to reconstruct total payout.


FundsRefunded

Emitted by refund(), refund_resolved(), and refund_with_capability().

Topics("f_ref", bounty_id: u64)
versionu32 = 2
bounty_idu64
amounti128
refund_toAddress — may differ from depositor on admin-approved refunds
timestampu64

FeeCollected

Emitted whenever a non-zero fee is transferred.

Topics("fee",)
operation_typeFeeOperationTypeLock or Release
amounti128 — actual fee (ceiling-rounded)
fee_ratei128 — basis points applied
recipientAddress
timestampu64

Note on ceiling division: fee = ⌈amount × rate / 10_000⌉. This prevents the dust-splitting attack where many small deposits each round the fee to zero.


FeeConfigUpdated

Emitted by update_fee_config().

Topics("fee_cfg",)
lock_fee_ratei128 — new lock rate in bps
release_fee_ratei128 — new release rate in bps
fee_recipientAddress
fee_enabledbool
timestampu64

BatchFundsLocked

Emitted once per batch_lock_funds() call, after all per-bounty FundsLocked events.

Topics("b_lock",)
countu32 — number of bounties locked
total_amounti128 — sum of all locked amounts
timestampu64

BatchFundsReleased

Emitted once per batch_release_funds() call.

Topics("b_rel",)
countu32
total_amounti128
timestampu64

FundsLockedAnon

Emitted by lock_funds_anonymous(). The depositor's address is never stored or emitted.

Topics("f_lkanon", bounty_id: u64)
versionu32 = 2
bounty_idu64
amounti128
depositor_commitmentBytesN<32> — hash commitment of depositor identity
deadlineu64

DeprecationStateChanged

Emitted by set_deprecated().

Topics("deprec",)
deprecatedbool — new state
migration_targetOption<Address>
adminAddress
timestampu64

When deprecated = true, all subsequent lock_funds / batch_lock_funds calls return Error::ContractDeprecated.


MaintenanceModeChanged

Emitted by set_maintenance_mode().

Topics("maint",)
enabledbool
adminAddress
timestampu64

ParticipantFilterModeChanged

Emitted by set_filter_mode().

Topics("pf_mode",)
previous_modeParticipantFilterMode
new_modeParticipantFilterMode
adminAddress
timestampu64

RiskFlagsUpdated

Emitted by set_escrow_risk_flags() and clear_escrow_risk_flags().

Topics("risk", bounty_id: u64)
versionu32 = 2
bounty_idu64
previous_flagsu32
new_flagsu32
adminAddress
timestampu64

Defined flag bits:

BitConstantMeaning
0RISK_FLAG_HIGH_RISKElevated risk
1RISK_FLAG_UNDER_REVIEWUnder review
2RISK_FLAG_RESTRICTEDPayout restricted
3RISK_FLAG_DEPRECATEDBounty deprecated

TicketIssued

Emitted by issue_claim_ticket() and issue_claim_ticket_deterministic().

Topics("ticket_i", ticket_id: u64)
ticket_idu64 — monotonic
bounty_idu64
beneficiaryAddress
amounti128
expires_atu64
issued_atu64

TicketClaimed

Emitted when a claim ticket is redeemed.

Topics("ticket_c", ticket_id: u64)
ticket_idu64
bounty_idu64
claimerAddress
claimed_atu64

DeterministicSelectionDerived

Emitted by issue_claim_ticket_deterministic() before ticket issuance.

Topics("prng_sel", bounty_id: u64)
bounty_idu64
selected_indexu32 — zero-based index into candidates
candidate_countu32
selected_beneficiaryAddress
seed_hashBytesN<32> — for off-chain verification
winner_scoreBytesN<32>
timestampu64

EmergencyWithdrawEvent

Emitted by emergency_withdraw().

Topics("em_wtd",)
adminAddress
recipientAddress
amounti128 — entire contract balance drained
timestampu64

Capability Events

EventTopicsKey fields
CapabilityIssued("cap_new", capability_id)owner, holder, action, bounty_id, amount_limit, expires_at, max_uses
CapabilityUsed("cap_use", capability_id)holder, action, amount_used, remaining_amount, remaining_uses
CapabilityRevoked("cap_rev", capability_id)owner, revoked_at

State → Event Matrix

init() → BountyEscrowInitialized
lock_funds() → FundsLocked [+ FeeCollected if fee > 0]
batch_lock_funds() → FundsLocked × N + BatchFundsLocked
lock_funds_anonymous() → FundsLockedAnon
release_funds() → FundsReleased [+ FeeCollected if fee > 0]
partial_release() → FundsReleased
release_with_capability() → FundsReleased + CapabilityUsed
batch_release_funds() → FundsReleased × N + BatchFundsReleased
refund() → FundsRefunded
refund_resolved() → FundsRefunded
refund_with_capability() → FundsRefunded + CapabilityUsed
set_deprecated() → DeprecationStateChanged
set_maintenance_mode() → MaintenanceModeChanged
set_paused() → PauseStateChanged (per operation)
set_filter_mode() → ParticipantFilterModeChanged
update_fee_config() → FeeConfigUpdated
set_escrow_risk_flags() → RiskFlagsUpdated
clear_escrow_risk_flags() → RiskFlagsUpdated
issue_claim_ticket() → TicketIssued
issue_claim_ticket_deterministic()→ DeterministicSelectionDerived + TicketIssued
emergency_withdraw() → EmergencyWithdrawEvent
issue_capability() → CapabilityIssued
revoke_capability() → CapabilityRevoked
approve_large_release() → ApprovalAdded

Indexing Guide

Filter all bounty-escrow events (Horizon RPC)

{ "topic1": "f_lock" }
{ "topic1": "f_rel" }
{ "topic1": "f_ref" }

Filter by bounty_id (topic2)

{ "topic1": "f_lock", "topic2": "0x000000000000002a" }

(topic2 is the bounty_id encoded as a u64 XDR integer)

Decode a FundsLocked payload (JavaScript)

import { xdr, scValToNative } from "@stellar/stellar-sdk";

function decodeFundsLocked(base64Data: string) {
const val = xdr.ScVal.fromXDR(base64Data, "base64");
return scValToNative(val);
// Returns: { version: 2, bounty_id: ..., amount: ..., depositor: ..., deadline: ... }
}

Security Notes

  1. CEI ordering — All events are emitted after state mutations and token transfers. An emitted event is a reliable indicator that the corresponding on-chain state change occurred.
  2. No PII on-chain — Events carry wallet addresses only. KYC identity data remains off-chain per Grainlify's privacy model.
  3. symbol_short! length limit — All topic strings are ≤ 8 bytes. Soroban silently truncates longer strings, which would corrupt topic-based filtering. Enforced by symbol_short! macro at compile time.
  4. Version in payload, not topics — Placing the version field in the data payload (rather than topics[0]) allows indexers to subscribe to a stable topic like "f_lock" without needing to re-subscribe when the schema version bumps.
  5. Re-entrancy — Events are only published after the reentrancy guard is held, so duplicate events from re-entrant calls are structurally impossible.
  6. Anonymous escrow privacyFundsLockedAnon publishes a 32-byte commitment, never the depositor address. The commitment must be computed off-chain using a collision-resistant function.

Changelog

VersionChange
EVENT_VERSION_V2 (= 2)Initial versioned schema for all bounty-escrow events. All payload structs carry version: u32 = 2.