Skip to content

Feat/gasless transactions - #89

Merged
rongquan1 merged 13 commits into
developfrom
feat/gasless-transactions
Jul 29, 2026
Merged

Feat/gasless transactions#89
rongquan1 merged 13 commits into
developfrom
feat/gasless-transactions

Conversation

@RishabhS7

@RishabhS7 RishabhS7 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Gasless Transactions — Added a full gasless module (src/gasless/) that lets EIP-7702 delegated accounts submit token-registry transactions fee-free via a platform paymaster. Covers all 10 transaction types: Transfer Holder/Beneficiary/Owners, Reject Transfer Holder/Beneficiary/Owners, Return to Issuer, Accept Return to Issuer, Reject Return to Issuer, and Nominate Beneficiary.
  • Paymaster Input UI — Users can paste a paymaster address in the Verify Result panel; the address is validated on-chain (must be a real paymaster, user must be whitelisted). Graceful fallback to paid transactions when gasless conditions are not met.
  • E2E Test Suite — Full Playwright + Synpress e2e coverage for all 10 gasless transaction flows, plus error-path specs for each. Merged upstream e2e infrastructure improvements: dismissMetaMaskPopups, MetaMask unlock helpers, screen recording via ffmpeg, longer CI timeouts, and additional fixture tokens for nominate/OA/W3C verification flows.
  • Unit Tests — Vitest test suites for checkEIP7702Delegation, checkPaymasterWhitelist, and all 10 gasless hooks (52 tests covering gasless path, fallback path, error handling, reset, and paymaster localStorage).

Changes

New: src/gasless/

  • checkDelegation.ts — checks eth_getCode for the 0xef0100 EIP-7702 prefix
  • checkPaymasterWhitelist.ts — reads authorizedCallers + authorizedTitleEscrows from the platform paymaster contract in parallel
  • buildSmartAccountClient.ts — builds a Pimlico/permissionless smart account client from the user's EOA
  • useGaslessTransferHolder.ts + 9 sibling hooks — each hook checks delegation → checks whitelist → takes gasless or paid fallback path
  • index.ts — re-exports all hooks
  • Unit tests for all of the above (*.test.ts, gaslessHooks.test.tsx)

Modified: src/components/home/VerifySection/VerifyResult.tsx

  • Added paymaster address input field with on-chain validation
  • Added EIP-7702 delegation status indicator
  • Added data-testid attributes required by e2e specs

E2E (e2e/)

  • setup-contracts.cjs — corrected ADDRESS_EXAMPLE_2, added nominateToken, added OA/W3C verification token mint entries
  • helpers/actions.ts — added dismissMetaMaskPopups, MetaMask unlock in connectMetaMask/switchMetaMaskAccount, hardhatRpc, hardhatRpcNode, revokeMetamaskPermissions
  • playwright.config.ts — added testIgnore for verify specs, retain-on-failure trace/video, slowMo: 800
  • .github/workflows/e2e.yml — 60-min job timeout, 180s node/server waits, ffmpeg screen recording, full dev server env vars

Test plan

  • npm test — all unit tests pass (52 gasless hook tests + delegation + whitelist tests)
  • npm run e2e — all 10 happy-path transaction specs pass locally against Hardhat
  • Gasless path: connect a delegated EOA with a valid paymaster → transactions go through without MetaMask gas prompt
  • Fallback path: no delegation or not whitelisted → standard paid MetaMask transaction
  • Paymaster input: invalid address → "Invalid Paymaster Address"; valid paymaster but not whitelisted → "This Address is not applicable to you"
  • Error path: MetaMask rejection → error state shown, reset() clears state correctly

Summary by CodeRabbit

  • New Features

    • Added pay-on-behalf (gasless) transaction support for transfer, nomination, return, and rejection flows, gated by EIP-7702 delegation and paymaster whitelist checks.
    • Enhanced verification UI to detect delegation and validate paymaster eligibility with clear success/error states.
    • Added an informational icon and a contextual “pay-on-behalf is enabled” notice.
  • Bug Fixes

    • Updated end-to-end checks and flows for beneficiary/owner updates and success messaging.
  • Tests

    • Added gasless unit tests and expanded end-to-end fixtures/scenarios for accept/reject and surrender paths.
  • Chores

    • Updated dependencies to support gasless functionality.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4deb392b-1aba-4cfa-bb54-40bc394fa345

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds optional EIP-7702/paymaster execution for title-escrow actions, integrates gasless verification and pay-on-behalf UI, adds supporting dependencies and tests, and updates E2E fixtures, filtering, setup, and assertions.

Changes

Gasless transaction and application UI changes

Layer / File(s) Summary
Gasless foundation and transaction hooks
src/gasless/*, src/hooks/useContractFunctionHook.tsx, package.json, .npmrc
Adds delegation and paymaster checks, Pimlico smart-account construction, gasless action hooks, exports, dependencies, and shared error handling.
Gasless action integration and verification UI
src/components/common/contexts/..., src/components/home/VerifySection/VerifyResult.tsx, src/components/AssetManagementPanel/..., src/components/common/Icons/*, src/index.css
Wires gasless hooks into token actions and adds delegation, paymaster, pay-on-behalf, icon, and verification styles.
Gasless validation
src/gasless/*.test.*
Tests delegation and whitelist helpers plus fallback routing, gasless routing, state transitions, errors, reset behavior, and paymaster resolution.

E2E workflow and fixture updates

Layer / File(s) Summary
E2E workflow, fixtures, and assertions
e2e/fixtures/local/w3c/*, e2e/tests/*, e2e/helpers/actions.ts, e2e/setup-contracts.cjs, e2e/playwright.config.ts
Adds transferable-record credential fixtures, broadens verification test filtering, updates setup formatting and helper naming, and refines transaction assertions.

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

Possibly related PRs

Suggested reviewers: rongquan1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding gasless transaction support and related flows.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gasless-transactions

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.

@RishabhS7
RishabhS7 changed the base branch from main to develop July 23, 2026 09:47
permissionless@0.3.7 declares peerOptional ox@^0.11.3 but viem@2.55.1
ships ox@0.14.30, which is out of range. trustvc@2.15.0-beta.2 requires
both viem@^2.53.1 and permissionless@^0.3.6, so neither can be downgraded.
legacy-peer-deps makes npm skip the conflicting optional peer check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index.css (1)

3873-3886: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

gap: 10px silently overrides the existing row-gap: 0px.

Adding gap: 10px after row-gap: 0px in .dropdown-btn-frame changes the effective row-gap from 0px to 10px (the shorthand gap overwrites the longhand set immediately above it), flagged by stylelint's declaration-block-no-shorthand-property-overrides. If the intent was to add column spacing only, use column-gap: 10px instead of gap.

🎯 Proposed fix
   padding: 0px;
-  row-gap: 0px;
-  gap: 10px;
+  column-gap: 10px;
+  row-gap: 0px;
🤖 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/index.css` around lines 3873 - 3886, Update the .dropdown-btn-frame
spacing declarations to use column-gap: 10px instead of the gap shorthand,
preserving the existing row-gap: 0px behavior.

Source: Linters/SAST tools

🧹 Nitpick comments (4)
src/index.css (1)

2226-2373: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fixed pixel widths in the new gasless error/success blocks have no responsive fallback.

.gasless-error-text (width: 355px), .pay-on-behalf-note (width: 546px), .pay-on-behalf-note-frame (width: 315px), and .pay-on-behalf-note-text (width: 278px) use hard-coded widths with no @media adjustment, unlike sibling blocks in this same section (.vr-card-header, .vr-upload-btn) which already handle the max-width: 712px breakpoint. On narrow viewports this text/frame combination is likely to overflow or get clipped.

🤖 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/index.css` around lines 2226 - 2373, Replace the fixed widths in
.gasless-error-text, .pay-on-behalf-note, .pay-on-behalf-note-frame, and
.pay-on-behalf-note-text with responsive sizing, adding the established
max-width: 712px breakpoint behavior used by nearby .vr-card-header and
.vr-upload-btn styles. Ensure the gasless error and pay-on-behalf content shrink
within narrow containers without overflow or clipping.
package.json (1)

36-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move @testing-library/dom into devDependencies.

@testing-library/dom is only pulled into production installs through dependencies; devDependencies already pins the test framework packages, including @testing-library/react, and there are no non-test imports under dependencies.

🤖 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 `@package.json` around lines 36 - 38, Move `@testing-library/dom` from
dependencies to devDependencies in package.json, keeping its existing version
unchanged and grouped with the other testing packages.
src/gasless/useGaslessNominate.ts (1)

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

Extract the shared gasless-eligibility + client-build logic.

This send body (paymaster resolution, hasGaslessConfig gate, delegation + whitelist checks, buildSmartAccountClient call, error mapping, state transitions) is duplicated almost verbatim across all useGasless* hooks (useGaslessTransferBeneficiary, useGaslessRejectTransferHolder, useGaslessRejectTransferOwners, useGaslessReturnToIssuer, useGaslessRejectReturned, etc.). Consider a shared helper/factory that takes the gasless/non-gasless callables so each hook only supplies its two trustvc functions. This removes ~9 copies and keeps the eligibility contract in one place.

getRpcUrl(chainId!) is also called twice per send here — reuse the earlier rpcUrl.

🤖 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/gasless/useGaslessNominate.ts` around lines 48 - 140, Extract the
duplicated gasless eligibility, paymaster resolution, smart-account client
construction, error mapping, and state-transition flow from send into a shared
helper or factory used by the useGasless* hooks, with each hook supplying only
its gasless and non-gasless callables. Preserve the existing eligibility
behavior and trustvc function parameters, and reuse the rpcUrl already resolved
during eligibility instead of calling getRpcUrl(chainId!) again when building
the client.
src/gasless/useGaslessTransferHolder.ts (1)

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

Consider extracting the shared gasless scaffolding. This eligibility resolution + delegation/whitelist check + gasless-vs-fallback dispatch + state machine is duplicated near-verbatim across all 10 useGasless* hooks. A single parameterized helper (taking the gasless/regular callables and param shape) would remove ~120 duplicated lines per hook and keep the routing logic in one place.

🤖 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/gasless/useGaslessTransferHolder.ts` around lines 40 - 158, Extract the
shared gasless flow currently implemented in
useGaslessTransferHolder.send—including eligibility resolution, EIP-7702
delegation and whitelist checks, gasless-versus-regular dispatch, and
state/error transitions—into one parameterized reusable helper. Update
useGaslessTransferHolder and the other useGasless* hooks to provide their
operation-specific callables and parameters while preserving existing behavior
and hook state APIs.
🤖 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 `@e2e/playwright.config.ts`:
- Line 11: Update the Playwright configuration around retries and the
transaction specs so failed CI retries do not reuse mutated Hardhat blockchain
state: either reset/redeploy the chain and wallet before each retry, or disable
retries for the stateful nomination, transfer, reject, and return tests while
preserving retries for unrelated specs.

In `@src/components/common/Tag/Tag.tsx`:
- Line 54: Update the className in the Tag component to replace the undefined
font-urbanist-bold utility with the supported font-urbanist and font-bold
utilities, preserving the other styling classes.

In `@src/components/home/VerifySection/VerifyResult.tsx`:
- Around line 159-161: The successful checkGasless branch in VerifyResult.tsx
must persist the trimmed paymaster value using the trustvc_paymaster_${account}
localStorage key; restore this write before setting gaslessStatus to success. In
ActionSelectionForm.tsx lines 193-203, verify the existing pay-on-behalf note
renders once that key is written; make no direct change there unless required by
the verification.
- Around line 82-93: Update the useEffect delegation check to ignore results
from stale requests when account or chainId changes. Track whether the effect is
still active, only call setIsDelegated for the current check, and clean up that
active state in the effect’s return function; preserve the existing early resets
for missing account or RPC URL.

In `@src/gasless/buildSmartAccountClient.ts`:
- Line 34: Update the Pimlico API key configuration used by
buildSmartAccountClient and VITE_PIMLICO_API_KEY so the key is restricted to the
intended application origin/domain before embedding it in the client-side RPC
URL; preserve the existing chain-specific Pimlico endpoint construction while
ensuring leaked keys cannot be reused from other domains.

In `@src/gasless/useGaslessRejectReturned.ts`:
- Around line 66-121: Update hasGaslessConfig in the gasless eligibility flow to
also require tokenRegistryAddress and tokenId before setting useGasless or
entering rejectReturnedGasless. Preserve the existing whitelist checks and
ensure the gasless branch cannot reach non-null assertions when either consumed
parameter is missing.

In `@src/gasless/useGaslessTransferHolder.ts`:
- Around line 70-74: Restore persistence for validated user-entered paymaster
addresses by re-enabling the localStorage.setItem call in VerifyResult.tsx after
trimming and validating the address, using the trustvc_paymaster_${account} key
consumed by the gasless hooks. The affected read sites require no direct
changes: src/gasless/useGaslessTransferHolder.ts lines 70-74,
src/gasless/useGaslessTransferOwners.ts lines 63-67, and
src/gasless/useGaslessRejectTransferBeneficiary.ts lines 64-67 already read the
stored value correctly.

In `@src/index.css`:
- Around line 15-29: Update the affected `@font-face` rules and selectors in
src/index.css to use unquoted font-family names, including Urbanist and Avenir,
consistent with the rest of the file. In .gasless-address-input-field, remove
the duplicate border-radius declaration. Remove extra blank lines before
declarations in the new check-gasless and pay-on-behalf rules so they satisfy
declaration-empty-line-before.

---

Outside diff comments:
In `@src/index.css`:
- Around line 3873-3886: Update the .dropdown-btn-frame spacing declarations to
use column-gap: 10px instead of the gap shorthand, preserving the existing
row-gap: 0px behavior.

---

Nitpick comments:
In `@package.json`:
- Around line 36-38: Move `@testing-library/dom` from dependencies to
devDependencies in package.json, keeping its existing version unchanged and
grouped with the other testing packages.

In `@src/gasless/useGaslessNominate.ts`:
- Around line 48-140: Extract the duplicated gasless eligibility, paymaster
resolution, smart-account client construction, error mapping, and
state-transition flow from send into a shared helper or factory used by the
useGasless* hooks, with each hook supplying only its gasless and non-gasless
callables. Preserve the existing eligibility behavior and trustvc function
parameters, and reuse the rpcUrl already resolved during eligibility instead of
calling getRpcUrl(chainId!) again when building the client.

In `@src/gasless/useGaslessTransferHolder.ts`:
- Around line 40-158: Extract the shared gasless flow currently implemented in
useGaslessTransferHolder.send—including eligibility resolution, EIP-7702
delegation and whitelist checks, gasless-versus-regular dispatch, and
state/error transitions—into one parameterized reusable helper. Update
useGaslessTransferHolder and the other useGasless* hooks to provide their
operation-specific callables and parameters while preserving existing behavior
and hook state APIs.

In `@src/index.css`:
- Around line 2226-2373: Replace the fixed widths in .gasless-error-text,
.pay-on-behalf-note, .pay-on-behalf-note-frame, and .pay-on-behalf-note-text
with responsive sizing, adding the established max-width: 712px breakpoint
behavior used by nearby .vr-card-header and .vr-upload-btn styles. Ensure the
gasless error and pay-on-behalf content shrink within narrow containers without
overflow or clipping.
🪄 Autofix (Beta)

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: cbfc881d-8cfc-4af2-b925-4f667db255be

📥 Commits

Reviewing files that changed from the base of the PR and between c95dcfe and 7b9fd70.

⛔ Files ignored due to path filters (11)
  • package-lock.json is excluded by !**/package-lock.json
  • public/fonts/GilroyBold/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyBold/font.woff2 is excluded by !**/*.woff2
  • public/fonts/GilroyExtraBold/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyExtraBold/font.woff2 is excluded by !**/*.woff2
  • public/fonts/GilroyLight/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyLight/font.woff2 is excluded by !**/*.woff2
  • public/fonts/GilroyMedium/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyMedium/font.woff2 is excluded by !**/*.woff2
  • public/fonts/Urbanist/Urbanist-Italic-Variable.woff2 is excluded by !**/*.woff2
  • public/fonts/Urbanist/Urbanist-Variable.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (56)
  • .github/workflows/e2e.yml
  • e2e/fixtures/local/w3c/tr_accept_surrender.json
  • e2e/fixtures/local/w3c/tr_reject_surrender.json
  • e2e/helpers/actions.ts
  • e2e/playwright.config.ts
  • e2e/setup-contracts.cjs
  • e2e/tests/nominate.spec.ts
  • e2e/tests/reject-return-to-issuer.spec.ts
  • e2e/tests/transfer-and-reject-beneficiary.spec.ts
  • e2e/tests/transfer-and-reject-owners.spec.ts
  • package.json
  • public/fonts/Urbanist/OFL.txt
  • src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx
  • src/components/about/CapabilityCard/CapabilityCard.tsx
  • src/components/about/EcosystemCard/EcosystemCard.tsx
  • src/components/common/AddressBookOverlay/AddressBookOverlay.tsx
  • src/components/common/Button/Button.tsx
  • src/components/common/FormAlert/FormAlert.tsx
  • src/components/common/Icons/Icons.tsx
  • src/components/common/Icons/index.ts
  • src/components/common/Navbar/Navbar.tsx
  • src/components/common/PartnerCard/index.tsx
  • src/components/common/Recaptcha/Recaptcha.tsx
  • src/components/common/Tag/Tag.tsx
  • src/components/common/TextAreaField/TextAreaField.tsx
  • src/components/common/TextField/TextField.tsx
  • src/components/common/contexts/TokenInformationContext/TokenInformationContext.tsx
  • src/components/home/PartnersSection/index.tsx
  • src/components/home/VerifySection/VerifyError.tsx
  • src/components/home/VerifySection/VerifyResult.tsx
  • src/gasless/buildSmartAccountClient.ts
  • src/gasless/checkDelegation.test.ts
  • src/gasless/checkDelegation.ts
  • src/gasless/checkPaymasterWhitelist.test.ts
  • src/gasless/checkPaymasterWhitelist.ts
  • src/gasless/gaslessHooks.test.tsx
  • src/gasless/index.ts
  • src/gasless/useGaslessAcceptReturned.ts
  • src/gasless/useGaslessNominate.ts
  • src/gasless/useGaslessRejectReturned.ts
  • src/gasless/useGaslessRejectTransferBeneficiary.ts
  • src/gasless/useGaslessRejectTransferHolder.ts
  • src/gasless/useGaslessRejectTransferOwners.ts
  • src/gasless/useGaslessReturnToIssuer.ts
  • src/gasless/useGaslessTransferBeneficiary.ts
  • src/gasless/useGaslessTransferHolder.ts
  • src/gasless/useGaslessTransferOwners.ts
  • src/hooks/useContactForm.ts
  • src/hooks/useContractFunctionHook.tsx
  • src/hooks/useRecaptcha.ts
  • src/index.css
  • src/pages/About/index.tsx
  • src/pages/Contact/index.tsx
  • src/pages/Partners/index.tsx
  • src/pages/Settings/index.tsx
  • tailwind.config.js
💤 Files with no reviewable changes (5)
  • e2e/tests/nominate.spec.ts
  • e2e/tests/transfer-and-reject-beneficiary.spec.ts
  • e2e/setup-contracts.cjs
  • e2e/tests/transfer-and-reject-owners.spec.ts
  • e2e/tests/reject-return-to-issuer.spec.ts

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index.css (1)

3873-3886: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

gap: 10px silently overrides the existing row-gap: 0px.

Adding gap: 10px after row-gap: 0px in .dropdown-btn-frame changes the effective row-gap from 0px to 10px (the shorthand gap overwrites the longhand set immediately above it), flagged by stylelint's declaration-block-no-shorthand-property-overrides. If the intent was to add column spacing only, use column-gap: 10px instead of gap.

🎯 Proposed fix
   padding: 0px;
-  row-gap: 0px;
-  gap: 10px;
+  column-gap: 10px;
+  row-gap: 0px;
🤖 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/index.css` around lines 3873 - 3886, Update the .dropdown-btn-frame
spacing declarations to use column-gap: 10px instead of the gap shorthand,
preserving the existing row-gap: 0px behavior.

Source: Linters/SAST tools

🧹 Nitpick comments (4)
src/index.css (1)

2226-2373: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fixed pixel widths in the new gasless error/success blocks have no responsive fallback.

.gasless-error-text (width: 355px), .pay-on-behalf-note (width: 546px), .pay-on-behalf-note-frame (width: 315px), and .pay-on-behalf-note-text (width: 278px) use hard-coded widths with no @media adjustment, unlike sibling blocks in this same section (.vr-card-header, .vr-upload-btn) which already handle the max-width: 712px breakpoint. On narrow viewports this text/frame combination is likely to overflow or get clipped.

🤖 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/index.css` around lines 2226 - 2373, Replace the fixed widths in
.gasless-error-text, .pay-on-behalf-note, .pay-on-behalf-note-frame, and
.pay-on-behalf-note-text with responsive sizing, adding the established
max-width: 712px breakpoint behavior used by nearby .vr-card-header and
.vr-upload-btn styles. Ensure the gasless error and pay-on-behalf content shrink
within narrow containers without overflow or clipping.
package.json (1)

36-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move @testing-library/dom into devDependencies.

@testing-library/dom is only pulled into production installs through dependencies; devDependencies already pins the test framework packages, including @testing-library/react, and there are no non-test imports under dependencies.

🤖 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 `@package.json` around lines 36 - 38, Move `@testing-library/dom` from
dependencies to devDependencies in package.json, keeping its existing version
unchanged and grouped with the other testing packages.
src/gasless/useGaslessNominate.ts (1)

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

Extract the shared gasless-eligibility + client-build logic.

This send body (paymaster resolution, hasGaslessConfig gate, delegation + whitelist checks, buildSmartAccountClient call, error mapping, state transitions) is duplicated almost verbatim across all useGasless* hooks (useGaslessTransferBeneficiary, useGaslessRejectTransferHolder, useGaslessRejectTransferOwners, useGaslessReturnToIssuer, useGaslessRejectReturned, etc.). Consider a shared helper/factory that takes the gasless/non-gasless callables so each hook only supplies its two trustvc functions. This removes ~9 copies and keeps the eligibility contract in one place.

getRpcUrl(chainId!) is also called twice per send here — reuse the earlier rpcUrl.

🤖 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/gasless/useGaslessNominate.ts` around lines 48 - 140, Extract the
duplicated gasless eligibility, paymaster resolution, smart-account client
construction, error mapping, and state-transition flow from send into a shared
helper or factory used by the useGasless* hooks, with each hook supplying only
its gasless and non-gasless callables. Preserve the existing eligibility
behavior and trustvc function parameters, and reuse the rpcUrl already resolved
during eligibility instead of calling getRpcUrl(chainId!) again when building
the client.
src/gasless/useGaslessTransferHolder.ts (1)

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

Consider extracting the shared gasless scaffolding. This eligibility resolution + delegation/whitelist check + gasless-vs-fallback dispatch + state machine is duplicated near-verbatim across all 10 useGasless* hooks. A single parameterized helper (taking the gasless/regular callables and param shape) would remove ~120 duplicated lines per hook and keep the routing logic in one place.

🤖 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/gasless/useGaslessTransferHolder.ts` around lines 40 - 158, Extract the
shared gasless flow currently implemented in
useGaslessTransferHolder.send—including eligibility resolution, EIP-7702
delegation and whitelist checks, gasless-versus-regular dispatch, and
state/error transitions—into one parameterized reusable helper. Update
useGaslessTransferHolder and the other useGasless* hooks to provide their
operation-specific callables and parameters while preserving existing behavior
and hook state APIs.
🤖 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 `@e2e/playwright.config.ts`:
- Line 11: Update the Playwright configuration around retries and the
transaction specs so failed CI retries do not reuse mutated Hardhat blockchain
state: either reset/redeploy the chain and wallet before each retry, or disable
retries for the stateful nomination, transfer, reject, and return tests while
preserving retries for unrelated specs.

In `@src/components/common/Tag/Tag.tsx`:
- Line 54: Update the className in the Tag component to replace the undefined
font-urbanist-bold utility with the supported font-urbanist and font-bold
utilities, preserving the other styling classes.

In `@src/components/home/VerifySection/VerifyResult.tsx`:
- Around line 159-161: The successful checkGasless branch in VerifyResult.tsx
must persist the trimmed paymaster value using the trustvc_paymaster_${account}
localStorage key; restore this write before setting gaslessStatus to success. In
ActionSelectionForm.tsx lines 193-203, verify the existing pay-on-behalf note
renders once that key is written; make no direct change there unless required by
the verification.
- Around line 82-93: Update the useEffect delegation check to ignore results
from stale requests when account or chainId changes. Track whether the effect is
still active, only call setIsDelegated for the current check, and clean up that
active state in the effect’s return function; preserve the existing early resets
for missing account or RPC URL.

In `@src/gasless/buildSmartAccountClient.ts`:
- Line 34: Update the Pimlico API key configuration used by
buildSmartAccountClient and VITE_PIMLICO_API_KEY so the key is restricted to the
intended application origin/domain before embedding it in the client-side RPC
URL; preserve the existing chain-specific Pimlico endpoint construction while
ensuring leaked keys cannot be reused from other domains.

In `@src/gasless/useGaslessRejectReturned.ts`:
- Around line 66-121: Update hasGaslessConfig in the gasless eligibility flow to
also require tokenRegistryAddress and tokenId before setting useGasless or
entering rejectReturnedGasless. Preserve the existing whitelist checks and
ensure the gasless branch cannot reach non-null assertions when either consumed
parameter is missing.

In `@src/gasless/useGaslessTransferHolder.ts`:
- Around line 70-74: Restore persistence for validated user-entered paymaster
addresses by re-enabling the localStorage.setItem call in VerifyResult.tsx after
trimming and validating the address, using the trustvc_paymaster_${account} key
consumed by the gasless hooks. The affected read sites require no direct
changes: src/gasless/useGaslessTransferHolder.ts lines 70-74,
src/gasless/useGaslessTransferOwners.ts lines 63-67, and
src/gasless/useGaslessRejectTransferBeneficiary.ts lines 64-67 already read the
stored value correctly.

In `@src/index.css`:
- Around line 15-29: Update the affected `@font-face` rules and selectors in
src/index.css to use unquoted font-family names, including Urbanist and Avenir,
consistent with the rest of the file. In .gasless-address-input-field, remove
the duplicate border-radius declaration. Remove extra blank lines before
declarations in the new check-gasless and pay-on-behalf rules so they satisfy
declaration-empty-line-before.

---

Outside diff comments:
In `@src/index.css`:
- Around line 3873-3886: Update the .dropdown-btn-frame spacing declarations to
use column-gap: 10px instead of the gap shorthand, preserving the existing
row-gap: 0px behavior.

---

Nitpick comments:
In `@package.json`:
- Around line 36-38: Move `@testing-library/dom` from dependencies to
devDependencies in package.json, keeping its existing version unchanged and
grouped with the other testing packages.

In `@src/gasless/useGaslessNominate.ts`:
- Around line 48-140: Extract the duplicated gasless eligibility, paymaster
resolution, smart-account client construction, error mapping, and
state-transition flow from send into a shared helper or factory used by the
useGasless* hooks, with each hook supplying only its gasless and non-gasless
callables. Preserve the existing eligibility behavior and trustvc function
parameters, and reuse the rpcUrl already resolved during eligibility instead of
calling getRpcUrl(chainId!) again when building the client.

In `@src/gasless/useGaslessTransferHolder.ts`:
- Around line 40-158: Extract the shared gasless flow currently implemented in
useGaslessTransferHolder.send—including eligibility resolution, EIP-7702
delegation and whitelist checks, gasless-versus-regular dispatch, and
state/error transitions—into one parameterized reusable helper. Update
useGaslessTransferHolder and the other useGasless* hooks to provide their
operation-specific callables and parameters while preserving existing behavior
and hook state APIs.

In `@src/index.css`:
- Around line 2226-2373: Replace the fixed widths in .gasless-error-text,
.pay-on-behalf-note, .pay-on-behalf-note-frame, and .pay-on-behalf-note-text
with responsive sizing, adding the established max-width: 712px breakpoint
behavior used by nearby .vr-card-header and .vr-upload-btn styles. Ensure the
gasless error and pay-on-behalf content shrink within narrow containers without
overflow or clipping.
🪄 Autofix (Beta)

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: cbfc881d-8cfc-4af2-b925-4f667db255be

📥 Commits

Reviewing files that changed from the base of the PR and between c95dcfe and 7b9fd70.

⛔ Files ignored due to path filters (11)
  • package-lock.json is excluded by !**/package-lock.json
  • public/fonts/GilroyBold/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyBold/font.woff2 is excluded by !**/*.woff2
  • public/fonts/GilroyExtraBold/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyExtraBold/font.woff2 is excluded by !**/*.woff2
  • public/fonts/GilroyLight/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyLight/font.woff2 is excluded by !**/*.woff2
  • public/fonts/GilroyMedium/font.woff is excluded by !**/*.woff
  • public/fonts/GilroyMedium/font.woff2 is excluded by !**/*.woff2
  • public/fonts/Urbanist/Urbanist-Italic-Variable.woff2 is excluded by !**/*.woff2
  • public/fonts/Urbanist/Urbanist-Variable.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (56)
  • .github/workflows/e2e.yml
  • e2e/fixtures/local/w3c/tr_accept_surrender.json
  • e2e/fixtures/local/w3c/tr_reject_surrender.json
  • e2e/helpers/actions.ts
  • e2e/playwright.config.ts
  • e2e/setup-contracts.cjs
  • e2e/tests/nominate.spec.ts
  • e2e/tests/reject-return-to-issuer.spec.ts
  • e2e/tests/transfer-and-reject-beneficiary.spec.ts
  • e2e/tests/transfer-and-reject-owners.spec.ts
  • package.json
  • public/fonts/Urbanist/OFL.txt
  • src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx
  • src/components/about/CapabilityCard/CapabilityCard.tsx
  • src/components/about/EcosystemCard/EcosystemCard.tsx
  • src/components/common/AddressBookOverlay/AddressBookOverlay.tsx
  • src/components/common/Button/Button.tsx
  • src/components/common/FormAlert/FormAlert.tsx
  • src/components/common/Icons/Icons.tsx
  • src/components/common/Icons/index.ts
  • src/components/common/Navbar/Navbar.tsx
  • src/components/common/PartnerCard/index.tsx
  • src/components/common/Recaptcha/Recaptcha.tsx
  • src/components/common/Tag/Tag.tsx
  • src/components/common/TextAreaField/TextAreaField.tsx
  • src/components/common/TextField/TextField.tsx
  • src/components/common/contexts/TokenInformationContext/TokenInformationContext.tsx
  • src/components/home/PartnersSection/index.tsx
  • src/components/home/VerifySection/VerifyError.tsx
  • src/components/home/VerifySection/VerifyResult.tsx
  • src/gasless/buildSmartAccountClient.ts
  • src/gasless/checkDelegation.test.ts
  • src/gasless/checkDelegation.ts
  • src/gasless/checkPaymasterWhitelist.test.ts
  • src/gasless/checkPaymasterWhitelist.ts
  • src/gasless/gaslessHooks.test.tsx
  • src/gasless/index.ts
  • src/gasless/useGaslessAcceptReturned.ts
  • src/gasless/useGaslessNominate.ts
  • src/gasless/useGaslessRejectReturned.ts
  • src/gasless/useGaslessRejectTransferBeneficiary.ts
  • src/gasless/useGaslessRejectTransferHolder.ts
  • src/gasless/useGaslessRejectTransferOwners.ts
  • src/gasless/useGaslessReturnToIssuer.ts
  • src/gasless/useGaslessTransferBeneficiary.ts
  • src/gasless/useGaslessTransferHolder.ts
  • src/gasless/useGaslessTransferOwners.ts
  • src/hooks/useContactForm.ts
  • src/hooks/useContractFunctionHook.tsx
  • src/hooks/useRecaptcha.ts
  • src/index.css
  • src/pages/About/index.tsx
  • src/pages/Contact/index.tsx
  • src/pages/Partners/index.tsx
  • src/pages/Settings/index.tsx
  • tailwind.config.js
