feat(dapp): transfer by Canton party id with offer expiry - #84
Conversation
Add a recipient-type toggle (EVM address / Canton party id) to the Transfer flow and route each path to the matching middleware endpoint: - Non-custodial sends (address or party) go through prepare/execute, sending `to` or `to_party_id` plus the now-mandatory `validity_seconds` (canton-middleware#334). - Custodial party-id sends use the server-signed POST /transfer/custodial endpoint; custodial address sends keep the existing ERC-20 path. The custodial-party receipt settles immediately with no eth tx hash. Add an Offer expiry control (presets + a custom value/unit) shown for any send that creates an on-ledger offer, feeding `validity_seconds`. Validate party ids and the expiry (positive, <= 365 days) client-side. Implements #80.
There was a problem hiding this comment.
Code Review
This pull request introduces support for Canton Party IDs as transfer recipients alongside EVM addresses, enabling both non-custodial and custodial single-call transfers. It adds a recipient type toggle, an offer expiry picker with presets and custom duration options, and corresponding validation and API integration. The feedback highlights two important improvements: resetting the validitySeconds and recipientType states in handleReset() to prevent inconsistent UI states when initiating a new transfer, and changing the custom expiry input's inputMode from "numeric" to "decimal" to ensure mobile users can input fractional values.
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.
Reset recipientType and validitySeconds in handleReset so the form starts clean (no orphaned custom expiry with nothing highlighted), and use inputMode="decimal" since the custom expiry accepts fractional values. Addresses Gemini review on #84.
|
Thanks @gemini-code-assist — both addressed in 915a563:
|
|
Thanks for the update, @sadiq1971. The changes in |
Offer expiry applies only to offer-based tokens (USDCx today) where the recipient must accept; other tokens settle directly, so the expiry field is hidden for them. Which tokens are offer-based is configurable via VITE_OFFER_EXPIRY_TOKENS (comma-separated symbols, default USDCX). Gate the expiry control and its validation on the selected token, and adapt the custodial-party info copy for direct vs. offer sends.
Update: offer expiry is now token-gated + configurableOffer expiry only makes sense for offer-based tokens (USDCx today) where the recipient must accept. Other tokens settle directly, so the Offer expiry field is now hidden for them.
Commit d7e54ad. |
Drop VITE_OFFER_EXPIRY_TOKENS; keep the offer-based token list as a plain constant (OFFER_BASED_TOKENS) in lib/config.ts. No env var needed.
|
Correction to the note above: per review, the offer-based token list is now a plain config constant ( |
salindne
left a comment
There was a problem hiding this comment.
1 day - DEFAULT_VALIDITY_SECONDS in this PR's lib/transfer.ts
30 days - ethRPCTransferValidity in middleware pkg/token/service.go
The default times seem to differ between middleware PR 334 and this PR, just flagging in case
What
Adds transfer by Canton party id to the Transfer flow, plus a user-settable offer expiry. Implements #80.
A recipient-type toggle (EVM Address / Canton Party ID) lets users send to a party id (
<hint>::<fingerprint>), including parties on an external participant. Each path is routed to the matching middleware endpoint:POST /transfer/prepare→ snap sign →/executetoPOST /transfer/prepare→ snap sign →/executeto_party_idtransfer()via/ethPOST /transfer/custodial(server-signed)Offer expiry (
validity_seconds)Per canton-middleware#334,
validity_secondsis now mandatory on prepare and required by the custodial endpoint. For an offer-based send it's the acceptance window. Added an Offer expiry control — presets (1h / 6h / 1d / 1w, default 1d) plus a Custom value + unit (minutes / hours / days) — shown for every send that creates an on-ledger offer (all non-custodial sends, and custodial party-id sends). Validated client-side: positive, ≤ 365 days.Aligned to the real contract
to/to_party_id(norecipient_typediscriminator).{status:"completed"}, no eth hash).Self-review notes
validity_secondsbecomes mandatory when canton-middleware#334 merges — once it does, the existing address transfer 400s without it. This PR always sends it, so it should ship in lockstep with that middleware release (or gated on the deployed version)./transfer/preparewithto_party_id,/transfer/custodial) depend on canton-middleware#334/#335 being deployed. Routes/shapes were taken frompkg/transfer/http.go+types.goon the middlewaremain/PR branches.validity_seconds.tsc,eslint, andvite buildall pass. No automated transfer-flow tests exist in the dapp; verified by build + manual review. Recommend a manual pass on devnet against the merged middleware.Out of scope
Implements #80.
🤖 Generated with Claude Code