Events
The BountyMesh service emits exactly five event variants. They are SCALE-encoded on chain, projected into the indexer's bounties table, and exposed to SDK consumers via per-event subscriptions (onBountyPosted, onBountyClaimed, etc.).
Event variant order is locked at deploy time. Reordering shifts SCALE discriminants and silently breaks every consumer with a cached IDL. New variants only at the end.
pub enum Event {
BountyPosted { … },
BountyClaimed { … },
BountySubmitted { … },
BountyAccepted { … },
BountyWithdrawn { … },
}BountyPosted
Emitted on successful Post.
BountyPosted {
id: u64,
poster: ActorId,
reward: u128,
track: TrackEnum,
posted_at: u32,
title: String,
description: String,
acceptance: String,
deadline: Option<u32>,
}F1 note: title, description, acceptance, deadline were appended at the END of the payload in a post-5a follow-up. SCALE encodes positionally; ENDS are safe to extend, MIDDLES are not. See type-drift rules.
Indexer projection: inserts a new row into bounties with status='Open', all six text fields populated, reward stored as NUMERIC(39,0) string.
SDK subscription:
sdk.onBountyPosted((evt, ctx) => {
console.log("new bounty", evt.id, evt.title, "from", ctx.blockHash);
});ctx carries blockHash, blockNumber, txHash, messageId — all sourced via subscribeNewHeads.
BountyClaimed
Emitted on successful Claim.
BountyClaimed {
id: u64,
worker: ActorId,
claimed_at: u32,
}Indexer projection: updates bounties.status = 'Claimed', bounties.worker_id, bounties.claimed_at. Append to events table.
SDK subscription:
sdk.onBountyClaimed((evt, ctx) => {
console.log("bounty", evt.id, "claimed by", evt.worker);
});BountySubmitted
Emitted on successful Submit.
BountySubmitted {
id: u64,
worker: ActorId,
result_hash: H256,
submitted_at: u32,
}Note: result_payload is NOT in the event — it's stored on chain as part of the bounty struct, accessible via a future GetBounty(id) query method. The event carries only the hash for fast off-chain verification.
Indexer projection: updates bounties.status = 'Submitted', bounties.result_hash, bounties.submitted_at. The result_payload is fetched on demand by the frontend from the indexer's bounties.result_payload (set on a separate path).
SDK subscription:
sdk.onBountySubmitted((evt, ctx) => {
console.log("bounty", evt.id, "submitted with hash", evt.resultHash);
});BountyAccepted
Emitted on successful Accept.
BountyAccepted {
id: u64,
poster: ActorId,
worker: ActorId,
reward: u128,
settled_at: u32,
}reward echoes the locked-at-Post value — no transfer happened yet. Settlement happens at Withdraw.
Indexer projection: updates bounties.status = 'Accepted', bounties.settled_at. The withdrawn column stays false.
SDK subscription:
sdk.onBountyAccepted((evt, ctx) => {
console.log("bounty", evt.id, "ready for worker", evt.worker, "to withdraw");
});BountyWithdrawn
Emitted on successful Withdraw.
BountyWithdrawn {
id: u64,
worker: ActorId,
amount: u128,
withdrawn_at: u32,
}amount equals bounty.reward. The defensive refund of any attached value is NOT part of amount — that's a free additional refund.
Indexer projection: updates bounties.withdrawn = true, bounties.withdrawn_at. Status stays Accepted.
SDK subscription:
sdk.onBountyWithdrawn((evt, ctx) => {
console.log("worker", evt.worker, "pulled", evt.amount, "for bounty", evt.id);
});Subscription architecture
The SDK uses api.rpc.chain.subscribeNewHeads (NOT api.gearEvents.subscribeToGearEvent) because consumers need blockHash + txHash in the event context. subscribeToGearEvent only exposes message-level data.
A single underlying WS subscription drives all five onBountyX handlers via the SDK's SubscriptionManager. ~3 RPC round-trips per block. The indexer absorbs production-scale load — use the indexer's worker_balance_changed projection for high-cardinality consumers.
Watching via vara-wallet
vara-wallet watch $BOUNTYMESH_PROGRAM_ID \
--idl ./bountymesh.idl \
--event BountyService/BountyAccepted \
--jsonNDJSON output, one line per event:
{
"event": "UserMessageSent",
"messageId": "0x...",
"source": "0x6683...c39b",
"destination": "0x000...",
"payload": "0x...",
"decoded": {
"kind": "sails",
"service": "BountyService",
"event": "BountyAccepted",
"data": {
"id": "42",
"poster": "0xa2d2...0b1f",
"worker": "0x...",
"reward": "500000000000",
"settled_at": 33347600
}
},
"timestamp": 1716845204000
}Limitation: watch emits messageId but NOT blockHash + txHash. For block-level context, use the SDK's onBountyX (block-level via subscribeNewHeads) or the indexer's GraphQL.
Indexer GraphQL query
PostGraphile generates one query per event projection. The flattened events table is also queryable:
query {
events(condition: { eventName: "BountyAccepted" }, first: 10, orderBy: BLOCK_NUMBER_DESC) {
nodes {
blockNumber
txHash
bountyId
payload
}
}
}See GraphQL schema for the full query surface.
Source
Next steps
- Errors — typed error variants
- GraphQL schema — indexer query surface
- SDK reference — TypeScript client API