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
feat(api): transfer history + outgoing/expired endpoints (#331)
* feat(api): transfer to external party via party id
Enable sending a token to a Canton party that is not registered in the
middleware (e.g. a party on an external participant node), addressed by
its party id rather than an EVM address.
- Non-custodial: PrepareRequest accepts an optional to_party_id; when set,
Prepare uses it directly and skips the userstore recipient lookup.
Reuses the existing /prepare + /execute flow.
- Custodial: new POST /api/v2/transfer/custodial does a single server-signed
transfer via TransferByPartyID (the middleware holds the user's key).
- Shared validatePartyID helper (hint::fingerprint) and self-transfer guard.
Authorization/whitelist policy and outgoing-offer tracking are out of scope
(tracked separately).
* fix(api): reject ambiguous recipient in Prepare at the service layer
Address review feedback: Prepare now explicitly rejects a request that
sets both (or neither) of `to` and `to_party_id` instead of silently
preferring `to_party_id`. The HTTP handler already enforced this; this
adds the same guard at the service boundary for direct callers.
* feat(indexer): move storage to generalized indexer_transfers table
Introduce a single indexer_transfers table holding both direct (atomic
CIP-56) and offer-based (2-step, e.g. USDCx) transfers with a mutable status
lifecycle, and retire indexer_pending_offers. The indexer's external API is
unchanged: the existing pending-offers endpoints are served by a thin
compatibility shim over the new table, so the api-server and custodial accept
worker are untouched (API modernization is a follow-up PR).
- Migration 7: create indexer_transfers, backfill history from the offers
table + the TRANSFER event log, then drop indexer_pending_offers. No data
lost; the two sources are disjoint so nothing is double-counted.
- Processor: offer CREATE -> insert pending transfer; ARCHIVE -> complete;
CIP-56 TRANSFER event -> upsert a direct (completed) transfer. Balances are
untouched (still maintained from the event/holding paths).
- Store: new transfer write methods; ListPendingOffersForParty /
ListAllPendingOffers reimplemented over indexer_transfers, returning the
existing PendingOffer shape.
* refactor(indexer): address review on transfers PR
- Remove the unused Transfer.Archived field (the offer decoder uses
PendingOffer.IsArchived in this PR).
- Document why the migration loads rows in memory (the per-participant
transfer history is small) and note the INSERT…SELECT alternative if it
ever grows.
- Add composite (from_party_id, created_at) / (to_party_id, created_at)
indexes so the list-by-party-newest-first queries avoid a filesort.
* refactor(indexer): single InsertTransfer; review fixes on transfers PR
Address PR review:
- Consolidate to one write method: InsertTransfer now records a transfer with
the caller-provided Kind/Status (offer/pending or direct/completed) instead
of two methods that each forced them. Removes UpsertDirectTransfer. The
ON CONFLICT (contract_id) DO NOTHING makes it idempotent for ledger-stream
replays (the indexer re-reads from the last saved offset on restart).
- migration 5: clearer comment on legacyOffer — PendingOfferDao was removed,
the struct stays here for backward compat so the historical migrations can
manage the legacy table with bun.
- migration 7: order the backfill by ledger_offset (canonical monotonic
sequence) instead of created_at (an effective-time that can tie).
* feat(api): transfer history + outgoing/expired endpoints on indexer_transfers
Re-synced on the updated storage PR (#330): inherits the single InsertTransfer
write method (no UpsertDirectTransfer) and the migration fixes.
- Indexer: /parties/{id}/pending-offers -> /parties/{id}/transfers?role=&status=
+ /pending-transfers; ListTransfers is a single-table query with native
pagination and a derived "expired" status. Removes the PendingOffer compat
shim; decoder/processor now produce Transfer.
- api-server: /api/v2/transfer/incoming (unchanged shape) + /outgoing and
/completed.
- Accept worker reads GetPendingTransfers, oldest-pending first (FIFO).
* docs(indexer): fix stale ListPendingTransfers order comment
The doc comment still said 'created_at DESC' from before the FIFO change; the
query is created_at ASC (oldest first). Also reword the inline comment to be
precise — it's FIFO (longest-pending first), which correlates with nearest
expiry rather than strictly ordering by it.
* test(indexer): mirror migration 7 composite indexes in pg_test setup
0 commit comments