You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For offer/accept-flow tokens (e.g. USDCx, via the Splice AllocationFactory/TransferInstruction), creating a transfer offer locks the sender's holding on-ledger but does not move it. The indexer currently derives balances purely from Utility.Registry.Holding create/archive (and CIP-56 TokenTransferEvents), so:
The sender's indexed balance only decreases when the offer is accepted (their locked holding is archived and a new one is created for the receiver).
Between offer creation and accept, the locked amount still counts toward the sender's indexed balance, even though it is not spendable.
This is misleading: a user who has offered out their entire balance still shows the full balance until the counterparty accepts.
Desired behavior
Track the sender's spendable balance by accounting for the lock at offer time:
Offer created (indexer_transfers row goes pending, kind=offer): deduct the amount from the sender's balance.
Offer accepted (archive → completed): credit the receiver (as today). The sender was already debited at offer time, so do not debit again.
Offer withdrawn / claimed back (sender reclaims a pending/expired offer — see feat: claim back expired offers #292): credit the sender back (the lock is released).
Offer expired but not yet withdrawn: amount stays deducted (funds remain locked on-ledger until withdrawn).
Direct CIP-56 transfers (DEMO/PROMPT) settle atomically with no offer, so they are unaffected.
Implementation notes / risks
The processor today applies balance deltas from holding create/archive and from CIP-56 events (balanceUpdatesFromEvent, pkg/indexer/engine/processor.go). Moving the sender debit to offer time must not double-count against the holding-archive debit that fires on accept. The offer/holding lifecycle for USDCx needs to be reconciled so each unit is debited exactly once.
Decide the model explicitly: either (a) balance = "spendable" (holdings minus outstanding outbound offers), computed by layering offer deltas on top of holding deltas, or (b) drive sender debits from the indexer_transfers offer lifecycle and suppress the holding-archive debit on accept. Whichever is chosen must be idempotent under ledger-stream replay (offers are keyed by contract_id).
Must handle the offer terminal states symmetrically: accept (debit stays, receiver credited), withdraw (debit reversed), expiry (debit stays until withdrawn).
Background
For offer/accept-flow tokens (e.g. USDCx, via the Splice
AllocationFactory/TransferInstruction), creating a transfer offer locks the sender's holding on-ledger but does not move it. The indexer currently derives balances purely fromUtility.Registry.Holdingcreate/archive (and CIP-56TokenTransferEvents), so:This is misleading: a user who has offered out their entire balance still shows the full balance until the counterparty accepts.
Desired behavior
Track the sender's spendable balance by accounting for the lock at offer time:
indexer_transfersrow goespending,kind=offer): deduct the amount from the sender's balance.completed): credit the receiver (as today). The sender was already debited at offer time, so do not debit again.Direct CIP-56 transfers (DEMO/PROMPT) settle atomically with no offer, so they are unaffected.
Implementation notes / risks
balanceUpdatesFromEvent,pkg/indexer/engine/processor.go). Moving the sender debit to offer time must not double-count against the holding-archive debit that fires on accept. The offer/holding lifecycle for USDCx needs to be reconciled so each unit is debited exactly once.indexer_transfersoffer lifecycle and suppress the holding-archive debit on accept. Whichever is chosen must be idempotent under ledger-stream replay (offers are keyed bycontract_id).Scope
AllocationFactory) tokens only.Acceptance
Relates to #199 (USDCx) and #292 (claim back expired offers).