💤 Files with no reviewable changes (5)
  • e2e/tests/nominate.spec.ts
  • e2e/tests/transfer-and-reject-beneficiary.spec.ts
  • e2e/setup-contracts.cjs
  • e2e/tests/transfer-and-reject-owners.spec.ts
  • e2e/tests/reject-return-to-issuer.spec.ts
🛑 Comments failed to post (8)
e2e/playwright.config.ts (1)

11-11: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'hardhat_reset|evm_snapshot|evm_revert|beforeEach|test\.retry' e2e .github/workflows/e2e.yml

Repository: TrustVC/trustvc-website

Length of output: 5017


🏁 Script executed:

#!/bin/bash
set -e

echo "## e2e/playwright.config.ts"
cat -n e2e/playwright.config.ts

echo
echo "## .github/workflows/e2e.yml"
cat -n .github/workflows/e2e.yml

echo
echo "## e2e spec files sizes and snapshot/revert locations"
python3 - <<'PY'
from pathlib import Path
for path in sorted(Path('e2e/tests').glob('*.spec.ts')):
    text = path.read_text()
    print(f"{path}: lines={len(text.splitlines())}")
    for needle in ['hardhat_reset','evm_snapshot','evm_revert','test.beforeEach']:
        hits = [i+1 for i,line in enumerate(text.splitlines()) if needle in line]
        if hits:
            print(f"  {needle}: {hits[:10]}{'...' if len(hits)>10 else ''}")
