Skip to content

feat(cctp): payout-preference module and forwarding-fee UX - #236

Open
armandocodecr wants to merge 10 commits into
developfrom
Feat/cctp-v2
Open

feat(cctp): payout-preference module and forwarding-fee UX#236
armandocodecr wants to merge 10 commits into
developfrom
Feat/cctp-v2

Conversation

@armandocodecr

@armandocodecr armandocodecr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Adds the CCTP payout-preference flow: cross-chain destination, attestation and complete-mint hooks, forwarding-fee quote shown before the receiver signs, and the receiver-only payout action in the escrow UI. Updates escrow-action-policy to hide the preference once released/resolved. Adapts to develop's milestoneIndexes rename and adds viem as a direct dependency.

Summary by CodeRabbit

  • New Features
    • Added cross-chain payout preferences for supported EVM and Solana destinations.
    • Receivers can select a destination, review live fees, and confirm or clear payout settings.
    • Added mint completion support for EVM wallets, including attestation status tracking.
    • Added payout preference actions to escrow and milestone menus.
  • Documentation
    • Added operational handoff documentation for the cross-chain transfer workflow.
  • Chores
    • Added development debugging configuration and improved local SDK support.

@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
d-app-trustless-work Ready Ready Preview Aug 4, 2026 2:51pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@armandocodecr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dc47e8a-210a-4e88-b148-77ef40a151b9

📥 Commits

Reviewing files that changed from the base of the PR and between 19d8b92 and aba76a4.

📒 Files selected for processing (10)
  • src/features/cctp-bridge/hooks/useCompleteCctpMint.ts
  • src/features/cctp-bridge/hooks/useFeeQuote.ts
  • src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts
  • src/features/cctp-bridge/lib/chains.ts
  • src/features/cctp-bridge/lib/evm.ts
  • src/features/cctp-bridge/schemas/payout-preference.schema.ts
  • src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx
  • src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx
  • src/features/escrows/domain/escrow-action-policy.ts
  • src/features/escrows/ui/actions/PayoutPreferenceAction.tsx
📝 Walkthrough

Walkthrough

The frontend adds CCTP payout destination management and EVM mint completion. It introduces bridge services, React Query hooks, validation, dialogs, chain configuration, receiver authorization, escrow menu integration, and local development documentation.

Changes

CCTP bridge foundation

