Indexing and interpretation are separate backend stages. Indexing writes raw encrypted audit rows from chain events. Interpretation decrypts those rows and writes normalized audit_interpretation rows used by cases, reports, and review screens.

End-to-end pipeline

Scanner runtime components

ComponentResponsibility
ScannerModule.registerRegisters scanner providers for configured Stellar and Solana chain names
StellarRpcModule.registerLoads a chain row by name, validates type = stellar, creates @stellar/stellar-sdk/rpc.Server
LedgerScannerSchedulerServiceRuns bootstrap scan and scheduled Stellar scan ticks
StellarLedgerScannerServiceLists contracts for one Stellar chain and scans each contract
performStellarContractScanCalculates ledger span from checkpoint, RPC retention, and scanner config
collectStellarAuditRowsReads ledger pages and collects accepted audit rows
buildAuditFromStellarEventMaps Soroban event and calldata into an audit upsert object
persistScanResultsQueryUpserts audit, writes audit_chain, updates stellar_scans

Contract selection

The scanner does not scan arbitrary chain activity.
chain(type = stellar) -> contracts(chain_id) -> StellarLedgerScannerService
applications.association.contract_id links an Arcane application to a registered contract. That link is used later by application-scoped API routes and disclosure workflows.

Ledger span selection

For each contract, the scanner determines:
InputSource
Previous checkpointstellar_scans.block_end for (contract_id, chain_id)
Initial hintchain.initial_ledger_hint or STELLAR_INITIAL_LEDGER
RPC lower boundgetLedgers(...).oldestLedger
RPC tipgetLatestLedger().sequence
Batch sizeSCANNER_LEDGERS_PER_REQUEST
The next scan starts at max(previousEnd + 1, oldestLedger). If the next ledger is past the RPC tip, the scan is skipped for that contract.

Event parsing

For each ledger page:
  1. Read ledger metadata from Stellar RPC.
  2. Extract Soroban events for the registered contract address.
  3. Ignore events that are not in successful contract calls.
  4. Read transact calldata from ledger metadata when available.
  5. Map the current audit topic to event_type = transact.
The current contract emits AuditEncodedDigest under the audit topic with message_name = "transact".

Raw audit row

The scanner writes one audit row per accepted event.
ColumnSource
contract_idRegistered contracts.id
tx_idStellar event transaction hash
soroban_event_idStellar event id; unique with contract_id
event_typetransact for current AuditEncodedDigest events
cyphertextSealed audit payload from the event value
created_atLedger close time
interpretedfalse until interpretation succeeds
signer_accountSigner resolved from transact calldata, fallback unknown
public_signals_jsonParsed transaction public signals
interpretation_errorLast interpretation error, if any
The scanner also writes audit_chain and advances stellar_scans. The audit table is unique by (contract_id, soroban_event_id).

Interpretation runner

AuditInterpretationRunnerService runs on a 12-second interval. Batch behavior:
  1. Open a database transaction.
  2. Lock up to 25 uninterpreted audit rows.
  3. Route Solana confidential-token rows to interpretCtAuditRow.
  4. Route all other rows to interpretStellarAuditRow.
  5. Replace interpretation rows for each successfully interpreted audit row.
  6. Store a truncated interpretation error on the source audit row if interpretation fails.

Stellar interpretation path

interpretStellarAuditRow performs:
StepDetail
Decode-key checkRequires audit.contract.decoding_key
Public signal parsingValidates audit.public_signals_json
Payload decodeCalls decryptAndDecodeTransaction(cyphertext, decoding_key)
PlanningCalls planInterpretations(tx, publicSignals)
PersistenceReplaces rows in audit_interpretation for audit_id
Error captureWrites audit.interpretation_error
Interpretation is backend processing. It does not grant auditor visibility by itself.

Interpreted records

audit_interpretation rows are normalized records.
ColumnDescription
audit_idSource raw audit row
seqSequence when one audit row creates multiple interpreted rows
kindNormalized kind: deposit, withdraw, or transfer
payloadJSONB payload used by case review and reports
amount_stroopsParsed amount when available
The unique key is (audit_id, seq).

Event type versus interpretation kind

LevelExampleMeaning
Raw eventtransactSoroban event/contract-call shape indexed from chain
Interpretation kinddeposit, withdraw, transferNormalized branch planned from decrypted transaction data and public signals
A single raw transact event can produce multiple interpreted rows.

Failure behavior

FailureEffect
No contracts for chainScanner logs and returns
RPC retention moved past checkpointStart ledger clamps to oldestLedger
RPC/network failureScan cycle logs an error; checkpoint is not advanced for failed work
Parser returns no audit rowEvent is ignored
Duplicate eventUpsert updates the existing audit row
Missing decoding keyInterpretation error is written to audit.interpretation_error
Decryption or planning failureInterpretation error is written; indexing is not rolled back