PY

echo
echo "## Relevant e2e helpers/config imports"
for f in $(git ls-files e2e/*.ts e2e/**/*.ts); do
  if rg -n 'hardhat|evm_snapshot|evm_revert|hardhat_reset|hardhatRpcNode|hardhatRpc' "$f" >/dev/null; then
    echo "### $f"
    rg -n 'hardhat|evm_snapshot|evm_revert|hardhat_reset|hardhatRpcNode|hardhatRpc' "$f"
  fi
done

Repository: TrustVC/trustvc-website

Length of output: 12015


🏁 Script executed:

#!/bin/bash
set -e

echo "## Nominate spec"
cat -n e2e/tests/nominate.spec.ts

echo
echo "## Transfer and reject owner/beneficiary specs"
for f in e2e/tests/transfer-and-reject-owners.spec.ts e2e/tests/transfer-and-reject-beneficiary.spec.ts e2e/tests/transfer-and-reject-holder.spec.ts e2e/tests/accept-return-to-issuer.spec.ts e2e/tests/reject-return-to-issuer.spec.ts; do
  echo "### $f"
  sed -n '1,120p' "$f" | cat -n
done

echo
echo "## Actions helpers around hardhat RPCs"
cat -n e2e/helpers/actions.ts | sed -n '140,190p'

Repository: TrustVC/trustvc-website

Length of output: 29016


Reset blockchain state before CI retries for transaction tests.

retries: 2 reruns failed tests against the same background Hardhat node, but the transaction specs don’t snapshot/revert or reset state. If a nomination, transfer, reject, or return flow fails after the contract transaction is accepted, the retry sees mutated on-chain state. Reset/redeploy the chain and wallet per retry, or disable retries for these stateful specs.

🤖 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 `@e2e/playwright.config.ts` at line 11, Update the Playwright configuration
around retries and the transaction specs so failed CI retries do not reuse
mutated Hardhat blockchain state: either reset/redeploy the chain and wallet
before each retry, or disable retries for the stateful nomination, transfer,
reject, and return tests while preserving retries for unrelated specs.
src/components/common/Tag/Tag.tsx (1)

54-54: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "urbanist-bold|fontFamily|font-urbanist" tailwind.config.js src/components/common/Tag/Tag.tsx

Repository: TrustVC/trustvc-website

Length of output: 320


🏁 Script executed:

#!/bin/bash
set -e

echo "== Candidate config files =="
git ls-files | rg '(^|/)(tailwind\.config\.(js|cjs|mjs|ts)|postcss\.config\.(js|cjs|mjs|ts)|package\.json)$|(^|/)styles/|(^|/)src/'

echo
echo "== Tailwind config around fontFamily =="
sed -n '40,80p' tailwind.config.js

echo
echo "== All font-family references =="
rg -n 'font-family|font-urba|urba|urbanist|fontFamily' -S . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -200

echo
echo "== Tag component =="
sed -n '1,90p' src/components/common/Tag/Tag.tsx

Repository: TrustVC/trustvc-website

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -e

echo "== Relevant Tailwind config excerpt =="
sed -n '45,65p' tailwind.config.js

echo
echo "== Tag component excerpt =="
sed -n '1,80p' src/components/common/Tag/Tag.tsx

echo
echo "== Focused text search =="
rg -n --hidden --glob '!*.gz' --glob '!*.map' '^.*(fontFamily|font-urba|urbanist-bold|font-urbanist)$|^.*(fontFamily|font-urba|urbanist-bold|font-urbanist)"|^.*(fontFamily|font-urba|urbanist-bold|font-urbanist):' . | head -200

Repository: TrustVC/trustvc-website

Length of output: 2160


Use a defined Tailwind font utility.

font-urbanist-bold has no matching fontFamily key in tailwind.config.js, so this class won’t generate the intended font. Use font-urbanist font-bold or define urbanist-bold explicitly.

🤖 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/components/common/Tag/Tag.tsx` at line 54, Update the className in the
Tag component to replace the undefined font-urbanist-bold utility with the
supported font-urbanist and font-bold utilities, preserving the other styling
classes.
src/components/home/VerifySection/VerifyResult.tsx (2)

82-93: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against stale delegation results.

checkEIP7702Delegation(...).then(setIsDelegated) has no cleanup, so if account/chainId change while a check is in flight, a late-resolving promise can overwrite isDelegated with a value for the previous wallet/chain.

🛠️ Proposed fix
   useEffect(() => {
     if (!account || !chainId) {
       setIsDelegated(false)
       return
     }
     const rpcUrl = getRpcUrl(chainId)
     if (!rpcUrl) {
       setIsDelegated(false)
       return
     }
-    checkEIP7702Delegation(account, rpcUrl).then(setIsDelegated)
+    let active = true
+    checkEIP7702Delegation(account, rpcUrl).then(result => {
+      if (active) setIsDelegated(result)
+    })
+    return () => {
+      active = false
+    }
   }, [account, chainId])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  useEffect(() => {
    if (!account || !chainId) {
      setIsDelegated(false)
      return
    }
    const rpcUrl = getRpcUrl(chainId)
    if (!rpcUrl) {
      setIsDelegated(false)
      return
    }
    let active = true
    checkEIP7702Delegation(account, rpcUrl).then(result => {
      if (active) setIsDelegated(result)
    })
    return () => {
      active = false
    }
  }, [account, chainId])
🤖 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/components/home/VerifySection/VerifyResult.tsx` around lines 82 - 93,
Update the useEffect delegation check to ignore results from stale requests when
account or chainId changes. Track whether the effect is still active, only call
setIsDelegated for the current check, and clean up that active state in the
effect’s return function; preserve the existing early resets for missing account
or RPC URL.

159-161: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Paymaster persistence is disabled — commented-out setItem breaks the pay-on-behalf feature. On success checkGasless no longer writes trustvc_paymaster_${account} to localStorage, so every consumer that reads that key is permanently inert: the auto-verify-on-delegation effect (VerifyResult.tsx L191-197) never finds a stored value, and the pay-on-behalf note never renders.

  • src/components/home/VerifySection/VerifyResult.tsx#L159-L161: restore localStorage.setItem(\trustvc_paymaster_${account}`, trimmed)` on the success branch (or remove the dependent read paths if persistence is intentionally dropped).
  • src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx#L193-L203: this note only appears once the key above is written; confirm it renders after the fix, otherwise this block is dead UI.
📍 Affects 2 files
  • src/components/home/VerifySection/VerifyResult.tsx#L159-L161 (this comment)
  • src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx#L193-L203
🤖 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/components/home/VerifySection/VerifyResult.tsx` around lines 159 - 161,
The successful checkGasless branch in VerifyResult.tsx must persist the trimmed
paymaster value using the trustvc_paymaster_${account} localStorage key; restore
this write before setting gaslessStatus to success. In ActionSelectionForm.tsx
lines 193-203, verify the existing pay-on-behalf note renders once that key is
written; make no direct change there unless required by the verification.
src/gasless/buildSmartAccountClient.ts (1)

34-34: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate file =="
git ls-files | rg '(^|/)buildSmartAccountClient\.ts$|src/gasless' || true

echo
echo "== File outline =="
ast-grep outline src/gasless/buildSmartAccountClient.ts || true

echo
echo "== Relevant file content =="
cat -n src/gasless/buildSmartAccountClient.ts | sed -n '1,120p'

echo
echo "== References to VITE_PIMLICO_API_KEY and buildSmartAccountClient =="
rg -n "VITE_PIMLICO_API_KEY|pimlicoApiKey|buildSmartAccountClient" .

Repository: TrustVC/trustvc-website

Length of output: 8814


Restrict the embedded Pimlico API key before exposing it in client URLs.

The Pimlico key is read from VITE_PIMLICO_API_KEY, passed into bundled client code, and added to Pimlico RPC requests as ?apikey=.... Make the Pimlico API key origin/domain-restricted so a leaked key cannot be reused from other pages/domains.

🤖 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/gasless/buildSmartAccountClient.ts` at line 34, Update the Pimlico API
key configuration used by buildSmartAccountClient and VITE_PIMLICO_API_KEY so
the key is restricted to the intended application origin/domain before embedding
it in the client-side RPC URL; preserve the existing chain-specific Pimlico
endpoint construction while ensuring leaked keys cannot be reused from other
domains.
src/gasless/useGaslessRejectReturned.ts (1)

66-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching useGaslessRejectReturned:"
fd -a 'useGaslessRejectReturned\.ts$' . || true

file=$(fd 'useGaslessRejectReturned\.ts$' . | head -n1)
if [ -n "${file:-}" ]; then
  echo "--- outline ---"
  ast-grep outline "$file" || true
  echo "--- relevant lines ---"
  nl -ba "$file" | sed -n '1,180p'
fi

echo "--- related gasless reject/confirm hooks ---"
rg -n "useGasless|gaslessRejectReturned|rejectReturned|titleEscrowAddress|tokenRegistryAddress" --iglob '*.ts' --iglob '*.tsx' .

Repository: TrustVC/trustvc-website

Length of output: 714


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=$(fd 'useGaslessRejectReturned\.ts$' . | head -n1)
if [ -n "${file:-}" ]; then
  python3 - <<'PY' "$file"
import pathlib, re, sys
p = pathlib.Path(sys.argv[1])
text = p.read_text()
checks = {
    "has gaslessConfig": "hasGaslessConfig" in text,
    "checks titleEscrowAddress": "!!titleEscrowAddress" in text,
    "checks tokenRegistryAddress": "!!tokenRegistryAddress" in text,
    "checks tokenId": "!!tokenId" in text,
    "gasless branch uses tokenRegistryAddress assertion": "tokenRegistryAddress as `0x${string}`" in text,
    "gasless branch uses tokenId assertion": "tokenId: tokenId!" in text,
    "whitelist uses titleEscrowAddress assertion": "isTitleEscrowAuthorized" in text and "titleEscrowAddress!" in text,
    "fallback guards tokenRegistryAddress": "if (!tokenRegistryAddress)" in text,
}
for k,v in checks.items():
    print(f"{k}: {v}")

# Extract conditional guards present before set hasGaslessConfig; print concise relevant assertions only.
for m in re.finditer(r"\bhasGaslessConfig\s*=([\s\S]*?)\n\s+if \(hasGaslessConfig\)", text):
    body = m.group(1)
    assertions = re.findall(r"\!\!([A-Za-z_$][A-Za-z0-9_$]*\)?|[^()]+?)[ \t]*\s*(?:,|\n|$)", body)
    print(f"guard assertions: {', '.join(asertions)}")
PY
fi

Repository: TrustVC/trustvc-website

Length of output: 586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="/home/jailuser/git/src/gasless/useGaslessRejectReturned.ts"
echo "--- file content ---"
awk '{printf "%6d  %s\n", NR, $0}' "$file"

echo "--- precise invariant checks ---"
python3 - "$file" <<'PY'
import sys, re
p = sys.argv[1]
text = open(p, encoding="utf-8").read()

guards = []
m = re.search(r"const hasGaslessConfig\s*=[\s\S]*?\n\s+if \(hasGaslessConfig\)", text)
if m:
    guards = re.findall(r"\!\!([^,\n)]+?)\b", m.group(0))
print("hasGaslessConfig guard checks:", guards)
for term in ["tokenRegistryAddress", "tokenId", "titleEscrowAddress"]:
    print(f"checks {term} in guard: {'!!' + term in guards}")

sections = {
    "gaslessCallOptions": re.search(r"if \(useGasless\) \{[\s\S]*?setTransactionHash\(txHash\)\s*\}", text),
    "fallback": re.search(r"} else \{[\s\S]*?if \(!tokenRegistryAddress\)", text),
}
for label, sec in sections.items():
    print(f"{label}: {sec is not None}")
    if sec:
        s = sec.group(0)
        print(f"  contains tokenRegistryAddress!: {'tokenRegistryAddress!' in s}")
        print(f"  contains tokenId!: {'tokenId!' in s}")
        print(f"  contains titleEscrowAddress!: {'titleEscrowAddress!' in s}")
PY

Repository: TrustVC/trustvc-website

Length of output: 6324


Require the consumed gasless params before entering the gasless path.

hasGaslessConfig enables gasless only from titleEscrowAddress, but rejectReturnedGasless needs tokenRegistryAddress and tokenId. Add these to the eligibility gate, or gate the whitelist/executive target behind the actual required params, so the path doesn’t proceed with assertions on missing values.

🤖 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/gasless/useGaslessRejectReturned.ts` around lines 66 - 121, Update
hasGaslessConfig in the gasless eligibility flow to also require
tokenRegistryAddress and tokenId before setting useGasless or entering
rejectReturnedGasless. Preserve the existing whitelist checks and ensure the
gasless branch cannot reach non-null assertions when either consumed parameter
is missing.
src/gasless/useGaslessTransferHolder.ts (1)

70-74: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find any writer of the paymaster localStorage key
rg -nP "trustvc_paymaster_" -g '!**/*.test.*' -C2

Repository: TrustVC/trustvc-website

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tracked files matching relevant names =="
git ls-files | rg '(^|/)Use(Gasless|Transfer)Result\.tsx$|(^|/)useGasless(Holder|Owners|RejectTransferBeneficiary|TransferOwners|TransferBeneficiary)\.(ts|tsx)$|(/gasless/|gasless/)' || true

echo
echo "== occurrences of paymaster key (all files) =="
rg -n "paymaster" . || true

echo
echo "== VerifyResult candidates =="
git ls-files | rg 'VerifyResult\.tsx$' || true

echo
echo "== git diff status/stat =="
git diff --stat

Repository: TrustVC/trustvc-website

Length of output: 6598


Restore persistance for user-entered paymaster addresses. The gasless hooks and stored-auto-verify path read localStorage.getItem('trustvc_paymaster_${account}'), but the matching write in VerifyResult.tsx remains commented out, so all reads fall back to PAYMASTER_ADDRESS even after whitelist verification succeeds. Re-enable the localStorage.setItem(...) after trimming/validation.

📍 Affects 3 files
  • src/gasless/useGaslessTransferHolder.ts#L70-L74 (this comment)
  • src/gasless/useGaslessTransferOwners.ts#L63-L67
  • src/gasless/useGaslessRejectTransferBeneficiary.ts#L64-L67
🤖 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/gasless/useGaslessTransferHolder.ts` around lines 70 - 74, Restore
persistence for validated user-entered paymaster addresses by re-enabling the
localStorage.setItem call in VerifyResult.tsx after trimming and validating the
address, using the trustvc_paymaster_${account} key consumed by the gasless
hooks. The affected read sites require no direct changes:
src/gasless/useGaslessTransferHolder.ts lines 70-74,
src/gasless/useGaslessTransferOwners.ts lines 63-67, and
src/gasless/useGaslessRejectTransferBeneficiary.ts lines 64-67 already read the
stored value correctly.
src/index.css (1)

15-29: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint errors flagged in the new/changed CSS: quoted font-family names and a duplicate declaration.

Stylelint (17.14.0) reports several real errors in the added/changed code:

  • font-family-name-quotes: quotes around 'Urbanist'/'Avenir' at lines 17, 26, 77, 2143, 2260, 2322, 2362, 3782, 4114, 4188, 4715 (most other new declarations in this file correctly use Urbanist, sans-serif unquoted, so this is also an internal inconsistency).
  • declaration-block-no-duplicate-properties: border-radius: 8px; is declared twice in .gasless-address-input-field (~2186-2190).
  • declaration-empty-line-before: multiple blank lines before declarations in the new check-gasless/pay-on-behalf rules (2208, 2293, 2322, 2342, 2345, 2355, 2362, 2368).

These are cheap to fix and, depending on the CI pipeline's lint gate, may currently be failing the build.

🎯 Example fixes
   `@font-face` {
-    font-family: 'Urbanist';
+    font-family: Urbanist;
   background: `#ffffff`;
   border-radius: 8px;
   /* TrustVC/Stroke/Base/Enabled */
   border: 1px solid `#a9b2bb`;
-  border-radius: 8px;
 }

Also applies to: 74-89, 2078-2372, 3779-3784, 4109-4117, 4184-4189, 4708-4719

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 17-17: Expected no quotes around "Urbanist" (font-family-name-quotes)

(font-family-name-quotes)


[error] 26-26: Expected no quotes around "Urbanist" (font-family-name-quotes)

(font-family-name-quotes)

🤖 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/index.css` around lines 15 - 29, Update the affected `@font-face` rules and
selectors in src/index.css to use unquoted font-family names, including Urbanist
and Avenir, consistent with the rest of the file. In
.gasless-address-input-field, remove the duplicate border-radius declaration.
Remove extra blank lines before declarations in the new check-gasless and
pay-on-behalf rules so they satisfy declaration-empty-line-before.

Source: Linters/SAST tools

RishabhS7 and others added 4 commits July 23, 2026 15:27
Lock file was missing @sentry/react, @sentry/vite-plugin, and react-ga4
that were added during the upstream merge, causing npm ci to fail in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ibility

The two whitelist-fallback tests require hasGaslessConfig=true (PIMLICO_API_KEY
set) to reach the whitelist check. In CI without a .env file the module-level
constant is undefined, so the hook skips delegation/whitelist entirely and the
assertions on checkPaymasterWhitelist always failed.

Moving them into the gasless section which uses vi.stubEnv + vi.resetModules +
dynamic imports ensures PIMLICO_API_KEY is set when these tests run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…talled

synpress-cache imports playwright-core at runtime. Since @playwright/test
was only a transitive peer dep of synpress (not listed directly), it was
not installed under legacy-peer-deps, causing ERR_MODULE_NOT_FOUND in CI
during the e2e:setup-wallet step. Pinning to 1.48.2 matches the version
already resolved in the lock file.
The pattern /verify-.*\.spec\.ts/ only matched files starting with
"verify-", leaving amoy-verify.spec.ts and pol-verify.spec.ts in the
main MetaMask suite. Those specs hit external RPC endpoints (Amoy/Polygon
mainnet) which are unreliable in CI and have no MetaMask dependency.

Widened to /.*verify.*\.spec\.ts/ so all verify specs (local, amoy, pol)
run only via playwright.verify.config.ts.
Comment thread .github/workflows/e2e.yml Outdated
RishabhS7 and others added 2 commits July 27, 2026 16:49
E2E runs on every push to main/develop were expensive and unnecessary.
Keeping only pull_request (gates PRs) and workflow_dispatch (manual runs).
…ty chain

- @testing-library/dom belongs in devDependencies (test-only, not shipped)
- retries disabled in playwright.config.ts: every spec mutates Hardhat chain
  state (transfer/reject/return), so a retry after partial failure hits an
  already-modified chain and fails for a different reason — retries here
  give false signal, not recovery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@RishabhS7

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rongquan1

Copy link
Copy Markdown
Contributor

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

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

Actionable comments posted: 20

🧹 Nitpick comments (14)
package.json (1)

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

Caret range on a beta pre-release can silently jump to an unrelated stable release.

^2.15.0-beta.2 desugars to >=2.15.0-beta.2 <3.0.0. Per npm semver rules, the prerelease-tuple restriction only limits which prerelease versions satisfy the range — once a stable 2.15.x/2.16.x/... version is published, it satisfies the range too (stable versions aren't gated by the prerelease-tuple rule). A future npm install could pull in an untested stable release with different gasless API behavior instead of the beta this PR was built and tested against.

♻️ Suggested fix: pin the exact beta
-    "`@trustvc/trustvc`": "^2.15.0-beta.2",
+    "`@trustvc/trustvc`": "2.15.0-beta.2",
🤖 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 `@package.json` at line 37, Update the `@trustvc/trustvc` dependency in
package.json from the caret range to the exact tested beta version
2.15.0-beta.2, preventing installation from selecting later stable or prerelease
versions.
.npmrc (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Global legacy-peer-deps=true masks peer-dependency conflicts project-wide.

This setting disables peer-dependency validation for every install, not just for the new permissionless/viem/beta @trustvc/trustvc additions. If it's needed because of a specific peer-dep mismatch (e.g. permissionless's viem peer range vs. the viem version pinned directly), consider scoping the fix via overrides/resolutions instead, so future genuine peer conflicts aren't silently hidden.

🤖 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 @.npmrc around lines 1 - 2, Remove the global legacy-peer-deps setting from
.npmrc and resolve the specific permissionless/viem/@trustvc/trustvc peer
mismatch through targeted package overrides or resolutions in the project’s
dependency configuration.
src/components/common/Icons/Icons.tsx (1)

235-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

stroke prop is only honored by one of the three shapes.

The <line> and <circle> hardcode #FF8200, so passing a custom stroke yields a two-tone icon. Also worth adding aria-hidden="true" as CheckCircle does, since this renders purely decoratively beside text.

🎨 Proposed fix
       xmlns="http://www.w3.org/2000/svg"
+      aria-hidden="true"
       {...props}
@@
-        stroke="`#FF8200`"
+        stroke={stroke}
         strokeWidth="1.5"
         strokeLinecap="round"
         strokeLinejoin="round"
       />
-      <circle cx="10.5357" cy="15.1786" r="0.535714" fill="`#FF8200`" />
+      <circle cx="10.5357" cy="15.1786" r="0.535714" fill={stroke} />
🤖 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/components/common/Icons/Icons.tsx` around lines 235 - 267, Update
InfoMsgIcon so the line and circle use the component’s stroke prop instead of
hardcoded `#FF8200`, keeping all three shapes consistently colored; also add
aria-hidden="true" to the SVG for decorative use.
src/gasless/useGaslessTransferHolder.ts (1)

153-154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unnecessary exhaustive-deps suppression.

reset is a useCallback with an empty dep array, so it's stable and can just be listed in the deps instead of disabling the rule. The other nine hooks inline the three setters rather than calling reset(); picking one style would remove the need for this suppression entirely.

🤖 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/gasless/useGaslessTransferHolder.ts` around lines 153 - 154, Remove the
unnecessary exhaustive-deps suppression in the hook containing reset, and
include the stable reset callback in that effect’s dependency array. Keep the
dependency behavior consistent with the surrounding hooks, avoiding inline
setter duplication unless needed to eliminate the reset call.
src/components/home/VerifySection/VerifyResult.tsx (3)

131-131: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Remove the debug logging before merge.

These console.log/warn/error calls ship the connected wallet address (via result/titleEscrowAddress), the resolved RPC endpoint, and raw errors to the browser console on every verification. Wallet addresses are user identifiers; drop these or route them through the project's logger at debug level.

Also applies to: 149-149, 157-157, 163-168, 173-173

🤖 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/components/home/VerifySection/VerifyResult.tsx` at line 131, Remove the
debug console logging in the verification flow around the checkGasless logic,
including the calls at the referenced locations that expose rpcUrl, chainId,
result, titleEscrowAddress, or raw errors. Do not log these values to the
browser console; if diagnostics are required, route them through the project
logger at debug level without exposing wallet addresses or sensitive error
details.

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

Use a relative import path.

'../../../../src/components/icons/info' walks up out of the tree and back down through src/, unlike every other import in this file. '../../icons/info' resolves to the same module.

🤖 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/components/home/VerifySection/VerifyResult.tsx` at line 21, Update the
InfoIcon import in VerifyResult.tsx to use the shorter relative path
../../icons/info, matching the surrounding imports and resolving to the same
module.

172-185: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Error classification by substring matching is fragile.

Any thrown error that doesn't contain network/fetch is reported to the user as "Invalid Paymaster Address" — including a reverted ownerOf(tokenId) read, a wrong-chain RPC, or a contract at the paymaster address that lacks authorizedCallers. Consider distinguishing the ownerOf read from the whitelist read so the message reflects which step actually failed.

🤖 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/components/home/VerifySection/VerifyResult.tsx` around lines 172 - 185,
The error handling in the gasless verification flow incorrectly classifies all
non-network failures as invalid paymaster addresses. Update the relevant
verification function and distinguish failures from the token ownerOf read
versus the paymaster whitelist/authorizedCallers read, assigning an error
message that reflects the failed step while preserving network-error handling.
src/gasless/checkDelegation.ts (1)

12-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a request timeout; consider reusing viem instead of raw fetch.

This runs inline in every send() before the transaction path. An unresponsive RPC has no timeout here, so the user is stuck in INITIALIZED indefinitely. Also, checkPaymasterWhitelist.ts already uses viem's createPublicClient; getCode would give the same result with consistent transport/retry handling.

