Build / Events

Events

Events let contracts react to each other. An organization declares an event type and emits it from its scripts. Anyone can deploy an event contract, on any node, that runs with each event it consumes, and pays for its own runs.

Language
RequestScript 1.6+
Declare
POST /v1/events
Emit
emit
Consume
consumes

01

What is an event?

A contract runs when someone calls it. An event contract runs when something happens instead: an order is placed, a payment clears, a record changes. Three pieces make that work:

What it isWho can use it
Event typea named, versioned set of fieldsdeclared by an organization, on one of its nodes
emitpublishes an event of that type from a runonly the type's owner, from its own nodes
Event contracta contract that consumes one typeanyone, on any node, unless the type is restricted

Publishers never pay for consumers, so anyone can subscribe to an event without costing the organization that emits it.

02

Declare an event type

An event type is a script of its own. It names the fields its events carry and has no body. Names, paths and versions work as they do for contracts, and versions are immutable.

OrderPlaced.req
event com.example.OrderPlaced@1(orderId: string, total: decimal)
restricted event com.example.AuditEntry(entry: object)

A restricted event type is consumed only by event contracts its owner deployed, for events you want to keep inside your organization.

Declare it on a node of your organization with its API key, or a run token from one of its own API clients. The node's organization owns the type, because it's the organization whose nodes may emit it.

Terminal
curl -X POST https://node.example/v1/events \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"source": "event com.example.OrderPlaced(orderId: string, total: decimal)"}'

GET /v1/events lists the event types a node knows, its own and those it learned from peers. DELETE /v1/events/com.example.OrderPlaced@1 deletes one.

03

Emit events

A request or a contract emits an event with emit, naming every field:

Checkout.req
contract com.example.Checkout(orderId: string, total: decimal): string {
  emit com.example.OrderPlaced(orderId: orderId, total: total)
  return orderId
}
  • Fields are checked. Each one is converted to its declared type, and a missing or unknown field is a runtime error.
  • Events are published only when a run succeeds. Like contract state, a run that fails publishes nothing.
  • Only the owner emits. The script must belong to the type's organization: a run with the node's API key or one of its API clients, or a contract it owns. Other organizations can still trigger events through your contracts: anyone can call Checkout, and it emits as you.
  • Runs have limits. A run emits at most 16 events, each with at most 32 KiB of data.
.env
# The most events one run may emit. Defaults to 16.
MAX_EVENTS_PER_RUN=16
# The most one event's data may take, as JSON, in bytes. Defaults to 32 KiB.
MAX_EVENT_DATA_BYTES=32768

04

Consume events

An event contract names the type it consumes. The event's fields are its parameters, so it declares none of its own, and it has no return type.

OnOrderPlaced.req
contract com.example.OnOrderPlaced consumes com.example.OrderPlaced@1 {
  var state(orderId) seen: int32 = 0
  seen = seen + 1

  const logger: com.example.Logger
  logger.info(message: "Order ${orderId}: ${total}, from ${caller.org}")
}
  • It consumes one version: the one it names, or the latest its node knows when it's deployed.
  • It reads two built-ins besides its fields: event, with its id, type, version, publishedAt and depth, and caller, the event's owner and the node that published it.
  • It isn't a resource. It runs only with real events, so nobody can call it with made-up data. A contract keeps its kind across versions.
  • It can keep state like any contract, here one counter per order.

Deploy it like any contract, with POST /v1/contracts.

05

How events travel

An event can be emitted on one node and consumed on many others. Every hop is signed, and receivers check the publisher against the operator, so no node has to take another's word for who owns an event.

01

Nodes announce

Each node signs and announces its event types and event contracts to its peers, with a membership token from the operator. Peers learn a type only if the token says the node belongs to its owner.

02

The run publishes

When a run succeeds, its node signs each event and queues it for its own event contracts, and once for each peer subscribed to it. Restricted events go only to the owner's contracts.

03

Peers receive it

A worker delivers each event, retrying with backoff for up to a day. The receiver checks the signature, the membership token, the event's age and depth, and that it knows the type.

04

Event contracts run

The receiver queues the event once for each local event contract and runs it, retrying a failure up to 3 times in all.

Delivery is at least once, and each event contract runs once per event id. Events may arrive in any order. Admins follow deliveries at GET /v1/admin/events/outbox and runs at GET /v1/admin/events/inbox.

06

Who pays

  • Emitting costs only the publishing run's tokens.
  • Your own event contracts run free on your organization's nodes.
  • Others pay through a mandate. When another organization's API client deploys an event contract, the node asks the operator for an event mandate. Each event then runs under a hold on that organization's balance, within its API client's limits and monthly cap, at the node's price per million tokens.
  • Charges stop when you say so. An organization sees its mandates under API clients on the Stellarium Platform, and stops one by revoking the mandate or the API client. Deploying the contract again renews it.

07

Chains

An event contract may emit events of its own, so one event can set off another. Each event carries its chain, and a few rules keep a chain from looping:

  • At most 4 deep. An event three hops from the first can't emit another.
  • No repeated types. A contract can't emit an event type already in its chain, and an event contract can't emit the type it consumes.
  • No calls back. A contract can't call a contract already in its chain, on any node.

For example, contract A emits Event1, and event contract B consumes it. If B emits Event2, event contract C may consume that, but C can't emit Event1 or Event2, or call A or B.

08

Reference

Read the full reference, including how nodes sign and check events, in the stellarium-ts events guide and the documentation. Events need RequestScript 1.6.0, which adds event declarations, consumes and emit; learn the language at requestscript.org.

Built something with events? Show it off on Discord.