Layer / File(s) Summary
Bridge contracts and API client
src/features/cctp-bridge/types/*, lib/*, schemas/*, services/*, hooks/useCrossChainDestination.ts, hooks/useFeeQuote.ts, package.json, next.config.ts
Adds typed CCTP APIs, supported destination chains, address validation, fee and destination queries, transaction endpoints, the viem dependency, and the updated local SDK path.

Payout preference workflow

Layer / File(s) Summary
Payout preference workflow
src/features/cctp-bridge/hooks/usePayoutPreference*, ui/PayoutPreferenceDialog.tsx
Adds form validation, fee quotes, unsigned transaction creation, Stellar signing, transaction submission, preference clearing, and confirmation states.

Attestation and mint completion

Layer / File(s) Summary
Attestation and EVM mint completion
src/features/cctp-bridge/lib/evm.ts, hooks/use*Cctp*, ui/CompleteCctpMintDialog.tsx
Polls Circle attestations and submits receiveMessage transactions through an injected EVM wallet after chain validation or setup.

Escrow authorization and integration

Layer / File(s) Summary
Escrow authorization and integration
src/features/escrows/domain/*, ui/actions/*, ui/detail/*, CCTP_SESSION_HANDOFF.md, .claude/launch.json
Adds receiver and milestone authorization checks, exposes payout preference actions in escrow menus, adds policy tests, and documents repository status and local development setup.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Receiver
  participant EscrowMenu
  participant PayoutPreferenceDialog
  participant BridgeService
  participant StellarWallet
  Receiver->>EscrowMenu: open payout preference
  EscrowMenu->>PayoutPreferenceDialog: render authorized action
  PayoutPreferenceDialog->>BridgeService: request quote and unsigned transaction
  BridgeService-->>PayoutPreferenceDialog: return fee and unsigned XDR
  PayoutPreferenceDialog->>StellarWallet: sign transaction
  StellarWallet-->>PayoutPreferenceDialog: return signed XDR
  PayoutPreferenceDialog->>BridgeService: submit signed XDR
Loading

Suggested labels: enhancement, core-dev

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. It omits all required template sections including issue link, issue description, type of change, evidence before/after, and important notes. Only a brief change summary is provided. Complete the pull request description using the provided template. Add issue link, brief issue description, type of change checkboxes, detailed changes list, Loom videos (before/after), and any important setup notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 47.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: adding the CCTP payout-preference module and forwarding-fee UX, which aligns with the main objectives and file changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Feat/cctp-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 10

🧹 Nitpick comments (5)
src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx (1)

138-142: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the spinner-only loading state with a matching skeleton.

Render Skeleton elements for the current-preference text, chain select, address input, and footer actions. The loading layout must match the form layout to prevent layout shift.

As per coding guidelines: “Use co-located loading skeletons built with @/components/ui/skeleton, and make the skeleton mirror the loaded layout exactly.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx` around lines 138 -
142, Update the isLoading branch in PayoutPreferenceDialog to replace the
centered Loader2 spinner with co-located Skeleton components imported from
`@/components/ui/skeleton`. Mirror the loaded form layout with skeletons for the
current-preference text, chain select, address input, and footer actions so the
dialog dimensions remain stable.

Source: Coding guidelines

src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts (2)

11-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use interface for these object shapes.

  • src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts#L11-L17: Convert UsePayoutPreferenceFormOptions to an interface.
  • src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx#L46-L52: Convert PayoutPreferenceDialogProps to an interface.

As per coding guidelines: “Use interface for object shapes and type for unions, intersections, mapped types, and conditional types.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts` around lines 11 -
17, Convert UsePayoutPreferenceFormOptions from type to interface in
src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts lines 11-17 since it
is an object shape, not a union or mapped type. Similarly, convert
PayoutPreferenceDialogProps from type to interface in
src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx lines 46-52 for the same
reason. Both changes align with the coding guideline to use interface for object
shapes and type for unions, intersections, mapped types, and conditional types.

Source: Coding guidelines


19-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Declare explicit return types for the exported hooks.

  • src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts#L19-L44: Define and return a named form-hook result type.
  • src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts#L37-L59: Declare the build-mutation hook result type.
  • src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts#L65-L92: Declare the confirm-mutation hook result type.
  • src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts#L98-L133: Declare the clear-mutation hook result type.

As per coding guidelines: “Add explicit types for public API surfaces such as payloads, responses, hook return values, service methods, and route handlers.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts` around lines 19 -
44, Exported hook functions lack explicit return type declarations as required
by the coding guidelines. In
src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts (lines 19-44), add an
explicit return type annotation to the usePayoutPreferenceForm function that
captures the shape of the returned object (form, onSubmit, isSubmitting). In
src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts, add explicit
return type annotations to: the build-mutation hook (lines 37-59), the
confirm-mutation hook (lines 65-92), and the clear-mutation hook (lines 98-133),
each returning the appropriate mutation hook result structure. Define named
types for each hook's return value and use them as the function return type
annotation.

Source: Coding guidelines

src/features/escrows/ui/actions/PayoutPreferenceAction.tsx (1)

4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Remove the direct feature-to-feature UI dependency.

src/features/escrows imports the implementation component PayoutPreferenceDialog from src/features/cctp-bridge/ui. Move this composition to the router layer or a shared integration boundary so feature slices do not depend on each other’s UI internals.

As per coding guidelines: “Do not import implementation details from one feature slice into another feature slice; shared needs should go through shared modules or the router layer.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/escrows/ui/actions/PayoutPreferenceAction.tsx` at line 4, Remove
the direct import of PayoutPreferenceDialog from src/features/cctp-bridge/ui in
PayoutPreferenceAction.tsx to eliminate the cross-feature-slice UI dependency.
Identify where PayoutPreferenceDialog is used within the escrows feature and
move that composition to the router layer or a shared integration boundary
module instead, so the escrows slice no longer depends on cctp-bridge's UI
internals while preserving the intended functionality.

Source: Coding guidelines

src/features/cctp-bridge/hooks/useCrossChainDestination.ts (1)

10-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return types to the public hooks.

  • src/features/cctp-bridge/hooks/useCrossChainDestination.ts#L10-L40: type the query key and UseQueryResult.
  • src/features/cctp-bridge/hooks/useFeeQuote.ts#L18-L27: declare UseQueryResult<FeeQuote, Error>.
  • src/features/cctp-bridge/hooks/useCctpAttestation.ts#L13-L20: declare the attestation query result.
  • src/features/cctp-bridge/hooks/useCompleteCctpMint.ts#L16-L42: declare the mutation variables and UseMutationResult.

As per coding guidelines, public API surfaces and hook return values require explicit types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/hooks/useCrossChainDestination.ts` around lines 10 -
40, Add explicit return type annotations to the public hooks in the CCTP bridge
module. In src/features/cctp-bridge/hooks/useCrossChainDestination.ts (lines
10-40), add an explicit return type annotation to the
crossChainDestinationQueryKey function and declare UseQueryResult with explicit
type parameters on the useCrossChainDestination function. In
src/features/cctp-bridge/hooks/useFeeQuote.ts (lines 18-27), declare the hook's
return type as UseQueryResult<FeeQuote, Error>. In
src/features/cctp-bridge/hooks/useCctpAttestation.ts (lines 13-20), add an
explicit query result return type declaration to the attestation hook. In
src/features/cctp-bridge/hooks/useCompleteCctpMint.ts (lines 16-42), declare
explicit types for the mutation variables and add a UseMutationResult return
type declaration to the mutation hook.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CCTP_SESSION_HANDOFF.md`:
- Around line 151-156: Update both shell code blocks in the
CCTP_SESSION_HANDOFF.md file to include the bash language identifier in their
opening code fences. Change the opening triple backticks from plain ``` to
```bash for both the first block containing docker compose and npx prisma
commands (around lines 151-156) and the second block mentioned in the comment
(around lines 162-165). This will resolve the MD040 markdown linting warnings
for missing language specifications on code fences.
- Around line 119-126: The payout-policy example in the handoff must reflect the
full canManagePayoutPreference behavior, including rejection for released or
resolved single-release escrows and terminal milestones, not only receiver-role
checks. Update the snippet and surrounding explanation associated with
EscrowActionPolicy.canManagePayoutPreference to document these state and
milestone constraints accurately.
- Line 92: Replace the absolute checkout path in the handoff entry with a
repository-relative or generic placeholder path, while preserving the repository
URL, branch name, and sibling-directory context.

In `@src/features/cctp-bridge/lib/evm.ts`:
- Around line 112-120: Store the result returned by
publicClient.waitForTransactionReceipt as a receipt object, then validate that
receipt.status equals "success" before returning the hash. If the status is not
"success", throw an error to prevent the UI from showing mint completion for a
failed receiveMessage transaction.
- Around line 60-82: After the wallet_addEthereumChain provider.request call
succeeds in the code === 4902 error handler, add a second provider.request call
with method wallet_switchEthereumChain using the same hexChainId to activate the
chain in the wallet. This ensures the wallet switches to the newly-added chain
rather than remaining on the previous chain, which would cause the next
writeContract call to execute on the wrong chain.

In `@src/features/cctp-bridge/schemas/payout-preference.schema.ts`:
- Around line 9-29: In
src/features/cctp-bridge/schemas/payout-preference.schema.ts lines 9-29,
restrict the destinationDomain field to valid CctpDestinationDomain literals
instead of accepting any coerced integer, and add discriminated union validation
logic in the superRefine block to ensure the recipientAddress format (EVM,
Stellar, or Solana) matches the selected domain—rejecting addresses that are
valid in general but not valid for that specific domain. In
src/features/cctp-bridge/hooks/useFeeQuote.ts lines 18-25, remove the unchecked
type assertion on the destination domain and use the narrowed
CctpDestinationDomain type directly from the validated schema result.

In `@src/features/cctp-bridge/services/cctp-bridge.service.ts`:
- Around line 73-78: The CCTP attestation flow must validate external responses
and narrow complete states before use. In
src/features/cctp-bridge/services/cctp-bridge.service.ts#L73-L78, parse the HTTP
payload with a Zod discriminated union; in
src/features/cctp-bridge/types/cctp-bridge.types.ts#L56-L60, require usable
message and attestation values for the complete variant; update
useCctpAttestation in
src/features/cctp-bridge/hooks/useCctpAttestation.ts#L13-L20 to stop polling
only for that validated variant; update useCompleteCctpMint in
src/features/cctp-bridge/hooks/useCompleteCctpMint.ts#L18-L32 to remove Hex
assertions and consume narrowed values; and update CompleteCctpMintDialog in
src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx#L45-L95 to derive
readiness from the validated complete variant.

In `@src/features/cctp-bridge/types/cctp-bridge.types.ts`:
- Around line 6-10: In src/features/cctp-bridge/types/cctp-bridge.types.ts
(lines 6-10), refactor EscrowRef from a single interface with an optional
milestoneIndex into a discriminated union with two variants: one where
escrowKind is "multi-release" and milestoneIndex is required, and another where
escrowKind is not "multi-release" and milestoneIndex does not exist. Then in
src/features/cctp-bridge/services/cctp-bridge.service.ts (lines 53-60), update
the path construction logic to use the type-narrowed union variant, which will
guarantee that milestoneIndex exists when escrowKind is "multi-release" and
eliminate the possibility of undefined URL paths.

In `@src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx`:
- Line 45: Update isReady and the related action flow in CompleteCctpMintDialog
so readiness requires a complete attestation status plus both message and
attestation payloads. Base the check on the validated complete-attestation
variant, and replace the silent click-handler guard with handling that cannot
enable the action for invalid or incomplete state.

In `@src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx`:
- Around line 99-102: Wrap the await buildPreference.mutateAsync(values) call in
the onSubmit handler with a try/catch block, and similarly wrap the await
confirmPreference.mutateAsync(...) call in its corresponding submit handler. The
catch blocks should not rethrow the error, allowing the dialog to remain open
while the mutation's onError handler and toast UI supply feedback to the user.

---

Nitpick comments:
In `@src/features/cctp-bridge/hooks/useCrossChainDestination.ts`:
- Around line 10-40: Add explicit return type annotations to the public hooks in
the CCTP bridge module. In
src/features/cctp-bridge/hooks/useCrossChainDestination.ts (lines 10-40), add an
explicit return type annotation to the crossChainDestinationQueryKey function
and declare UseQueryResult with explicit type parameters on the
useCrossChainDestination function. In
src/features/cctp-bridge/hooks/useFeeQuote.ts (lines 18-27), declare the hook's
return type as UseQueryResult<FeeQuote, Error>. In
src/features/cctp-bridge/hooks/useCctpAttestation.ts (lines 13-20), add an
explicit query result return type declaration to the attestation hook. In
src/features/cctp-bridge/hooks/useCompleteCctpMint.ts (lines 16-42), declare
explicit types for the mutation variables and add a UseMutationResult return
type declaration to the mutation hook.

In `@src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts`:
- Around line 11-17: Convert UsePayoutPreferenceFormOptions from type to
interface in src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts lines
11-17 since it is an object shape, not a union or mapped type. Similarly,
convert PayoutPreferenceDialogProps from type to interface in
src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx lines 46-52 for the same
reason. Both changes align with the coding guideline to use interface for object
shapes and type for unions, intersections, mapped types, and conditional types.
- Around line 19-44: Exported hook functions lack explicit return type
declarations as required by the coding guidelines. In
src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts (lines 19-44), add an
explicit return type annotation to the usePayoutPreferenceForm function that
captures the shape of the returned object (form, onSubmit, isSubmitting). In
src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts, add explicit
return type annotations to: the build-mutation hook (lines 37-59), the
confirm-mutation hook (lines 65-92), and the clear-mutation hook (lines 98-133),
each returning the appropriate mutation hook result structure. Define named
types for each hook's return value and use them as the function return type
annotation.

In `@src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx`:
- Around line 138-142: Update the isLoading branch in PayoutPreferenceDialog to
replace the centered Loader2 spinner with co-located Skeleton components
imported from `@/components/ui/skeleton`. Mirror the loaded form layout with
skeletons for the current-preference text, chain select, address input, and
footer actions so the dialog dimensions remain stable.

In `@src/features/escrows/ui/actions/PayoutPreferenceAction.tsx`:
- Line 4: Remove the direct import of PayoutPreferenceDialog from
src/features/cctp-bridge/ui in PayoutPreferenceAction.tsx to eliminate the
cross-feature-slice UI dependency. Identify where PayoutPreferenceDialog is used
within the escrows feature and move that composition to the router layer or a
shared integration boundary module instead, so the escrows slice no longer
depends on cctp-bridge's UI internals while preserving the intended
functionality.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c8078302-78fe-43dc-9633-583cdf06b8bd

📥 Commits

Reviewing files that changed from the base of the PR and between 35c6612 and 19d8b92.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • .claude/launch.json
  • CCTP_SESSION_HANDOFF.md
  • next.config.ts
  • package.json
  • src/features/cctp-bridge/hooks/useCctpAttestation.ts
  • src/features/cctp-bridge/hooks/useCompleteCctpMint.ts
  • src/features/cctp-bridge/hooks/useCrossChainDestination.ts
  • src/features/cctp-bridge/hooks/useFeeQuote.ts
  • src/features/cctp-bridge/hooks/usePayoutPreferenceForm.ts
  • src/features/cctp-bridge/hooks/usePayoutPreferenceMutations.ts
  • src/features/cctp-bridge/lib/chains.ts
  • src/features/cctp-bridge/lib/evm.ts
  • src/features/cctp-bridge/schemas/payout-preference.schema.ts
  • src/features/cctp-bridge/services/cctp-bridge.service.ts
  • src/features/cctp-bridge/types/cctp-bridge.types.ts
  • src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx
  • src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx
  • src/features/escrows/domain/escrow-action-policy.test.ts
  • src/features/escrows/domain/escrow-action-policy.ts
  • src/features/escrows/ui/actions/PayoutPreferenceAction.tsx
  • src/features/escrows/ui/detail/EscrowGeneralActions.tsx
  • src/features/escrows/ui/detail/MilestoneActionsMenu.tsx

Comment thread CCTP_SESSION_HANDOFF.md
The frontend's own `develop` branch landed a full escrows feature that imports `@trustless-work/escrow` directly (types, REST client, hooks) — but this package **was never added to `package.json`**, and worse, **the published npm version (3.0.5) is stale** — the real v2 rewrite (matching the V2 contracts) lives unpublished on a branch.

What was done:
1. Cloned `https://github.com/Trustless-Work/trustlesswork-sdk-react` (branch `feature/version-2`) to `/Users/armandocodecr/Documents/Programacion/Proyectos/trustlesswork-sdk-react` — a sibling directory to the other repos, **not inside** `dApp-Trustless-Work`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'CCTP_SESSION_HANDOFF.md line count and context:\n'
wc -l CCTP_SESSION_HANDOFF.md
sed -n '80,100p' CCTP_SESSION_HANDOFF.md

printf '\nOther occurrences of username-like local paths or host layout markers:\n'
rg -n '(/Users/|Documents/Programacion|Proyectos|/Users/armandocodecr|dApp-Trustless-Work|trustlesswork-sdk-react)' CCTP_SESSION_HANDOFF.md

Repository: Trustless-Work/trustlesswork-backoffice

Length of output: 3963


Remove the absolute local checkout path.

Line 92 exposes a home-directory username and local filesystem layout; replace it with a repository-relative or placeholder path before publishing this handoff document.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CCTP_SESSION_HANDOFF.md` at line 92, Replace the absolute checkout path in
the handoff entry with a repository-relative or generic placeholder path, while
preserving the repository URL, branch name, and sibling-directory context.

Comment thread CCTP_SESSION_HANDOFF.md
Comment on lines +119 to +126
- **`src/features/escrows/domain/escrow-action-policy.ts`** — added a concrete (non-abstract) method on the base `EscrowActionPolicy` class:
```ts
canManagePayoutPreference(milestoneIndex?: number): boolean {
if (milestoneIndex === undefined) return this.roles.isEscrowReceiver();
return this.roles.isMilestoneReceiver(milestoneIndex);
}
```
Same logic works for both single- and multi-release because `EscrowRoleContext` already had `isEscrowReceiver()`/`isMilestoneReceiver(index)` (used internally for dispute-opening gating) — just needed a public policy method mirroring the existing `canOpenDispute(milestoneIndex?)` pattern.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the payout-policy example.

The example only checks the receiver role. The current canManagePayoutPreference implementation also rejects released or resolved single-release escrows and terminal milestones. Update the snippet and surrounding text so the handoff does not describe weaker policy behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CCTP_SESSION_HANDOFF.md` around lines 119 - 126, The payout-policy example in
the handoff must reflect the full canManagePayoutPreference behavior, including
rejection for released or resolved single-release escrows and terminal
milestones, not only receiver-role checks. Update the snippet and surrounding
explanation associated with EscrowActionPolicy.canManagePayoutPreference to
document these state and milestone constraints accurately.

Comment thread CCTP_SESSION_HANDOFF.md
Comment on lines +151 to +156
```
docker compose up -d # Postgres + RabbitMQ
npx prisma generate && npx prisma migrate deploy
# .env needs WALLET_AUTH_SIGNING_KEY, WALLET_AUTH_HOME_DOMAIN added (see §4)
npx dotenv -e <env-file> -- nest start --watch -- # or plain `npm run start:dev` if .env is already complete
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add languages to both shell code fences.

Add bash to the opening fences. Static analysis reports MD040 warnings for both blocks.

Also applies to: 162-165

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 151-151: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CCTP_SESSION_HANDOFF.md` around lines 151 - 156, Update both shell code
blocks in the CCTP_SESSION_HANDOFF.md file to include the bash language
identifier in their opening code fences. Change the opening triple backticks
from plain ``` to ```bash for both the first block containing docker compose and
npx prisma commands (around lines 151-156) and the second block mentioned in the
comment (around lines 162-165). This will resolve the MD040 markdown linting
warnings for missing language specifications on code fences.

Source: Linters/SAST tools

Comment on lines +60 to +82
} catch (err) {
// 4902 = chain not added to the wallet yet.
const code = (err as { code?: number })?.code;
if (code === 4902) {
const rpcUrl = chain.viemChain.rpcUrls.default.http[0];
await provider.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: hexChainId,
chainName: chain.viemChain.name,
nativeCurrency: chain.viemChain.nativeCurrency,
rpcUrls: [rpcUrl],
blockExplorerUrls: chain.viemChain.blockExplorers?.default?.url
? [chain.viemChain.blockExplorers.default.url]
: [],
},
],
});
} else {
throw err;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Target file:"
git ls-files | rg '(^|/)evm\.ts$' || true

if [ -f src/features/cctp-bridge/lib/evm.ts ]; then
  echo
  echo "Relevant lines:"
  nl -ba src/features/cctp-bridge/lib/evm.ts | sed -n '1,140p'
else
  echo "Target file not found"
fi

echo
echo "Search for wallet switching/add patterns:"
rg -n "wallet_(addEthereumChain|switchEthereumChain)|4902|request\(" src/features -g '*.ts' -g '*.tsx' || true

Repository: Trustless-Work/trustlesswork-backoffice

Length of output: 284


🌐 Web query:

EIP-3085 wallet_addEthereumChain returns selected_chain_id or wallet must select chain docs

💡 Result:

EIP-3085, which defines the wallet_addEthereumChain RPC method, does not require wallets to automatically select a newly added chain [1][2]. The specification explicitly states that the method makes no statement regarding whether a wallet should change the user's currently selected chain [2]. Whether a wallet switches to the new network is considered an implementation detail left to the wallet developer [2]. The specification clarifies that a chain must not be assumed to be automatically selected by the wallet, even if the request is successful [2]. Regarding the return value, the method returns null if the request to add the chain is successful, and it returns an error if the request fails (e.g., if the user rejects the request or validation fails) [1][3][2]. There is no requirement or standard mechanism for the method to return a selected_chain_id [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Target file content:"
awk '{ printf "%6d\t%s\n", NR, $0 }' src/features/cctp-bridge/lib/evm.ts | sed -n '1,130p'

echo
echo "Wallet switch/add and provider request patterns:"
rg -n "wallet_(addEthereumChain|switchEthereumChain)|4902|provider\.request|request\(" src/features -g '*.ts' -g '*.tsx' || true

Repository: Trustless-Work/trustlesswork-backoffice

Length of output: 4940


🌐 Web query:

EIP-3085 wallet_addEthereumChain must switch to added chain wallet_switchEthereumChain

💡 Result:

The RPC method wallet_addEthereumChain (EIP-3085) and the method wallet_switchEthereumChain (EIP-3326) serve distinct purposes and are not required to behave as a single atomic operation [1][2]. According to their respective specifications, wallet_addEthereumChain is used to suggest that a wallet add a new chain's configuration (such as RPC URLs, native currency, and chain ID) to its list of known networks [1][3]. Conversely, wallet_switchEthereumChain is used to request that the wallet switch its active chain to one that is already known [2][4]. Key technical clarifications regarding your query: 1. No Mandatory Requirement to Switch: There is no EIP requirement stating that wallet_addEthereumChain must automatically switch the wallet to the newly added chain [1][2]. While some wallet implementations may prompt the user to switch immediately after a successful addition as a convenience feature, this is an implementation-specific choice rather than a protocol mandate [5]. 2. Error Handling Pattern: The standard way for a decentralized application (dapp) to handle network switching is a two-step pattern [6][7]: - First, call wallet_switchEthereumChain. - If the request fails with error code 4902 (Unrecognized chain ID), it indicates the wallet does not yet know about this chain [8][2]. - The dapp should then catch this error and call wallet_addEthereumChain to register the network [6][7]. 3. Separation of Concerns: The specifications were designed with a separation of concerns [2][4]. wallet_addEthereumChain handles the provisioning of metadata, whereas wallet_switchEthereumChain is strictly focused on modifying the user's active connection [2][4]. Forcing an automatic switch during the "add" process could conflict with user intent or wallet security policies [2]. In summary, a well-behaved dapp should not rely on wallet_addEthereumChain to switch the network; instead, it should implement logic that attempts a switch first and gracefully handles the addition process only when necessary [6][7].

Citations:


Switch the wallet after adding an unknown chain.

wallet_addEthereumChain only registers the chain configuration; it does not require wallets to make that chain active. This branch calls wallet_switchEthereumChain once, then adds the chain and returns. If the wallet stays on the previous chain, the next walletClient.writeContract(...) call can submit on the wrong chain or be rejected.

Call provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hexChainId }] }) again after wallet_addEthereumChain succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/lib/evm.ts` around lines 60 - 82, After the
wallet_addEthereumChain provider.request call succeeds in the code === 4902
error handler, add a second provider.request call with method
wallet_switchEthereumChain using the same hexChainId to activate the chain in
the wallet. This ensures the wallet switches to the newly-added chain rather
than remaining on the previous chain, which would cause the next writeContract
call to execute on the wrong chain.

Comment on lines +112 to +120
const hash = await walletClient.writeContract({
address: chain.messageTransmitter,
abi: RECEIVE_MESSAGE_ABI,
functionName: "receiveMessage",
args: [message, attestation],
});

await publicClient.waitForTransactionReceipt({ hash });
return hash;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | grep -F 'src/features/cctp-bridge/lib/evm.ts' || true

echo "== relevant file excerpt =="
if [ -f src/features/cctp-bridge/lib/evm.ts ]; then
  wc -l src/features/cctp-bridge/lib/evm.ts
  sed -n '1,180p' src/features/cctp-bridge/lib/evm.ts | cat -n
fi

echo "== package viem version =="
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock bun.lockb; do
  [ -f "$f" ] && { echo "--- $f"; grep -n -i "viem" "$f" | head -20 || true; }
done

echo "== search for writeWaitReceipt pattern =="
rg -n "waitForTransactionReceipt|receiveMessage|reverted|status !==|status ==" src -g '*.ts' -g '*.tsx' | head -80

Repository: Trustless-Work/trustlesswork-backoffice

Length of output: 9316


Reject a reverted transaction receipt.

waitForTransactionReceipt() only waits for the transaction to confirm; it can return a reverted receipt. Store the receipt and check receipt.status === "success" before returning the hash, or throw otherwise so the UI does not show mint completion for a failed receiveMessage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/lib/evm.ts` around lines 112 - 120, Store the result
returned by publicClient.waitForTransactionReceipt as a receipt object, then
validate that receipt.status equals "success" before returning the hash. If the
status is not "success", throw an error to prevent the UI from showing mint
completion for a failed receiveMessage transaction.

Comment on lines +9 to +29
export const payoutPreferenceSchema = z
.object({
destinationDomain: z.coerce.number().int(),
recipientAddress: z.string().trim().min(1, "Recipient address is required"),
})
.superRefine((values, ctx) => {
// Accept any destination-chain address shape we support (EVM, Stellar, or
// Solana) without branching on the selected domain — the backend is the
// authority on the domain↔address pairing. The three formats can't
// overlap, so this stays unambiguous.
const isStellarAddress = isValidWallet(values.recipientAddress);
const isEvmAddress = EVM_ADDRESS_REGEX.test(values.recipientAddress);
const isSolanaAddress = SOLANA_ADDRESS_REGEX.test(values.recipientAddress);

if (!isStellarAddress && !isEvmAddress && !isSolanaAddress) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["recipientAddress"],
message: "Enter a valid EVM (0x...), Stellar (G...) or Solana address",
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Encode the destination-domain contract in the schema. The schema accepts unsupported integers and mismatched recipient formats. The fee hook then restores the domain type with an unchecked assertion.

  • src/features/cctp-bridge/schemas/payout-preference.schema.ts#L9-L29: restrict the domain literals and validate the matching address format.
  • src/features/cctp-bridge/hooks/useFeeQuote.ts#L18-L25: consume the narrowed CctpDestinationDomain without an assertion.
📍 Affects 2 files
  • src/features/cctp-bridge/schemas/payout-preference.schema.ts#L9-L29 (this comment)
  • src/features/cctp-bridge/hooks/useFeeQuote.ts#L18-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/schemas/payout-preference.schema.ts` around lines 9
- 29, In src/features/cctp-bridge/schemas/payout-preference.schema.ts lines
9-29, restrict the destinationDomain field to valid CctpDestinationDomain
literals instead of accepting any coerced integer, and add discriminated union
validation logic in the superRefine block to ensure the recipientAddress format
(EVM, Stellar, or Solana) matches the selected domain—rejecting addresses that
are valid in general but not valid for that specific domain. In
src/features/cctp-bridge/hooks/useFeeQuote.ts lines 18-25, remove the unchecked
type assertion on the destination domain and use the narrowed
CctpDestinationDomain type directly from the validated schema result.

Source: Coding guidelines

Comment on lines +73 to +78
/** Fetches Circle's CCTP attestation for a Stellar burn (release) tx hash. */
async getAttestation(burnTxHash: string): Promise<AttestationResponse> {
const { data } = await http.get<AttestationResponse>(
`/core/cctp/attestation/${burnTxHash}`,
);
return data;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Validate the attestation response at the HTTP boundary. The current type permits a complete response without usable hex payloads. This state stops polling, enables the button, and then causes a silent no-op or an invalid wallet request.

  • src/features/cctp-bridge/services/cctp-bridge.service.ts#L73-L78: parse the external JSON with a Zod discriminated union.
  • src/features/cctp-bridge/types/cctp-bridge.types.ts#L56-L60: require message and attestation for the complete variant.
  • src/features/cctp-bridge/hooks/useCctpAttestation.ts#L13-L20: stop polling only after the validated complete variant is returned.
  • src/features/cctp-bridge/hooks/useCompleteCctpMint.ts#L18-L32: remove the Hex assertions and accept narrowed values.
  • src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx#L45-L95: derive readiness from the validated complete variant.

As per coding guidelines, parse and narrow unknown external JSON at the boundary and model variant states with discriminated unions.

📍 Affects 5 files
  • src/features/cctp-bridge/services/cctp-bridge.service.ts#L73-L78 (this comment)
  • src/features/cctp-bridge/types/cctp-bridge.types.ts#L56-L60
  • src/features/cctp-bridge/hooks/useCctpAttestation.ts#L13-L20
  • src/features/cctp-bridge/hooks/useCompleteCctpMint.ts#L18-L32
  • src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx#L45-L95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/services/cctp-bridge.service.ts` around lines 73 -
78, The CCTP attestation flow must validate external responses and narrow
complete states before use. In
src/features/cctp-bridge/services/cctp-bridge.service.ts#L73-L78, parse the HTTP
payload with a Zod discriminated union; in
src/features/cctp-bridge/types/cctp-bridge.types.ts#L56-L60, require usable
message and attestation values for the complete variant; update
useCctpAttestation in
src/features/cctp-bridge/hooks/useCctpAttestation.ts#L13-L20 to stop polling
only for that validated variant; update useCompleteCctpMint in
src/features/cctp-bridge/hooks/useCompleteCctpMint.ts#L18-L32 to remove Hex
assertions and consume narrowed values; and update CompleteCctpMintDialog in
src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx#L45-L95 to derive
readiness from the validated complete variant.

Source: Coding guidelines

Comment on lines +6 to +10
interface EscrowRef {
escrowKind: EscrowKind;
contractId: string;
/** Required for multi-release escrows — each milestone has its own receiver. */
milestoneIndex?: number;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Model the milestone identity as a discriminated union. The optional milestoneIndex permits invalid multi-release requests and produces an /undefined URL.

  • src/features/cctp-bridge/types/cctp-bridge.types.ts#L6-L10: require milestoneIndex when escrowKind is "multi-release".
  • src/features/cctp-bridge/services/cctp-bridge.service.ts#L53-L60: construct the path from the narrowed union variant.
📍 Affects 2 files
  • src/features/cctp-bridge/types/cctp-bridge.types.ts#L6-L10 (this comment)
  • src/features/cctp-bridge/services/cctp-bridge.service.ts#L53-L60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/types/cctp-bridge.types.ts` around lines 6 - 10, In
src/features/cctp-bridge/types/cctp-bridge.types.ts (lines 6-10), refactor
EscrowRef from a single interface with an optional milestoneIndex into a
discriminated union with two variants: one where escrowKind is "multi-release"
and milestoneIndex is required, and another where escrowKind is not
"multi-release" and milestoneIndex does not exist. Then in
src/features/cctp-bridge/services/cctp-bridge.service.ts (lines 53-60), update
the path construction logic to use the type-narrowed union variant, which will
guarantee that milestoneIndex exists when escrowKind is "multi-release" and
eliminate the possibility of undefined URL paths.

Source: Coding guidelines

);
const completeMint = useCompleteCctpMint();

const isReady = attestation?.status === "complete";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not enable the action without both attestation payloads.

isReady can be true while message or attestation is absent. The button is then enabled, but its click handler silently returns.

Derive readiness from a validated complete-attestation variant. Do not use a silent guard for this invalid state.

Also applies to: 87-95

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/ui/CompleteCctpMintDialog.tsx` at line 45, Update
isReady and the related action flow in CompleteCctpMintDialog so readiness
requires a complete attestation status plus both message and attestation
payloads. Base the check on the validated complete-attestation variant, and
replace the silent click-handler guard with handling that cannot enable the
action for invalid or incomplete state.

Source: Coding guidelines

Comment on lines +99 to +102
onSubmit: async (values) => {
const built = await buildPreference.mutateAsync(values);
setPendingTx(built);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For @tanstack/react-query v5 and react-hook-form v7, does mutateAsync reject after onError runs, and does handleSubmit propagate an error thrown by its async callback?

💡 Result:

For @tanstack/react-query v5 and react-hook-form v7, the behaviors are as follows: TanStack Query v5: Yes, mutateAsync rejects after onError runs. The mutateAsync function is designed to return a promise that throws if the mutation fails [1][2]. Internally, when an error occurs, TanStack Query awaits your onError callback (if provided) before rejecting the returned promise [3]. If an error is thrown within the onError callback itself, that error will propagate and become the reason for the rejection, as the original error is caught and handled within the mutation lifecycle [4][3]. React Hook Form v7: Yes, handleSubmit propagates an error thrown by its async callback. Starting in v7.84.0 and earlier versions (following behavior changes around v7.42.0), handleSubmit no longer swallows errors thrown inside the async onSubmit callback [5][6][7]. If your async callback throws, the promise returned by handleSubmit will reject [5]. Consequently, it is recommended to wrap your async logic within a try/catch block inside the callback to manage error states (e.g., using setError) or to ensure proper cleanup if you need to handle side effects gracefully [5][8][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'PayoutPreferenceDialog\.tsx$' . || true

echo "== outline =="
file="$(fd 'PayoutPreferenceDialog\.tsx$' . | head -n 1)"
if [ -n "${file:-}" ]; then
  ast-grep outline "$file" --view compact || true
  echo "== relevant lines =="
  sed -n '1,150p' "$file" | cat -n
fi

echo "== package versions =="
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock bun.lockb; do
  [ -f "$f" ] && echo "-- $f --" && (rg -n '`@tanstack/react-query`|react-hook-form' "$f" \| head -30 || true)
done

Repository: Trustless-Work/trustlesswork-backoffice

Length of output: 6502


Handle rejected mutation promises at the dialog event handlers.

mutateAsync can reject even when onError runs, and React Hook Form propagates errors thrown from async submit handlers. Wrap both await buildPreference.mutateAsync(...) and await confirmPreference.mutateAsync(...) in try/catch so rejections are caught, the dialog stays open, and the mutation toast supplies UI feedback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/cctp-bridge/ui/PayoutPreferenceDialog.tsx` around lines 99 -
102, Wrap the await buildPreference.mutateAsync(values) call in the onSubmit
handler with a try/catch block, and similarly wrap the await
confirmPreference.mutateAsync(...) call in its corresponding submit handler. The
catch blocks should not rethrow the error, allowing the dialog to remain open
while the mutation's onError handler and toast UI supply feedback to the user.

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.

1 participant