transfer, mint, burn or clawback event becomes a
token_transfer record with from, to and amount. Every other successful
contract event stays a contract_event with its topics and data.
This page explains exactly which event shapes count as token movements, so you
know which route and which kind to read. Lumeus classifies events with the
Stellar SDK token transfer processor (go-stellar-sdk v0.7.3).
For example, a contract event with topics ["transfer", from, to] (both
addresses) and the i128 data 5000000 becomes a token_transfer with from = topic 1, to = topic 2
and amount_raw "5000000". An ["approve", owner, spender] event stays a
contract_event of type approve, like the record on events.
Requirements for every token event
An event is a token movement candidate only when all of these hold:- It was emitted by a contract call (
InvokeHostFunction) in a successful transaction. - It is a
CONTRACTevent (not a system or diagnostic event) with a contract ID and a version 0 body. - It has at least two topics, and the first topic is a Symbol equal to
transfer,mint,burnorclawback. - Each address topic the shape uses is an
ScAddress(an account, contract or other address). A String or Symbol in that position does not qualify.
Topic layouts by protocol
Before Protocol 23 (version 3 metadata)
Before Protocol 23 (version 3 metadata)
The data must be an
i128 amount.Protocol 23 and later (version 4 metadata)
Protocol 23 and later (version 4 metadata)
The data is either an
i128 amount or a map whose keys are all Symbols. The map
needs an i128 amount entry and can carry to_muxed_id as a U64, Bytes,
String or Void value; other keys are ignored. A non-Symbol key or a missing
amount disqualifies the event.Some custom tokens built with older contract SDKs still emit the earlier
["mint", admin, to] and ["clawback", admin, from] shapes. When topic 2 of a
mint or clawback is an ScAddress, Lumeus reads that layout: to (mint) or
from (clawback) is topic 2, and the admin in topic 1 is dropped. The record is
attributed to the holder, so it appears in the holder’s account history. A
Stellar Asset Contract mint or clawback carries the asset String in topic 2
and keeps to/from = topic 1.to_muxed_id becomes to_muxed_id (U64), to_muxed_hash (Bytes, hex) or
to_muxed_text (String). to_muxed_address is added when the recipient is a
G account.Fee events
fee records are not read from contract events. They are generated for every
transaction from its fee result, paid by the fee account (the outer account of a
fee bump) in the XLM asset contract. A Soroban refund is a second fee record
with a negative amount. A custom contract that emits a ["fee", ...] event keeps
it as a contract_event.
Stellar Asset Contracts
After an event qualifies, Lumeus checks whether it came from a Stellar Asset Contract: the topic count must exactly match the SAC layout, and the last topic must be the asset string whose contract ID equals the emitting contract. A match fillsasset (native or CODE:ISSUER) and issuer for mint, burn and
clawback. A custom contract cannot claim a SAC asset this way, because the
contract ID must match.
Custom contract tokens
A qualifying event from any other contract is a custom token movement:assetis absent from the record (notnull);contract_addressidentifies the token.amountequalsamount_raw, the raw integer. Lumeus applies no decimal scaling because it does not know the contract’s decimals.- There is no
issuer. - The record is attributed to its
fromandtoaddresses, so it appears in their account history, and to the contract for contract queries.
Events that stay contract_event
A non-conforming event keepskind: "contract_event", its first-topic name as
type (or event when the first topic is not a Symbol), decoded topics and
data, and exact topics_xdr and data_xdr. It has no from, to or amount.
It is filed under the emitting contract, so it is not in
/accounts/{id}/activity. Addresses in topics 1 to 3 are indexed: find the
events that name an address with
GET /accounts/{id}/contract-events, or one
contract’s events with /contracts/{id}/activity?topic1={address} (also
topic2, topic3). Addresses inside data are not indexed.
For webhooks, an account filter never matches a contract_event, but an
event_types filter compares type for both kinds: a non-conforming event
named transfer matches event_types: ["transfer"]. Check kind in your
receiver.