⏱️ Minimal fix: bound the request
     const response = await fetch(rpcUrl, {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
+      signal: AbortSignal.timeout(10_000),
       body: JSON.stringify({
🤖 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/gasless/checkDelegation.ts` around lines 12 - 30, Bound the RPC request
in the delegation check so an unresponsive endpoint cannot leave send() stuck
indefinitely. Update the checkDelegation flow around the raw fetch to apply the
project’s established request-timeout approach, while preserving the existing
false-on-error behavior; preferably reuse the viem createPublicClient/getCode
pattern already used by checkPaymasterWhitelist.ts for consistent transport
handling.
src/gasless/useGaslessNominate.ts (1)

106-117: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Structural as cast on smartAccountClient hides a real contract.

Casting to an inline { sendTransaction(...) } shape suppresses any future signature drift in either permissionless or nominateGasless. Define the expected client interface once in src/gasless/ and reuse it across hooks.

🤖 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/gasless/useGaslessNominate.ts` around lines 106 - 117, Replace the inline
structural cast on smartAccountClient in the nominateGasless call with a shared
client interface defined in the gasless module. Update nominateGasless and
related hooks to reuse that interface, ensuring the sendTransaction signature
remains checked consistently and future contract drift is exposed by TypeScript.
src/gasless/useGaslessRejectTransferHolder.ts (1)

77-106: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

getRpcUrl(chainId!) is computed twice; reuse the value from Line 78.

Minor, but the second call at Line 104 also re-applies a non-null assertion on a value already proven non-null inside the if (rpcUrl) block. Hoisting rpcUrl above the hasGaslessConfig branch removes both.

🤖 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/gasless/useGaslessRejectTransferHolder.ts` around lines 77 - 106, Reuse
the existing rpcUrl variable from the gasless configuration flow when calling
buildSmartAccountClient, instead of invoking getRpcUrl(chainId!) again and
reapplying a non-null assertion. Hoist or scope rpcUrl so it remains available
after the hasGaslessConfig checks while preserving the existing validation that
prevents the gasless path without a valid RPC URL.
src/gasless/buildSmartAccountClient.ts (1)

59-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded paymaster gas limits and empty paymasterData assume a permissionless (non-signature-verifying) paymaster.

If the platform paymaster ever requires signed data or the postOp cost changes, these constants will silently under/over-estimate. Consider sourcing them from config, or querying the paymaster service, rather than baking magic numbers into the client.

🤖 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/gasless/buildSmartAccountClient.ts` around lines 59 - 77, Update the
paymaster configuration in buildSmartAccountClient, specifically
getPaymasterStubData and getPaymasterData, to obtain paymasterData and gas
limits from the configured paymaster source or service instead of returning
hardcoded empty data and magic-number limits. Ensure both methods use the same
authoritative values while preserving isFinal on the stub response.
src/gasless/gaslessHooks.test.tsx (3)

763-775: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

"State transitions" only asserts the terminal state.

INITIALIZED and PENDING_CONFIRMATION are never observed, so a hook that jumped straight to CONFIRMED would pass. Capture intermediate states by resolving the mocked tx from a deferred promise.

🤖 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/gasless/gaslessHooks.test.tsx` around lines 763 - 775, Strengthen the
“state transitions” test for useGaslessTransferHolder by making the mocked
transaction confirmation deferred, then assert INITIALIZED before sending and
PENDING_CONFIRMATION after send begins but before the deferred transaction
resolves. Resolve the deferred transaction and retain the final CONFIRMED
assertion after the send completes.

976-1028: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This describe block does not test what its title claims.

All three tests assert only state === 'CONFIRMED', and the inline comment at Line 984 concedes the paymaster read path is actually verified elsewhere. The tests at Line 998 and Line 1013 are functionally identical to each other. The real assertion — that the localStorage value wins over PAYMASTER_ADDRESS — needs the gasless-path hooks plus a checkPaymasterWhitelist argument check, which is already done once at Line 540.

Suggest collapsing to a single meaningful test in the gasless block that stubs VITE_PAYMASTER_ADDRESS to a sentinel and asserts the localStorage value takes precedence.

🤖 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/gasless/gaslessHooks.test.tsx` around lines 976 - 1028, Replace the three
redundant tests in the describe block with one gasless-path test that configures
a sentinel VITE_PAYMASTER_ADDRESS, stores a different paymaster in localStorage,
and asserts checkPaymasterWhitelist receives the localStorage value as
resolvedPaymasterAddress. Reuse the existing gasless setup and assertion pattern
near the earlier checkPaymasterWhitelist test, and remove the state-only
fallback tests.

74-80: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verify the ACCOUNT reference inside the hoisted vi.mock factory.

vi.mock factories are hoisted above the const ACCOUNT declaration at Line 121. This is safe only because ACCOUNT is dereferenced lazily when useProviderContext() is invoked during renderHook, not when the factory runs. It is fragile — moving the constant or making the mock eager reintroduces a TDZ error. Consider vi.hoisted for ACCOUNT alongside the other hoisted values.

Also applies to: 121-129

🤖 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/gasless/gaslessHooks.test.tsx` around lines 74 - 80, Move the ACCOUNT
test value into a vi.hoisted declaration alongside the other hoisted values, and
use that hoisted symbol in the useProviderContext mock factory and affected test
setup. Remove the later const ACCOUNT declaration while preserving the existing
account value and mock behavior.
🤖 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
`@src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx`:
- Line 199: Update the user-facing text in ActionSelectionForm from “all
transaction” to “all transactions,” leaving the surrounding behavior unchanged.
- Around line 193-203: Replace the direct localStorage lookup in
ActionSelectionForm’s render with shared reactive paymaster state, preferably
via a usePaymasterAddress(account) hook or existing context backed by the same
trustvc_paymaster key used by the gasless hooks. Ensure VerifyResult updates
that state when the key is written so the pay-on-behalf note re-renders
immediately, while preserving the existing account guard and note behavior.

In `@src/components/home/VerifySection/VerifyResult.tsx`:
- Line 160: The trustvc_paymaster_${account} localStorage contract is broken
because its writer is disabled and consumers do not react to updates. In
src/components/home/VerifySection/VerifyResult.tsx:160-160, restore the
successful-verification setItem and remove the key on failure; in
src/gasless/useGaslessTransferBeneficiary.ts:65-68, make no direct change, as
the shared fix should cover all useGasless* hooks; extract the key and lookup
into one shared helper; and in
src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx:193-203,
replace the render-time getItem with the helper backed by React state so
post-mount writes update the indicator.
- Around line 82-93: Guard the async result in the useEffect that calls
checkEIP7702Delegation so only the latest account and chainId request can update
isDelegated. Track whether the effect is still active and prevent setIsDelegated
after cleanup, while preserving the existing reset behavior for missing accounts
or RPC URLs.
- Around line 366-397: Update the paymaster address input in VerifyResult so it
has a persistent programmatic label rather than relying on the placeholder, and
connect the gasless error message to the input with matching accessible
identifiers. Mark the input’s invalid state when gaslessStatus is error and
ensure the associated error text is announced by assistive technologies.

In `@src/gasless/checkPaymasterWhitelist.ts`:
- Around line 21-36: Update the whitelist-checking function around publicClient
and the parallel authorizedCallers/authorizedTitleEscrows reads to validate
paymasterAddress, userAddress, and titleEscrowAddress before casting or calling
readContract, and use a chain-configured client rather than a chain-less
createPublicClient. Ensure invalid addresses or failed contract reads are caught
and returned as an ineligible result so callers can continue through the paid
path instead of propagating the error.

In `@src/gasless/gaslessHooks.test.tsx`:
- Around line 921-964: Update the “clears state after a successful transaction”
test to explicitly mock checkEIP7702Delegation as resolving false before
rendering useGaslessNominate, ensuring it always exercises the fallback path
regardless of VITE_PIMLICO_API_KEY.

In `@src/gasless/useGaslessAcceptReturned.ts`:
- Line 1: Extract the duplicated gasless eligibility and dispatch setup from
useGaslessAcceptReturned into a shared helper or hook, such as
useGaslessEligibility, returning useGasless and smartAccountClient after
resolving configuration, validating delegation and whitelist status, and
building the client. Update useGaslessAcceptReturned and
useGaslessRejectTransferBeneficiary to consume this result while retaining only
their action-specific gasless/non-gasless calls and parameters.
- Line 1: Update the eligibility blocks in useGaslessAcceptReturned and
useGaslessRejectTransferBeneficiary to locally catch errors from
checkEIP7702Delegation and checkPaymasterWhitelist, keep useGasless false when
either check fails, and continue into the existing paid-transaction path instead
of reaching the outer send catch that sets ERROR. Preserve current gasless
behavior when eligibility checks succeed.
- Around line 66-92: Update hasGaslessConfig in useGaslessAcceptReturned to
validate tokenRegistryAddress instead of, or in addition to, titleEscrowAddress,
matching the required arguments for acceptReturnedGasless. Ensure the gasless
path is only selected when tokenRegistryAddress is present, preserving the
fallback branch’s existing missing-address handling.

In `@src/gasless/useGaslessNominate.ts`:
- Around line 62-73: The resolved paymaster address in useGaslessNominate must
not be trusted directly from localStorage. Validate its address format and
require it to match the configured allowlist of approved platform paymasters
before using it in hasGaslessConfig or buildSmartAccountClient; otherwise fall
back to no usable paymaster configuration.
- Around line 57-93: Wrap the gasless eligibility checks within the main try—
including getRpcUrl, checkEIP7702Delegation, and checkPaymasterWhitelist— in a
nested try/catch that sets useGasless to false on any RPC failure. Keep the
outer transaction error handling unchanged so eligibility failures fall back to
the paid path instead of setting the hook state to ERROR.

In `@src/gasless/useGaslessRejectReturned.ts`:
- Around line 66-72: Update hasGaslessConfig in useGaslessRejectReturned to also
require tokenRegistryAddress and tokenId before selecting the gasless path. Keep
the existing account, titleEscrowAddress, paymaster, API key, chain, and
ethereum checks, and ensure rejectReturnedGasless is only called when all
asserted arguments are present.

In `@src/gasless/useGaslessRejectTransferOwners.ts`:
- Around line 50-142: Extract a shared createGaslessHook factory that resolves
paymaster eligibility and builds the smart-account client inside a separate
try/catch, defaulting to the direct transaction path on eligibility failures.
Apply it to src/gasless/useGaslessRejectTransferOwners.ts lines 50-142 using
rejectTransferOwners/rejectTransferOwnersGasless,
src/gasless/useGaslessNominate.ts lines 57-93 using nominate/nominateGasless and
NominateParams, src/gasless/useGaslessRejectTransferHolder.ts lines 77-106 using
rejectTransferHolder/rejectTransferHolderGasless, and
src/gasless/useGaslessReturnToIssuer.ts lines 56-92 using
returnToIssuer/returnToIssuerGasless; remove each duplicated eligibility,
paymaster, and client-building implementation while preserving each hook’s
required fields and transaction arguments.

In `@src/gasless/useGaslessTransferBeneficiary.ts`:
- Around line 60-107: Extract the shared gasless eligibility and smart-account
construction flow from the hook containing setState('INITIALIZED'), including
paymaster resolution, hasGaslessConfig checks, delegation, whitelist validation,
and buildSmartAccountClient, into a reusable useGaslessExecutor or
resolveGaslessExecution helper. Update each useGasless* hook to call this helper
and handle its { smartAccountClient } | null result before performing its
action-specific execution, preserving existing eligibility behavior.
- Around line 137-140: Update the catch block in the gasless transfer flow to
ensure setErrorMessage receives a non-empty message when getMetaMaskErrorMessage
returns an empty string for unknown bundler, paymaster, or EIP-7702 errors.
Preserve known MetaMask/ethers mappings, and fall back to the caught error’s
available message or a generic gasless transfer failure message before setting
state to ERROR.

In `@src/index.css`:
- Around line 2307-2332: Merge the two .check-gasless-content-success-text
rulesets into a single declaration, preserving all effective properties
including flex-direction, padding, and gap alongside the typography and layout
styles. Remove the duplicate selector while keeping the existing styling
behavior unchanged.
- Line 2143: Update the CSS declarations in src/index.css to remove quotes from
the Avenir font-family name and eliminate the flagged empty lines before
declarations at the referenced locations; run stylelint --fix to apply the
required formatting consistently.
- Around line 2334-2360: Update the .pay-on-behalf-note,
.pay-on-behalf-note-frame, and .pay-on-behalf-note-text rules to remove fixed
width and height constraints that can overflow or clip text. Use responsive
max-width values compatible with the .dropdown-btn-frame parent and height:
auto, while preserving the existing flex layout and spacing.
- Around line 3881-3882: Replace the shorthand gap declaration in the affected
style rule with column-gap: 10px, preserving row-gap: 0px so only horizontal
spacing is applied and the shorthand override warning is resolved.

---

Nitpick comments:
In @.npmrc:
- Around line 1-2: Remove the global legacy-peer-deps setting from .npmrc and
resolve the specific permissionless/viem/@trustvc/trustvc peer mismatch through
targeted package overrides or resolutions in the project’s dependency
configuration.

In `@package.json`:
- Line 37: Update the `@trustvc/trustvc` dependency in package.json from the caret
range to the exact tested beta version 2.15.0-beta.2, preventing installation
from selecting later stable or prerelease versions.

In `@src/components/common/Icons/Icons.tsx`:
- Around line 235-267: Update InfoMsgIcon so the line and circle use the
component’s stroke prop instead of hardcoded `#FF8200`, keeping all three shapes
consistently colored; also add aria-hidden="true" to the SVG for decorative use.

In `@src/components/home/VerifySection/VerifyResult.tsx`:
- Line 131: Remove the debug console logging in the verification flow around the
checkGasless logic, including the calls at the referenced locations that expose
rpcUrl, chainId, result, titleEscrowAddress, or raw errors. Do not log these
values to the browser console; if diagnostics are required, route them through
the project logger at debug level without exposing wallet addresses or sensitive
error details.
- Line 21: Update the InfoIcon import in VerifyResult.tsx to use the shorter
relative path ../../icons/info, matching the surrounding imports and resolving
to the same module.
- Around line 172-185: The error handling in the gasless verification flow
incorrectly classifies all non-network failures as invalid paymaster addresses.
Update the relevant verification function and distinguish failures from the
token ownerOf read versus the paymaster whitelist/authorizedCallers read,
assigning an error message that reflects the failed step while preserving
network-error handling.

In `@src/gasless/buildSmartAccountClient.ts`:
- Around line 59-77: Update the paymaster configuration in
buildSmartAccountClient, specifically getPaymasterStubData and getPaymasterData,
to obtain paymasterData and gas limits from the configured paymaster source or
service instead of returning hardcoded empty data and magic-number limits.
Ensure both methods use the same authoritative values while preserving isFinal
on the stub response.

In `@src/gasless/checkDelegation.ts`:
- Around line 12-30: Bound the RPC request in the delegation check so an
unresponsive endpoint cannot leave send() stuck indefinitely. Update the
checkDelegation flow around the raw fetch to apply the project’s established
request-timeout approach, while preserving the existing false-on-error behavior;
preferably reuse the viem createPublicClient/getCode pattern already used by
checkPaymasterWhitelist.ts for consistent transport handling.

In `@src/gasless/gaslessHooks.test.tsx`:
- Around line 763-775: Strengthen the “state transitions” test for
useGaslessTransferHolder by making the mocked transaction confirmation deferred,
then assert INITIALIZED before sending and PENDING_CONFIRMATION after send
begins but before the deferred transaction resolves. Resolve the deferred
transaction and retain the final CONFIRMED assertion after the send completes.
- Around line 976-1028: Replace the three redundant tests in the describe block
with one gasless-path test that configures a sentinel VITE_PAYMASTER_ADDRESS,
stores a different paymaster in localStorage, and asserts
checkPaymasterWhitelist receives the localStorage value as
resolvedPaymasterAddress. Reuse the existing gasless setup and assertion pattern
near the earlier checkPaymasterWhitelist test, and remove the state-only
fallback tests.
- Around line 74-80: Move the ACCOUNT test value into a vi.hoisted declaration
alongside the other hoisted values, and use that hoisted symbol in the
useProviderContext mock factory and affected test setup. Remove the later const
ACCOUNT declaration while preserving the existing account value and mock
behavior.

In `@src/gasless/useGaslessNominate.ts`:
- Around line 106-117: Replace the inline structural cast on smartAccountClient
in the nominateGasless call with a shared client interface defined in the
gasless module. Update nominateGasless and related hooks to reuse that
interface, ensuring the sendTransaction signature remains checked consistently
and future contract drift is exposed by TypeScript.

In `@src/gasless/useGaslessRejectTransferHolder.ts`:
- Around line 77-106: Reuse the existing rpcUrl variable from the gasless
configuration flow when calling buildSmartAccountClient, instead of invoking
getRpcUrl(chainId!) again and reapplying a non-null assertion. Hoist or scope
rpcUrl so it remains available after the hasGaslessConfig checks while
preserving the existing validation that prevents the gasless path without a
valid RPC URL.

In `@src/gasless/useGaslessTransferHolder.ts`:
- Around line 153-154: Remove the unnecessary exhaustive-deps suppression in the
hook containing reset, and include the stable reset callback in that effect’s
dependency array. Keep the dependency behavior consistent with the surrounding
hooks, avoiding inline setter duplication unless needed to eliminate the reset
call.
🪄 Autofix (Beta)

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: 1c2bdd05-815e-451a-aa26-0a13176f32bb

📥 Commits

Reviewing files that changed from the base of the PR and between 17fdb7b and f772084.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • .npmrc
  • e2e/fixtures/local/w3c/tr_accept_surrender.json
  • e2e/fixtures/local/w3c/tr_reject_surrender.json
  • e2e/helpers/actions.ts
  • e2e/playwright.config.ts
  • e2e/setup-contracts.cjs
  • e2e/tests/nominate.spec.ts
  • e2e/tests/reject-return-to-issuer.spec.ts
  • e2e/tests/transfer-and-reject-beneficiary.spec.ts
  • e2e/tests/transfer-and-reject-owners.spec.ts
  • package.json
  • src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx
  • src/components/common/Icons/Icons.tsx
  • src/components/common/Icons/index.ts
  • src/components/common/contexts/TokenInformationContext/TokenInformationContext.tsx
  • src/components/home/VerifySection/VerifyResult.tsx
  • src/gasless/buildSmartAccountClient.ts
  • src/gasless/checkDelegation.test.ts
  • src/gasless/checkDelegation.ts
  • src/gasless/checkPaymasterWhitelist.test.ts
  • src/gasless/checkPaymasterWhitelist.ts
  • src/gasless/gaslessHooks.test.tsx
  • src/gasless/index.ts
  • src/gasless/useGaslessAcceptReturned.ts
  • src/gasless/useGaslessNominate.ts
  • src/gasless/useGaslessRejectReturned.ts
  • src/gasless/useGaslessRejectTransferBeneficiary.ts
  • src/gasless/useGaslessRejectTransferHolder.ts
  • src/gasless/useGaslessRejectTransferOwners.ts
  • src/gasless/useGaslessReturnToIssuer.ts
  • src/gasless/useGaslessTransferBeneficiary.ts
  • src/gasless/useGaslessTransferHolder.ts
  • src/gasless/useGaslessTransferOwners.ts
  • src/hooks/useContractFunctionHook.tsx
  • src/index.css
💤 Files with no reviewable changes (5)
  • e2e/tests/transfer-and-reject-owners.spec.ts
  • e2e/setup-contracts.cjs
  • e2e/tests/reject-return-to-issuer.spec.ts
  • e2e/tests/transfer-and-reject-beneficiary.spec.ts
  • e2e/tests/nominate.spec.ts

Comment thread src/components/home/VerifySection/VerifyResult.tsx
Comment thread src/components/home/VerifySection/VerifyResult.tsx Outdated
Comment thread src/components/home/VerifySection/VerifyResult.tsx
Comment thread src/gasless/useGaslessTransferBeneficiary.ts Outdated
Comment thread src/index.css
Comment thread src/index.css
Comment thread src/index.css Outdated
Comment thread src/index.css Outdated
- Extract shared makeGaslessHook factory to eliminate ~90-line duplication
  across 10 hooks; each hook is now a ~20-line thin wrapper
- Add paymasterStorage.ts helper with custom event for same-tab reactivity
- Make ActionSelectionForm reactive to paymaster localStorage changes
- Add try/catch around eligibility checks to fall back to paid path on RPC failure
- Add tokenRegistryAddress + tokenId to hasGaslessConfig for AcceptReturned/RejectReturned
- Fix blank error messages for unknown/gasless errors with Error.message fallback
- Add aria-invalid, aria-describedby, role="alert" to paymaster address input
- Merge duplicate .check-gasless-content-success-text CSS rules
- Replace hard-coded pixel dimensions with fluid values in pay-on-behalf CSS
- Replace gap shorthand with column-gap to preserve row-gap: 0px
Previously the component rendered errorMessage OR children — never both.
After the error-fallback fix in makeGaslessHook (e.message for unknown
errors), errorMessage became truthy for contract-level reverts, hiding
address/context children that E2E tests depend on.

Now children always render; errorMessage appends below when present,
giving users both the context (current addresses) and the error detail.
Swap localStorage for a module-level Map so the paymaster address
clears on every page load. Also drops the cross-tab 'storage' event
listener in ActionSelectionForm since it is no longer relevant.

TODO: restore localStorage persistence when the feature is ready.
@rongquan1
rongquan1 merged commit 38b29c6 into develop Jul 29, 2026
5 checks passed
@rongquan1
rongquan1 deleted the feat/gasless-transactions branch July 29, 2026 02:03
@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 2026
rongquan1 pushed a commit that referenced this pull request Jul 31, 2026
* chore: e2e tests for txns

* feat: gasless transactions

* fix: add legacy-peer-deps to resolve permissionless/ox/viem conflict

permissionless@0.3.7 declares peerOptional ox@^0.11.3 but viem@2.55.1
ships ox@0.14.30, which is out of range. trustvc@2.15.0-beta.2 requires
both viem@^2.53.1 and permissionless@^0.3.6, so neither can be downgraded.
legacy-peer-deps makes npm skip the conflicting optional peer check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: regenerate package-lock.json with sentry and react-ga4 entries

Lock file was missing @sentry/react, @sentry/vite-plugin, and react-ga4
that were added during the upstream merge, causing npm ci to fail in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: move whitelist fallback tests into gasless section for CI compatibility

The two whitelist-fallback tests require hasGaslessConfig=true (PIMLICO_API_KEY
set) to reach the whitelist check. In CI without a .env file the module-level
constant is undefined, so the hook skips delegation/whitelist entirely and the
assertions on checkPaymasterWhitelist always failed.

Moving them into the gasless section which uses vi.stubEnv + vi.resetModules +
dynamic imports ensures PIMLICO_API_KEY is set when these tests run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add @playwright/test explicitly to ensure playwright-core is installed

synpress-cache imports playwright-core at runtime. Since @playwright/test
was only a transitive peer dep of synpress (not listed directly), it was
not installed under legacy-peer-deps, causing ERR_MODULE_NOT_FOUND in CI
during the e2e:setup-wallet step. Pinning to 1.48.2 matches the version
already resolved in the lock file.

* fix: exclude amoy-verify and pol-verify specs from main e2e run

The pattern /verify-.*\.spec\.ts/ only matched files starting with
"verify-", leaving amoy-verify.spec.ts and pol-verify.spec.ts in the
main MetaMask suite. Those specs hit external RPC endpoints (Amoy/Polygon
mainnet) which are unreliable in CI and have no MetaMask dependency.

Widened to /.*verify.*\.spec\.ts/ so all verify specs (local, amoy, pol)
run only via playwright.verify.config.ts.

* chore: remove push trigger from e2e workflow

E2E runs on every push to main/develop were expensive and unnecessary.
Keeping only pull_request (gates PRs) and workflow_dispatch (manual runs).

* fix: move @testing-library/dom to devDeps; disable e2e retries on dirty chain

- @testing-library/dom belongs in devDependencies (test-only, not shipped)
- retries disabled in playwright.config.ts: every spec mutates Hardhat chain
  state (transfer/reject/return), so a retry after partial failure hits an
  already-modified chain and fails for a different reason — retries here
  give false signal, not recovery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply CodeRabbit review fixes for gasless transaction feature

- Extract shared makeGaslessHook factory to eliminate ~90-line duplication
  across 10 hooks; each hook is now a ~20-line thin wrapper
- Add paymasterStorage.ts helper with custom event for same-tab reactivity
- Make ActionSelectionForm reactive to paymaster localStorage changes
- Add try/catch around eligibility checks to fall back to paid path on RPC failure
- Add tokenRegistryAddress + tokenId to hasGaslessConfig for AcceptReturned/RejectReturned
- Fix blank error messages for unknown/gasless errors with Error.message fallback
- Add aria-invalid, aria-describedby, role="alert" to paymaster address input
- Merge duplicate .check-gasless-content-success-text CSS rules
- Replace hard-coded pixel dimensions with fluid values in pay-on-behalf CSS
- Replace gap shorthand with column-gap to preserve row-gap: 0px

* fix: show both children and errorMessage in DocumentTransferMessage

Previously the component rendered errorMessage OR children — never both.
After the error-fallback fix in makeGaslessHook (e.message for unknown
errors), errorMessage became truthy for contract-level reverts, hiding
address/context children that E2E tests depend on.

Now children always render; errorMessage appends below when present,
giving users both the context (current addresses) and the error detail.

* chore: reset paymaster address on page reload (in-memory store)

Swap localStorage for a module-level Map so the paymaster address
clears on every page load. Also drops the cross-tab 'storage' event
listener in ActionSelectionForm since it is no longer relevant.

TODO: restore localStorage persistence when the feature is ready.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot mentioned this pull request Jul 31, 2026
rongquan1 pushed a commit that referenced this pull request Jul 31, 2026
* feat: gasless transactions (#89)

* chore: e2e tests for txns

* feat: gasless transactions

* fix: add legacy-peer-deps to resolve permissionless/ox/viem conflict

permissionless@0.3.7 declares peerOptional ox@^0.11.3 but viem@2.55.1
ships ox@0.14.30, which is out of range. trustvc@2.15.0-beta.2 requires
both viem@^2.53.1 and permissionless@^0.3.6, so neither can be downgraded.
legacy-peer-deps makes npm skip the conflicting optional peer check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: regenerate package-lock.json with sentry and react-ga4 entries

Lock file was missing @sentry/react, @sentry/vite-plugin, and react-ga4
that were added during the upstream merge, causing npm ci to fail in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: move whitelist fallback tests into gasless section for CI compatibility

The two whitelist-fallback tests require hasGaslessConfig=true (PIMLICO_API_KEY
set) to reach the whitelist check. In CI without a .env file the module-level
constant is undefined, so the hook skips delegation/whitelist entirely and the
assertions on checkPaymasterWhitelist always failed.

Moving them into the gasless section which uses vi.stubEnv + vi.resetModules +
dynamic imports ensures PIMLICO_API_KEY is set when these tests run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add @playwright/test explicitly to ensure playwright-core is installed

synpress-cache imports playwright-core at runtime. Since @playwright/test
was only a transitive peer dep of synpress (not listed directly), it was
not installed under legacy-peer-deps, causing ERR_MODULE_NOT_FOUND in CI
during the e2e:setup-wallet step. Pinning to 1.48.2 matches the version
already resolved in the lock file.

* fix: exclude amoy-verify and pol-verify specs from main e2e run

The pattern /verify-.*\.spec\.ts/ only matched files starting with
"verify-", leaving amoy-verify.spec.ts and pol-verify.spec.ts in the
main MetaMask suite. Those specs hit external RPC endpoints (Amoy/Polygon
mainnet) which are unreliable in CI and have no MetaMask dependency.

Widened to /.*verify.*\.spec\.ts/ so all verify specs (local, amoy, pol)
run only via playwright.verify.config.ts.

* chore: remove push trigger from e2e workflow

E2E runs on every push to main/develop were expensive and unnecessary.
Keeping only pull_request (gates PRs) and workflow_dispatch (manual runs).

* fix: move @testing-library/dom to devDeps; disable e2e retries on dirty chain

- @testing-library/dom belongs in devDependencies (test-only, not shipped)
- retries disabled in playwright.config.ts: every spec mutates Hardhat chain
  state (transfer/reject/return), so a retry after partial failure hits an
  already-modified chain and fails for a different reason — retries here
  give false signal, not recovery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply CodeRabbit review fixes for gasless transaction feature

- Extract shared makeGaslessHook factory to eliminate ~90-line duplication
  across 10 hooks; each hook is now a ~20-line thin wrapper
- Add paymasterStorage.ts helper with custom event for same-tab reactivity
- Make ActionSelectionForm reactive to paymaster localStorage changes
- Add try/catch around eligibility checks to fall back to paid path on RPC failure
- Add tokenRegistryAddress + tokenId to hasGaslessConfig for AcceptReturned/RejectReturned
- Fix blank error messages for unknown/gasless errors with Error.message fallback
- Add aria-invalid, aria-describedby, role="alert" to paymaster address input
- Merge duplicate .check-gasless-content-success-text CSS rules
- Replace hard-coded pixel dimensions with fluid values in pay-on-behalf CSS
- Replace gap shorthand with column-gap to preserve row-gap: 0px

* fix: show both children and errorMessage in DocumentTransferMessage

Previously the component rendered errorMessage OR children — never both.
After the error-fallback fix in makeGaslessHook (e.message for unknown
errors), errorMessage became truthy for contract-level reverts, hiding
address/context children that E2E tests depend on.

Now children always render; errorMessage appends below when present,
giving users both the context (current addresses) and the error detail.

* chore: reset paymaster address on page reload (in-memory store)

Swap localStorage for a module-level Map so the paymaster address
clears on every page load. Also drops the cross-tab 'storage' event
listener in ActionSelectionForm since it is no longer relevant.

TODO: restore localStorage persistence when the feature is ready.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply CodeRabbit review fixes (round 2) (#94)

* fix: apply CodeRabbit review fixes (round 2)

- Icons: use stroke prop for line/circle in warning icon (was hardcoded #FF8200)
- VerifyResult: remove debug console statements leaking RPC URL and wallet identifiers
- checkDelegation: add 10s AbortController timeout to RPC fetch
- AcceptReturned/RejectReturned: add tokenId validation to directGuard
- useContactForm: validate presigned.length === pendingItems.length before upload
- index.css: replace fixed height/width on gasless error classes with min-height/auto

* chore: pin @trustvc/trustvc to exact version 2.15.0-beta.2

Remove caret range to prevent unintended upgrades to future beta versions.

* fix: merge conflicts (#98)

* fix: code rabbit changes

* chore: replace legacy-peer-deps with targeted ox override for permissionless

permissionless@0.3.7 has optional peer ox@^0.11.3 but viem@2.55.1 ships
ox@0.14.30, which is outside that range. Adding overrides.permissionless.ox
in package.json pins the resolution to 0.14.30 so npm resolves cleanly
without the blanket legacy-peer-deps flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: code rabit comments

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: wire VITE_PIMLICO_API_KEY into dev and prod deploy workflows (#99)

Without this key at build time hasGaslessConfig evaluates to false in
makeGaslessHook, causing every transaction to fall back to the direct
(payable) path even after paymaster verification succeeds.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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