Documentation
¶
Index ¶
Constants ¶
const ( TransferStatusPending = "pending" TransferStatusExpired = "expired" TransferStatusCompleted = "completed" TransferStatusCanceled = "canceled" TransferStatusRejected = "rejected" )
Transfer status values used by ListTransfers / the /transfers endpoint. A transfer is "completed" once settled; an offer that hasn't settled is "pending" (or "expired" once past its executeBefore). "expired" is a derived status — it is never persisted, only computed at read time from a still-pending row whose ExpiresAt is in the past. "canceled" is persisted when the sender withdraws (claims back) the offer; "rejected" when the receiver declines it. Both mean the offer did not settle and the escrowed funds returned to the sender — they differ only in who ended it.
const ( // TransferKindDirect is our atomic CIP-56 TokenTransferEvent — a single-step // settled transfer. Always Status "completed". TransferKindDirect = "direct" // TransferKindOffer is a 2-step (offer-based) transfer, e.g. USDCx. It starts // "pending" on the TransferOffer CREATE and on its ARCHIVE becomes "completed" // (accepted), "canceled" (sender withdrew), or "rejected" (receiver declined). TransferKindOffer = "offer" )
Transfer kind values, recorded on Transfer.Kind / the indexer_transfers table.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Balance ¶
type Balance struct {
PartyID string `json:"party_id"` // canton party (ERC-20: address)
InstrumentAdmin string `json:"instrument_admin"` // instrumentId.admin
InstrumentID string `json:"instrument_id"` // instrumentId.id
Amount string `json:"amount"` // current balance, decimal string ≥ 0
}
Balance is a party's current token holding for a specific instrument. (ERC-20: the per-address entry in the balances mapping, i.e. balanceOf(address).)
Amount is a non-negative decimal string representing the live balance, e.g. "1500.000000000000000000". Updated by the store via delta arithmetic (Store.ApplyBalanceDelta) — the store adds the signed delta to the persisted value.
type Config ¶
type Config struct {
// CIP56PackageID is the DAML package ID containing CIP56.Events.TokenTransferEvent.
// Setting this pins the indexer to a specific package version; leave empty to
// match the template across all package versions.
CIP56PackageID string `yaml:"cip56_package_id" validate:"required"`
// FilterMode controls which instruments are indexed.
// "all" — index every TokenTransferEvent on the stream.
// "whitelist" — only index instruments listed in Instruments.
FilterMode string `yaml:"filter_mode" default:"all" validate:"required,oneof=all whitelist"`
// Instruments is the whitelist of CIP-56 instruments to index.
// Only consulted when FilterMode is "whitelist".
Instruments []InstrumentKey `yaml:"instruments"`
// UtilityRegistryPackageID is the DAML package ID for the Utility Registry app
// (Utility.Registry.App.V0.Model.Transfer.TransferOffer).
// Leave empty to disable TransferOffer tracking.
UtilityRegistryPackageID string `yaml:"utility_registry_package_id"`
// UtilityRegistryHoldingPackageID is the DAML package ID for the Utility Registry
// Holding template (Utility.Registry.Holding.V0.Holding). Required to track
// USDCx-style balances — without it the indexer never sees Holding contract
// create/archive events and balances for AllocationFactory-based instruments
// stay at 0. Leave empty to disable Holding tracking.
UtilityRegistryHoldingPackageID string `yaml:"utility_registry_holding_package_id"`
}
Config holds stream-specific settings for the indexer process. It lives in the indexer domain package so that app-level config (pkg/config) can embed it without creating a god-config pattern.
func (*Config) FilterModeAndKeys ¶
func (c *Config) FilterModeAndKeys() (FilterMode, []InstrumentKey)
FilterModeAndKeys converts the config into the domain FilterMode and instrument key slice expected by engine.NewTokenTransferDecoder.
type EventFilter ¶
type EventFilter struct {
InstrumentAdmin string
InstrumentID string
PartyID string
EventType EventType // empty = all types
}
EventFilter narrows event list queries. Zero-value fields are ignored by the store.
type EventType ¶
type EventType string
EventType classifies a TokenTransferEvent as MINT, BURN, or TRANSFER. Derived from the fromParty/toParty Optional fields — mirrors ERC-20 Transfer semantics:
MINT: fromParty = None, toParty = Some(recipient) BURN: fromParty = Some(owner), toParty = None TRANSFER: fromParty = Some(sender), toParty = Some(receiver)
type FilterMode ¶
type FilterMode int
FilterMode controls which token instruments the Parser processes.
const ( // FilterModeAll indexes events from every instrument — equivalent to a global // ERC-20 Transfer log covering all CIP56 token deployments visible to the indexer. FilterModeAll FilterMode = iota // FilterModeWhitelist indexes only events whose InstrumentKey{Admin, ID} is in // the allowed set. Use this for an operator who manages a fixed set of tokens. // Both Admin and ID must match — this is the Canton equivalent of whitelisting // by ERC-20 contract address. FilterModeWhitelist )
type HoldingChange ¶
type HoldingChange struct {
ContractID string
IsArchived bool
LedgerOffset int64
// Only populated for CREATED events. ARCHIVED events leave these empty and the
// processor reads the matching row from indexer_holdings by ContractID.
Owner string
InstrumentAdmin string
InstrumentID string
Amount string
// Locked is true when the holding carries a lock (DAML `lock` is Some) — i.e. it
// is escrowed by an outstanding transfer offer rather than spendable. Locked
// holdings are excluded from indexed balances so a sender's balance reflects the
// offered amount as deducted from offer creation until accept or claim-back.
// Only meaningful for CREATED events.
Locked bool
}
HoldingChange is a Utility.Registry.Holding.V0.Holding lifecycle event. Each CREATED event becomes a synthetic MINT-style balance increment for the owner; each ARCHIVED event becomes the symmetric decrement (looked up from the store using ContractID since archive events carry no field payload). Unlike CIP-56 — which emits dedicated TokenTransferEvent contracts — Utility.Registry tokens have no separate event template, so the indexer derives balance deltas from the Holding contracts themselves to keep indexer_balances consistent for USDCx and similar instruments.
type InstrumentKey ¶
type InstrumentKey struct {
Admin string `yaml:"admin"` // instrumentId.admin — the token admin/issuer party
ID string `yaml:"id"` // instrumentId.id — the token identifier (e.g. "DEMO")
}
InstrumentKey is the Canton equivalent of an ERC-20 contract address. It uniquely identifies a CIP56 token deployment. Corresponds to the DAML InstrumentId{admin: Party, id: Text} record.
instrumentId.id alone is NOT unique — two different issuers can both deploy a token with id="DEMO". The full {Admin, ID} pair IS unique and is the correct key for whitelisting specific token deployments.
type Page ¶
type Page[T any] struct { Items []T `json:"items"` Total int64 `json:"total"` Page int `json:"page"` Limit int `json:"limit"` }
Page is the generic paginated response envelope.
type Pagination ¶
Pagination holds a 1-based page number and page size for list queries.
type ParsedEvent ¶
type ParsedEvent struct {
// Instrument identification — fully qualified by both fields.
InstrumentID string `json:"instrument_id"` // instrumentId.id — token identifier (e.g. "DEMO", "PROMPT")
InstrumentAdmin string `json:"instrument_admin"` // instrumentId.admin — token admin/issuer party
// Issuer of the TokenTransferEvent contract (the token config issuer).
Issuer string `json:"issuer"`
// Transfer semantics, mirroring ERC-20 Transfer(from, to, value).
EventType EventType `json:"event_type"`
Amount string `json:"amount"` // decimal string, e.g. "1.500000000000000000"
FromPartyID *string `json:"from_party_id,omitempty"` // nil for mints
ToPartyID *string `json:"to_party_id,omitempty"` // nil for burns
// Bridge audit context extracted from meta.values (nil for native peer-to-peer transfers).
ExternalTxID *string `json:"external_tx_id,omitempty"` // meta["bridge.externalTxId"] — EVM transaction hash
ExternalAddress *string `json:"external_address,omitempty"` // meta["bridge.externalAddress"] — EVM destination address
Fingerprint *string `json:"fingerprint,omitempty"` // meta["bridge.fingerprint"] — user fingerprint
// Provenance.
ContractID string `json:"contract_id"` // TokenTransferEvent contract ID — idempotency key (event_id in store)
TxID string `json:"tx_id"` // Ledger transaction UpdateId
LedgerOffset int64 `json:"ledger_offset"` // Ledger offset of the containing transaction
Timestamp time.Time `json:"timestamp"` // Contract-level time from TokenTransferEvent.timestamp
EffectiveTime time.Time `json:"effective_time"` // Ledger transaction effective time
}
ParsedEvent is a fully decoded TokenTransferEvent ready for the processor.
Fields map directly to the DAML TokenTransferEvent template in CIP56.Events:
issuer → Issuer instrumentId → InstrumentID (id field) + InstrumentAdmin (admin field) fromParty → FromPartyID (*string, nil for mints) toParty → ToPartyID (*string, nil for burns) amount → Amount (decimal string) timestamp → Timestamp (contract-level time, from the DAML event) meta.values → ExternalTxID, ExternalAddress, Fingerprint (bridge context, nil for transfers)
ContractID (the TokenTransferEvent contract ID) is the idempotency key used as event_id in the store — guaranteed unique across the ledger.
Primary identity throughout is canton_party_id — no EVM address at this layer.
type Token ¶
type Token struct {
// Identity — canonical composite key.
InstrumentAdmin string `json:"instrument_admin"` // instrumentId.admin — token admin/issuer party (ERC-20: deployer)
InstrumentID string `json:"instrument_id"` // instrumentId.id — token symbol/identifier (ERC-20: symbol, e.g. "DEMO")
// Roles.
Issuer string `json:"issuer"` // issuer party on the TokenTransferEvent contract (ERC-20: minter role)
// Supply (ERC-20: totalSupply()).
// Running total, always ≥ 0. Incremented by each MINT amount, decremented by each BURN amount.
// Updated atomically with every mint/burn via Store.ApplySupplyDelta.
TotalSupply string `json:"total_supply"` // decimal string, e.g. "1000000.000000000000000000"
// Holders (ERC-20: no standard equivalent, but a standard block-explorer metric).
// Count of distinct parties currently holding a non-zero balance.
// The store increments this when a balance first becomes positive, decrements when it returns to zero.
HolderCount int64 `json:"holder_count"`
// Provenance.
FirstSeenOffset int64 `json:"first_seen_offset"` // ledger offset when this token was first indexed
FirstSeenAt time.Time `json:"first_seen_at"` // ledger effective time when this token was first indexed
}
Token represents a CIP56 token deployment, uniquely identified by {InstrumentAdmin, InstrumentID}. A Token record is created the first time the indexer observes a TokenTransferEvent for a given instrument pair. It tracks the ERC-20-equivalent on-chain state derivable from transfer events.
ERC-20 parallel:
symbol() → InstrumentID
owner/minter → InstrumentAdmin, Issuer
totalSupply() → TotalSupply (maintained: +amount on MINT, -amount on BURN)
HolderCount (non-standard but shown on all block explorers)
type Transfer ¶ added in v0.8.0
type Transfer struct {
ContractID string `json:"contract_id"`
Kind string `json:"kind"` // "direct" | "offer"
Status string `json:"status"` // "pending" | "expired" | "completed" | "canceled"
FromPartyID string `json:"from_party_id"`
ToPartyID string `json:"to_party_id"`
InstrumentAdmin string `json:"instrument_admin"`
InstrumentID string `json:"instrument_id"`
Amount string `json:"amount"`
ExpiresAt *time.Time `json:"expires_at,omitempty"` // offer executeBefore; nil for direct
TxID string `json:"tx_id,omitempty"` // ledger update id
LedgerOffset int64 `json:"ledger_offset"`
CreatedAt time.Time `json:"created_at"`
// Archived is a decode-time signal only — not persisted. Set by the offer
// decoder on an ARCHIVED event so the processor finalizes the transfer with
// the terminal Status the decoder derived from the archiving choice.
Archived bool `json:"-"`
}
Transfer is a token transfer, generalized across all tokens and both transfer shapes. Direct transfers (Kind "direct") are our atomic CIP-56 TokenTransferEvents; 2-step transfers (Kind "offer") are offer-based, e.g. USDCx. Rows live in indexer_transfers with a mutable status lifecycle: offers start "pending" and on archive become "completed" (accepted) or "canceled" (withdrawn/rejected); direct transfers are always "completed". "expired" is derived at read time and never stored.
type TransferQuery ¶ added in v0.8.0
type TransferQuery struct {
Role TransferRole
Status string // "" = all; pending / expired / completed / canceled / rejected
}
TransferQuery filters a party's transfers by role and status. A zero Role defaults to receiver; a zero Status means "all statuses".
type TransferRole ¶ added in v0.8.0
type TransferRole string
TransferRole selects which side of a transfer a party query matches.
const ( TransferRoleSender TransferRole = "sender" // transfers sent BY the party (outgoing) TransferRoleReceiver TransferRole = "receiver" // transfers sent TO the party (incoming) TransferRoleAny TransferRole = "any" // either side )