Sitelet https://github.com/ChainSafe/canton-snap/pull/84
Skip to content

feat(dapp): transfer by Canton party id with offer expiry - #84

Merged
sadiq1971 merged 5 commits into
mainfrom
feat/transfer-by-party-id
Jun 29, 2026
Merged

sadiq1971 merged 5 commits into
mainfrom
feat/transfer-by-party-id

Conversation

@sadiq1971

Copy link
Copy Markdown
Member

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:

Mode × recipient Path Notes
Non-custodial · address POST /transfer/prepare → snap sign → /execute sends to
Non-custodial · party POST /transfer/prepare → snap sign → /execute sends to_party_id
Custodial · address existing ERC-20 transfer() via /eth unchanged
Custodial · party POST /transfer/custodial (server-signed) settles directly; no eth tx hash

Offer expiry (validity_seconds)

Per canton-middleware#334, validity_seconds is 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

  • Sends exactly one of to / to_party_id (no recipient_type discriminator).
  • Custodial party-id receipt settles immediately and renders without a transaction row (the endpoint returns {status:"completed"}, no eth hash).

Self-review notes

  • ⚠️ Sequencing / breaking change: validity_seconds becomes 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).
  • Endpoints (/transfer/prepare with to_party_id, /transfer/custodial) depend on canton-middleware#334/#335 being deployed. Routes/shapes were taken from pkg/transfer/http.go + types.go on the middleware main/PR branches.
  • The existing custodial-address ERC-20 path is untouched; non-party flows behave exactly as before aside from now sending validity_seconds.
  • tsc, eslint, and vite build all 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

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.
@sadiq1971 sadiq1971 added the Type: Feature Added to issues and PRs to identify that the change is a new feature. label Jun 25, 2026

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

Copy link
Copy Markdown

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 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.

Comment thread packages/dapp/src/pages/TransferPage.tsx
Comment thread packages/dapp/src/pages/TransferPage.tsx Outdated
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.
@sadiq1971

Copy link
Copy Markdown
Member Author

Thanks @gemini-code-assist — both addressed in 915a563:

  • handleReset now resets recipientType → address and validitySeconds → DEFAULT_VALIDITY_SECONDS, so "Send another" starts clean with the "1 day" preset highlighted.
  • Custom expiry input switched to inputMode="decimal" since fractional values (e.g. 1.5 hours) are allowed.

@gemini-code-assist

Copy link
Copy Markdown

Thanks for the update, @sadiq1971. The changes in handleReset and the inputMode adjustment look correct and improve the user experience for the new transfer flow. Everything looks good to go.

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.
@sadiq1971

Copy link
Copy Markdown
Member Author

Update: offer expiry is now token-gated + configurable

Offer 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.

  • Which tokens are offer-based is configurable via VITE_OFFER_EXPIRY_TOKENS (comma-separated symbols, default USDCX) — see isOfferBasedToken() in lib/config.ts and the documented var in packages/dapp/.env.example.
  • The expiry control and its client-side validation are gated on the selected token being offer-based.
  • Custodial party-id info copy now adapts: "offer to accept" for offer-based tokens vs. "sent directly" otherwise.
  • A default validity_seconds is still sent on prepare/custodial to satisfy the mandatory field even when the picker is hidden.

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.
@sadiq1971

Copy link
Copy Markdown
Member Author

Correction to the note above: per review, the offer-based token list is now a plain config constant (OFFER_BASED_TOKENS in lib/config.ts), not an env var — VITE_OFFER_EXPIRY_TOKENS was dropped. Edit the array to change which tokens show the expiry field. Commit 0cccfd3.

@salindne
salindne self-requested a review June 25, 2026 20:58

@salindne salindne 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.

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

@sadiq1971
sadiq1971 merged commit ef53bb6 into main Jun 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Feature Added to issues and PRs to identify that the change is a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants