Skip to content

feat: validate transfer recipient party#362

Merged
sadiq1971 merged 3 commits into
mainfrom
feat/transfer-recipient-validation
Jul 15, 2026
Merged

feat: validate transfer recipient party#362
sadiq1971 merged 3 commits into
mainfrom
feat/transfer-recipient-validation

Conversation

@sadiq1971

@sadiq1971 sadiq1971 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Problem

Transfers to a raw to_party_id were only syntax-checked. Two failure modes surfaced as 500s at Canton submission:

  • PROMPT/DEMO are internal tokens not hosted on external nodes, but a transfer to an external party id was still submitted and failed on-ledger.
  • USDCx (the supported external-transfer demo token) to a well-formed but nonexistent party id also failed only at submission.

Changes

Recipient validation (pkg/transfer) — both Prepare (raw party-id form) and SendCustodial now run checkRecipientParty after syntax/self checks:

  • Recipient found in the user DB → registered user, any supported token.
  • Not in DB + internal-only token → 400 recipient party is not registered with this service and <TOKEN> does not support transfers to external parties.
  • Not in DB + external token → the party must exist in the participant topology, else 400 recipient party id is not known on the network. Topology lookup failure → 502.

Token config — new external_transfer flag on supported_tokens entries marks tokens transferable to unregistered/external parties. Set for USDCx in the deploy configs; PROMPT/DEMO default to internal-only.

Topology check (pkg/cantonsdk/identity) — new PartyExists on the identity client using PartyManagementService.GetParties (unknown parties are omitted from the response rather than erroring).

Error mapping safety net — Canton rejections from PrepareTransfer/TransferByPartyID now map InvalidArgument/NotFound/FailedPrecondition → 400 and Unavailable/DeadlineExceeded → 502 via mapTransferErr, so races that slip past the pre-checks no longer 500.

Tests

  • Internal token → unregistered party rejected (prepare + custodial)
  • USDCx → topology-known external party succeeds (prepare + custodial)
  • USDCx → unknown party → 400; topology lookup outage → 502
  • Ledger NOT_FOUND on submission → 400, not 500
  • Existing DEMO→party-id tests updated to stub a registered recipient

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for transferring tokens to external participant nodes (external transfers, e.g., USDCx) by adding an external_transfer configuration option and implementing a PartyExists check in the Canton identity client. The TransferService has been updated to validate recipient party IDs, ensuring unregistered parties are only allowed for external-transfer tokens and exist within the network topology. The review feedback highlights critical improvements to prevent potential nil pointer dereferences in PartyExists and TransferService, and suggests using errors.As in mapTransferErr to robustly extract gRPC status codes from wrapped errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread pkg/transfer/service.go
Comment thread pkg/transfer/service.go
Comment thread pkg/cantonsdk/identity/client.go
@codecov-commenter

codecov-commenter commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 32 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@83d4116). Learn more about missing BASE report.

Files with missing lines Patch % Lines
pkg/transfer/service.go 64.00% 16 Missing and 2 partials ⚠️
pkg/cantonsdk/identity/client.go 0.00% 13 Missing ⚠️
pkg/app/api/server.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #362   +/-   ##
=======================================
  Coverage        ?   33.81%           
=======================================
  Files           ?      163           
  Lines           ?    12585           
  Branches        ?        0           
=======================================
  Hits            ?     4255           
  Misses          ?     7989           
  Partials        ?      341           
Flag Coverage Δ
unittests 33.81% <50.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/token/config.go 83.33% <ø> (ø)
pkg/app/api/server.go 0.00% <0.00%> (ø)
pkg/cantonsdk/identity/client.go 0.00% <0.00%> (ø)
pkg/transfer/service.go 73.29% <64.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread pkg/cantonsdk/identity/client.go

@dhyaniarun1993 dhyaniarun1993 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base branch seems incorrect

Transfers to a raw to_party_id previously failed at Canton submission
with an opaque 500 when the recipient was unusable. Now validated
up front in both the prepare/execute and custodial flows:

- recipient registered in the user DB: any supported token
- unregistered recipient + internal-only token (PROMPT/DEMO): 400
- unregistered recipient + external token (USDCx): party must exist
  in the participant topology (PartyManagementService.GetParties),
  else 400

Tokens are marked externally transferable via a new external_transfer
flag in token config (set for USDCx). Remaining ledger rejections map
to 400/502 instead of 500 via mapTransferErr.
Self-review findings: Aborted (Canton contention) now maps to Conflict
like mapWithdrawErr; mapTransferErr wraps with the op before
classification so logs keep prepare-vs-send context; checkRecipientParty
no longer re-runs validatePartyID (both callers validate first).
@sadiq1971
sadiq1971 force-pushed the feat/transfer-recipient-validation branch from 16a8b03 to c78d6c9 Compare July 14, 2026 17:40
@sadiq1971
sadiq1971 changed the base branch from feat/auth-integration to main July 14, 2026 17:41
@sadiq1971
sadiq1971 requested a review from dhyaniarun1993 July 14, 2026 18:00
@sadiq1971
sadiq1971 merged commit 924c9d3 into main Jul 15, 2026
3 checks passed
@sadiq1971
sadiq1971 deleted the feat/transfer-recipient-validation branch July 15, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants