Skip to content

Conversation

@mikeredmond
Copy link
Member

@mikeredmond mikeredmond commented Dec 5, 2025

Description

This pull request introduces support for dApp-sponsored (delegated fee) transactions in the transaction hooks and UI, and improves the GitHub Actions workflows for preview environment deployment and teardown. The main changes add an optional dAppSponsoredUrl parameter throughout the transaction building and sending flow, and update the preview deployment workflow to enforce branch naming conventions for external pull requests.

dApp-Sponsored Transaction Support:

  • Added an optional dAppSponsoredUrl parameter to useBuildTransaction, useSendTransaction, and the PrivyWalletProviderContextType, allowing transactions to be sent via a delegator for sponsored fees. This includes updates to type definitions, function signatures, and transaction sending logic.

  • Updated the TransactionExamples component to demonstrate and handle dApp-sponsored transactions, including new button logic, modal state handling, and user feedback.

Preview Environment Workflow Improvements:

  • Enforced branch naming convention for external pull requests in the deploy-preview.yaml workflow, requiring branches to be prefixed with the GitHub username (e.g., username/feature-branch). The workflow now fails early if this is not met, and user-facing messages were updated accordingly.

  • Improved deployment comment logic in the preview deployment workflow to avoid duplicate comments by checking for an existing deployment comment before posting a new one.

  • Changed the destroy preview environment workflow trigger from pull_request to pull_request_target for better permission handling, and improved the logic for finding and removing deployment comments.

Demo

Screen.Recording.2025-12-02.at.11.27.33.mov

Closes #471

Summary by CodeRabbit

  • New Features

    • Added per-call delegation URL support enabling delegated transaction flows.
    • UI flows now support both delegated and standard transactions with distinct modal/toast behavior.
  • Improvements

    • Improved fee estimation for delegator scenarios with smarter token selection.
    • Streamlined preview deployment lifecycle: branch-name validation, normalized preview URLs, and reliable preview comment create/update/delete.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

Adds per-call fee delegation support by threading a new delegationUrl through build/send hooks, Privy integration, and provider signing; replaces several gas-estimation hook usages with a new generic-delegator estimator; and improves GitHub preview workflows with branch-name validation and comment replacement logic.

Changes

Cohort / File(s) Summary
Workflow updates
/.github/workflows/deploy-preview.yaml, /.github/workflows/destroy-preview-env.yaml
Added branch-prefix validation for external PRs, normalized branch-name processing, added Find/Create/Update PR comment steps to replace preview comments, and adjusted destroy workflow to remove deployment comment once.
Transaction hooks — delegation surface
packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts, packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
Added delegationUrl?: string to public props/signatures; threaded delegationUrl through send/build flows, memoization deps, and adapter call sites; integrated Privy request path for transaction submission.
Privy wallet provider
packages/vechain-kit/src/providers/PrivyWalletProvider.tsx
Extended provider/context API to accept per-call delegationUrl, adjusted signAndSend to use delegationUrl for gas payer resolution, and changed delegation selection to prefer explicit delegationUrl over generic delegator.
Generic delegator: new estimator & API change
packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts, packages/vechain-kit/src/hooks/generic-delegator/index.ts, packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegator.ts
Introduced useGenericDelegatorFeeEstimation (React Query hook) to pick a fee token and estimate costs across tokens; unified transaction body building to always include isDelegated; replaced exported useGasEstimation with this new estimator.
UI components — hook replacements
packages/vechain-kit/src/components/AccountModal/Contents/ChooseName/ChooseNameSummaryContent.tsx, .../Profile/Customization/CustomizationSummaryContent.tsx, .../SendToken/SendTokenSummaryContent.tsx, .../Swap/SwapTokenContent.tsx
Replaced imports/usages of useGasEstimation with useGenericDelegatorFeeEstimation in multiple modal/summary components without changing call-site argument structure.
Example app — dual flows
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
Added delegated and non-delegated transaction flows, new modal/toast state to switch content, distinct handlers and retry paths, and UI wiring to reflect delegated vs non-delegated loading states.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as DApp UI
    participant Hook as useSendTransaction / useBuildTransaction
    participant Privy as Privy Wallet Service
    participant Provider as PrivyWalletProvider
    participant Thor as Thor SDK / Node

    UI->>Hook: user triggers sendTransaction(..., delegationUrl?)
    Hook->>Hook: build clauses / include delegationUrl in payload
    Hook->>Provider: request signAndSend(payload, delegationUrl)
    alt delegationUrl provided
        Provider->>Privy: call privyWalletProvider.sendTransaction(payload with delegationUrl)
        Privy->>Provider: txid / response
    else no delegationUrl (generic delegator)
        Provider->>Provider: resolve genericDelegatorUrl
        Provider->>Privy: call privyWalletProvider.sendTransaction(payload with genericDelegatorUrl)
        Privy->>Provider: txid / response
    end
    Provider->>Thor: send signed tx / buildTransactionBody (isDelegated flag)
    Thor->>Provider: tx receipt / txid
    Provider->>Hook: resolve promise with txid/receipt
    Hook->>UI: update UI (toasts/modals)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files requiring extra attention:
    • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts — Privy integration, new payload shape, dependency arrays.
    • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts — token-iteration logic, query key/enabled conditions, error propagation.
    • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx — gas payer resolution precedence and conditional branching when delegationUrl is present.
    • Example UI file TransactionExamples.tsx — ensure state isolation between delegated/non-delegated flows.

Possibly related PRs

Suggested labels

safe-to-deploy

Suggested reviewers

  • victorkl400
  • Agilulfo1820

Poem

🐰
I hopped in code with eager paws,
I threaded URLs through clause and clause,
Privy hums and sponsors pay,
Gas finds tokens on its way,
Now transactions dance — hip hooray! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Allow once-off sponsored transactions' clearly summarizes the main change—adding support for one-off dApp-sponsored transactions—which aligns with the core objective of the PR.
Description check ✅ Passed The PR description includes a summary of changes related to dApp-sponsored transactions and workflow improvements, and it closes issue #471. The description follows the template structure and provides comprehensive detail.
Linked Issues check ✅ Passed The PR successfully implements the core coding requirements from #471: adds optional delegationUrl/dAppSponsoredUrl to transaction hooks (useBuildTransaction, useSendTransaction, PrivyWalletProvider), implements delegation-aware transaction sending logic, and extends examples to demonstrate sponsored transactions.
Out of Scope Changes check ✅ Passed The PR includes workflow changes (.github/workflows files) for preview environment deployment and teardown that, while not explicitly mentioned in #471, serve to improve the development infrastructure and align with managing external PR workflows. These represent supportive changes rather than out-of-scope additions.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mike/sponsor-specific-txs

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 and usage tips.

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

Size Change: -87.1 kB (-1.59%)

Total Size: 5.39 MB

Filename Size Change
packages/vechain-kit/dist/index-C8Uj8ple.d.cts 0 B -5.63 kB (removed) 🏆
packages/vechain-kit/dist/index-C8Uj8ple.d.cts.map 0 B -2.99 kB (removed) 🏆
packages/vechain-kit/dist/index-CWViOs1U.d.mts 0 B -5.63 kB (removed) 🏆
packages/vechain-kit/dist/index-CWViOs1U.d.mts.map 0 B -2.99 kB (removed) 🏆
packages/vechain-kit/dist/index-HLGzf4yQ.d.cts 0 B -145 kB (removed) 🏆
packages/vechain-kit/dist/index-HLGzf4yQ.d.cts.map 0 B -41.9 kB (removed) 🏆
packages/vechain-kit/dist/index-yuX8WKhf.d.mts 0 B -145 kB (removed) 🏆
packages/vechain-kit/dist/index-yuX8WKhf.d.mts.map 0 B -41.9 kB (removed) 🏆
packages/vechain-kit/dist/index.cjs 571 kB -6.93 kB (-1.2%)
packages/vechain-kit/dist/index.cjs.map 1.73 MB -36.2 kB (-2.05%)
packages/vechain-kit/dist/index.mjs 537 kB -6.77 kB (-1.24%)
packages/vechain-kit/dist/index.mjs.map 1.68 MB -36 kB (-2.1%)
packages/vechain-kit/dist/index-BPTAm-na.d.cts 5.63 kB +5.63 kB (new file) 🆕
packages/vechain-kit/dist/index-BPTAm-na.d.cts.map 2.99 kB +2.99 kB (new file) 🆕
packages/vechain-kit/dist/index-BYfc-9J5.d.mts 5.63 kB +5.63 kB (new file) 🆕
packages/vechain-kit/dist/index-BYfc-9J5.d.mts.map 2.99 kB +2.99 kB (new file) 🆕
packages/vechain-kit/dist/index-DwfbUIxI.d.cts 145 kB +145 kB (new file) 🆕
packages/vechain-kit/dist/index-DwfbUIxI.d.cts.map 41.3 kB +41.3 kB (new file) 🆕
packages/vechain-kit/dist/index-n8mA8dv9.d.mts 145 kB +145 kB (new file) 🆕
packages/vechain-kit/dist/index-n8mA8dv9.d.mts.map 41.3 kB +41.3 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
packages/vechain-kit/dist/assets 4.1 kB 0 B
packages/vechain-kit/dist/assets-aAdDxPJu.mjs 50.1 kB 0 B
packages/vechain-kit/dist/assets-aAdDxPJu.mjs.map 70.2 kB 0 B
packages/vechain-kit/dist/assets-DXVXPy3w.cjs 54.8 kB 0 B
packages/vechain-kit/dist/assets-DXVXPy3w.cjs.map 71.6 kB 0 B
packages/vechain-kit/dist/assets/index.cjs 716 B 0 B
packages/vechain-kit/dist/assets/index.d.cts 973 B 0 B
packages/vechain-kit/dist/assets/index.d.mts 973 B 0 B
packages/vechain-kit/dist/assets/index.mjs 718 B 0 B
packages/vechain-kit/dist/index.d.cts 20.1 kB +64 B (+0.32%)
packages/vechain-kit/dist/index.d.mts 20.1 kB +64 B (+0.32%)
packages/vechain-kit/dist/utils 4.1 kB 0 B
packages/vechain-kit/dist/utils-Bl-JeVTg.cjs 26.2 kB 0 B
packages/vechain-kit/dist/utils-Bl-JeVTg.cjs.map 63 kB 0 B
packages/vechain-kit/dist/utils-DAs6kMGs.mjs 21.1 kB 0 B
packages/vechain-kit/dist/utils-DAs6kMGs.mjs.map 62.7 kB 0 B
packages/vechain-kit/dist/utils/index.cjs 1.91 kB 0 B
packages/vechain-kit/dist/utils/index.d.cts 2.94 kB 0 B
packages/vechain-kit/dist/utils/index.d.mts 2.94 kB 0 B
packages/vechain-kit/dist/utils/index.mjs 1.93 kB 0 B

compressed-size-action

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
.github/workflows/deploy-preview.yaml (1)

128-137: Condition prevents comment updates; comment will never be edited after initial creation.

The conditional if: steps.find-comment.outputs.comment-id == '' causes the step to run only when no existing comment is found. This means existing comments are never updated, defeating the purpose of the Find Comment step.

Remove the condition or always run the step so that create-or-update-comment can update existing deployment comments. The action handles both scenarios: if comment-id is provided, it updates; if empty, it creates.

  - name: Create Deployment Comment
    uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
-   if: steps.find-comment.outputs.comment-id == ''
    with:
        comment-id: ${{ steps.find-comment.outputs.comment-id }}
packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (1)

168-189: dAppSponsoredUrl parameter is accepted but unused in the VeChain wallet (non-Privy) code path.

The parameter is passed to sendTransaction (line 132) but only used when connected via Privy (line 145). In the VeChain wallet path (lines 168-188), the parameter is ignored and buildTransactionBody is called with only an isDelegated flag, which doesn't support per-transaction sponsored URLs. The TODO comment at line 172 ("kit-migration check how to pass the delegator url") confirms this is incomplete. Either this feature needs to be implemented for VeChain wallets, or the parameter should be removed from the non-Privy code path to avoid confusion about its availability.

packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts (1)

62-79: Remove unnecessary dAppSponsoredUrl parameter from useSendTransaction hook props.

The dAppSponsoredUrl parameter destructured at the hook level (line 112 of useSendTransaction.ts) is never used—it appears only in the dependency array but not in any actual logic. The sendTransactionAdapter function accepts and uses dAppSponsoredUrl as a call-time parameter instead. In useBuildTransaction, passing dAppSponsoredUrl to useSendTransaction at line 68 is unnecessary; only the call-time pass at line 77 has effect. Remove the hook-level parameter to eliminate dead code and the apparent redundancy.

🧹 Nitpick comments (1)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (1)

48-66: Demo placeholder for dAppSponsoredUrl is commented out.

The dAppSponsoredUrl is commented out, which means this demo doesn't actually exercise the sponsored transaction flow. Consider either:

  1. Adding a placeholder URL that developers can see in action, or
  2. Adding a comment explaining how to test with a real delegator URL.
         suggestedMaxGas: undefined,
-        // dAppSponsoredUrl: "", <--- YOUR DELEGATOR URL HERE
+        // To test sponsored transactions, uncomment and provide your delegator URL:
+        // dAppSponsoredUrl: "https://sponsor.example.com/delegate",
     });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between deac832 and c73ed0c.

📒 Files selected for processing (6)
  • .github/workflows/deploy-preview.yaml (4 hunks)
  • .github/workflows/destroy-preview-env.yaml (2 hunks)
  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (4 hunks)
  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts (5 hunks)
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (10 hunks)
  • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
  • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
🧠 Learnings (3)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
  • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : In VeChain Kit Version 2, use `useThor` instead of `useConnex` for contract interactions

Applied to files:

  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For multiple parallel contract calls, use the `executeMultipleClausesCall` utility wrapped in a `useQuery` hook with the pattern: define query key function, use `executeMultipleClausesCall` in queryFn mapping items to clause objects, and transform results

Applied to files:

  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
🧬 Code graph analysis (1)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (2)
packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts (1)
  • useBuildTransaction (30-83)
examples/next-template/src/app/constants.ts (1)
  • b3trMainnetAddress (1-1)
🪛 Biome (2.1.2)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx

[error] 95-95: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint & Build
  • GitHub Check: e2e-test
  • GitHub Check: build-size-check
  • GitHub Check: deploy
🔇 Additional comments (6)
.github/workflows/destroy-preview-env.yaml (2)

4-4: Clarify the security implications of pull_request_target trigger.

The switch from pull_request to pull_request_target changes workflow execution to the base branch context (main) with full access to secrets. While this provides better permission isolation for untrusted external PRs, it should be explicitly documented in your PR or ADR to ensure the team understands the security posture change and its implications.

Is this change intended for security hardening? If so, please confirm it's aligned with your organization's GitHub Actions security guidelines.


45-51: LGTM: Appropriate scope reduction for BRANCH_NAME.

Moving BRANCH_NAME from top-level env to step-level env (line 48) is good practice—it limits variable visibility to where it's used and reduces potential for accidental reuse in unintended contexts.

packages/vechain-kit/src/providers/PrivyWalletProvider.tsx (2)

189-195: Logic correctly bypasses generic delegator when dAppSponsoredUrl is provided.

The condition genericDelegator && !dAppSponsoredUrl ensures that when a per-transaction sponsor URL is provided, it takes precedence over the generic delegator path. This aligns with the PR objective of allowing one-off sponsored transactions.


104-125: LGTM: signAndSend correctly prioritizes dAppSponsoredUrl.

The dAppSponsoredUrl ?? delegatorUrl fallback ensures the per-transaction sponsor URL takes precedence, falling back to the default delegator URL when not provided. This is the correct behavior for enabling one-off sponsored transactions.

packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (2)

214-241: Adapter signature updated correctly to forward dAppSponsoredUrl.

The sendTransactionAdapter now accepts _dAppSponsoredUrl as a second parameter and forwards it to the internal sendTransaction. This enables callers to specify a per-transaction sponsor URL.


52-60: Public API correctly updated for dAppSponsoredUrl parameter.

The UseSendTransactionReturnValue.sendTransaction signature now includes dAppSponsoredUrl as an optional second parameter, aligning with the implementation and enabling consumers to pass the sponsor URL per transaction.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (3)

48-66: Consider extracting the shared clause builder to reduce duplication.

The buildTransactionWithDAppSponsored hook duplicates the entire configuration from the first useBuildTransaction hook (lines 22-46), differing only in the commented-out dAppSponsoredUrl. While this duplication may aid clarity in a demo, extracting the common clauseBuilder function would demonstrate best practices and improve maintainability.

Apply this diff to extract the shared logic:

+    const buildDummyTransferClause = useCallback(() => {
+        if (!account?.address) return [];
+
+        return [
+            {
+                ...thor.contracts
+                    .load(b3trMainnetAddress, IB3TR__factory.abi)
+                    .clause.transfer(account.address, BigInt('0')).clause,
+                comment: `This is a dummy transaction to test the transaction modal. Confirm to transfer 0 B3TR to ${account?.address}`,
+            },
+        ];
+    }, [account?.address, thor.contracts]);
+
     const {
         sendTransaction,
         status,
         txReceipt,
         isTransactionPending,
         error,
         resetStatus,
     } = useBuildTransaction({
-        clauseBuilder: () => {
-            if (!account?.address) return [];
-
-            return [
-                {
-                    ...thor.contracts
-                        .load(b3trMainnetAddress, IB3TR__factory.abi)
-                        .clause.transfer(account.address, BigInt('0')).clause,
-                    comment: `This is a dummy transaction to test the transaction modal. Confirm to transfer 0 B3TR to ${account?.address}`,
-                },
-            ];
-        },
+        clauseBuilder: buildDummyTransferClause,
         refetchQueryKeys: [],
         onSuccess: () => {},
         onFailure: () => {},
         suggestedMaxGas: undefined,
     });

     const buildTransactionWithDAppSponsored = useBuildTransaction({
-        clauseBuilder: () => {
-            if (!account?.address) return [];
-
-            return [
-                {
-                    ...thor.contracts
-                        .load(b3trMainnetAddress, IB3TR__factory.abi)
-                        .clause.transfer(account.address, BigInt('0')).clause,
-                    comment: `This is a dummy transaction to test the transaction modal. Confirm to transfer 0 B3TR to ${account?.address}`,
-                },
-            ];
-        },
+        clauseBuilder: buildDummyTransferClause,
         refetchQueryKeys: [],
         onSuccess: () => {},
         onFailure: () => {},
         suggestedMaxGas: undefined,
         // dAppSponsoredUrl: "", <--- YOUR DELEGATOR URL HERE
     });

117-118: Optional: Simplify the fallback logic.

Since buildTransactionWithDAppSponsored is always returned by useBuildTransaction, its isTransactionPending property should always be defined (true or false), making the nullish coalescing fallback unnecessary. However, this defensive pattern is reasonable for a demo and doesn't cause any functional issues.

If you prefer to simplify:

-                        isLoading={buildTransactionWithDAppSponsored.isTransactionPending ?? isTransactionPending}
-                        isDisabled={buildTransactionWithDAppSponsored.isTransactionPending ?? isTransactionPending}
+                        isLoading={buildTransactionWithDAppSponsored.isTransactionPending}
+                        isDisabled={buildTransactionWithDAppSponsored.isTransactionPending}

141-144: Optional: Consider simplifying modal prop fallbacks.

Similar to the button states, the nullish coalescing fallbacks for status, txReceipt, error, and onTryAgain are likely unnecessary since buildTransactionWithDAppSponsored is always defined. However, this pattern is acceptable for demonstrating both transaction flows in an example.

If you prefer to use only the sponsored flow:

-                status={buildTransactionWithDAppSponsored.status ?? status}
-                txReceipt={buildTransactionWithDAppSponsored.txReceipt ?? txReceipt}
-                txError={buildTransactionWithDAppSponsored.error ?? error}
-                onTryAgain={handleTryAgainWithSponsoredModal ?? handleTryAgain}
+                status={buildTransactionWithDAppSponsored.status}
+                txReceipt={buildTransactionWithDAppSponsored.txReceipt}
+                txError={buildTransactionWithDAppSponsored.error}
+                onTryAgain={handleTryAgainWithSponsoredModal}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 158d622 and d4163d8.

📒 Files selected for processing (1)
  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : Always conditionally enable queries using the `enabled` property to prevent unnecessary contract calls, checking for all required parameters: `enabled: !!requiredParam && !!otherRequiredParam`

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
🧬 Code graph analysis (1)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (2)
packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts (1)
  • useBuildTransaction (30-83)
examples/next-template/src/app/constants.ts (1)
  • b3trMainnetAddress (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-size-check
🔇 Additional comments (3)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (3)

85-88: Good fix for the Promise fallback logic.

The previous incorrect use of nullish coalescing with Promises has been properly resolved. The modal now directly invokes the sponsored transaction flow, which correctly demonstrates the feature.


95-98: Excellent fix for the Rules of Hooks violation.

The conditional hook call has been properly resolved. useCallback is now invoked unconditionally, which complies with React's Rules of Hooks. The retry logic correctly resets and re-sends the sponsored transaction.


146-146: Clear and appropriate title update.

The modal title correctly indicates this demonstrates a DApp-sponsored transaction, helping developers understand which flow they're testing.

@Agilulfo1820
Copy link
Member

Can you update also the docs?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (1)

31-31: Stale closure: balances not in queryKey may cause incorrect token selection.

The balances array is accessed inside queryFn but isn't included in queryKey. When balances update (e.g., after a transaction), the query won't refetch, and the closure will use stale balance data, potentially selecting an incorrect gas token.

Consider adding a stable representation of balances to the queryKey:

-    const queryKey = ['gas-estimation', JSON.stringify(clauses), JSON.stringify(tokens), sendingAmount, sendingTokenSymbol];
+    const queryKey = ['gas-estimation', JSON.stringify(clauses), JSON.stringify(tokens), sendingAmount, sendingTokenSymbol, JSON.stringify(balances.map(b => ({ symbol: b.symbol, balance: b.balance })))];

Alternatively, if you want to avoid frequent refetches, ensure the enabled condition and manual invalidation handle this correctly.

Also applies to: 49-49

packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (1)

208-234: Signature mismatch between adapter and exported type.

The exported UseSendTransactionReturnValue.sendTransaction type (lines 53-61) accepts privyUIOptions as the third parameter, but sendTransactionAdapter (line 209) only accepts two parameters and always uses privyUIOptions from the hook's closure. Callers passing privyUIOptions at call time will have their value ignored.

Either update the adapter to accept and use the third parameter, or update the exported type to reflect the actual behavior.

If the intent is to allow per-call privyUIOptions:

     const sendTransactionAdapter = useCallback(
-        async (_clauses?: TransactionClause[], _delegationUrl?: string): Promise<void> => {
+        async (_clauses?: TransactionClause[], _delegationUrl?: string, _privyUIOptions?: {
+            title?: string;
+            description?: string;
+            buttonText?: string;
+        }): Promise<void> => {
             if (!_clauses && !clauses) throw new Error('clauses are required');
             try {
                 // ...
-                const response = await sendTransaction(_clauses ?? [], _delegationUrl, {
-                    ...privyUIOptions,
-                });
+                const response = await sendTransaction(_clauses ?? [], _delegationUrl, _privyUIOptions ?? privyUIOptions);
🧹 Nitpick comments (7)
packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts (2)

62-79: Redundant delegationUrl passing pattern.

delegationUrl is passed to useSendTransaction at line 68, and then passed again to sendTransaction at line 77. If the intent is to allow per-call overrides, this is acceptable but could benefit from documentation. Otherwise, one of these could be removed.

If per-call override is not needed, simplify by removing the second parameter:

     const sendTransaction = useCallback(
         async (props: ClausesParams) => {
-            result.sendTransaction(clauseBuilder(props), delegationUrl);
+            result.sendTransaction(clauseBuilder(props));
         },
-        [clauseBuilder, result, delegationUrl],
+        [clauseBuilder, result],
     );

If per-call override IS the intended design (allowing different delegation URLs for different calls), consider documenting this in the JSDoc and updating the function signature to make it explicit:

const sendTransaction = useCallback(
    async (props: ClausesParams, overrideDelegationUrl?: string) => {
        result.sendTransaction(clauseBuilder(props), overrideDelegationUrl ?? delegationUrl);
    },
    [clauseBuilder, result, delegationUrl],
);

15-15: Standardize parameter naming for fee delegation across the transaction hooks API.

The transaction hooks (useBuildTransaction, useSendTransaction) use delegationUrl while the provider configuration uses delegatorUrl. Although this distinction may be intentional (override vs. config), the inconsistency creates API confusion. Consider aligning the naming with VeChain SDK conventions—either standardizing on delegatorUrl or consistently prefixing override parameters (e.g., delegatorUrlOverride).

packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (3)

8-14: Interface naming convention: use PascalCase.

The interface name useGenericDelegatorFeeEstimationParams uses camelCase, which is unconventional for TypeScript interfaces. Interface names typically use PascalCase.

-export interface useGenericDelegatorFeeEstimationParams {
+export interface UseGenericDelegatorFeeEstimationParams {
     clauses: EnhancedClause[];
     enabled?: boolean;
     tokens: string[]; // Array of tokens to try in order
     sendingAmount?: string; // Amount being sent
     sendingTokenSymbol?: string; // Symbol of token being sent
 }

76-77: Redundant retryDelay when retry: false.

retryDelay: 1000 has no effect since retry: false disables retries entirely. Consider removing it to avoid confusion.

         retry: false,
-        retryDelay: 1000,

59-59: Side effect inside queryFn: updatePreferences may cause unexpected behavior.

Calling updatePreferences inside queryFn is a side effect that will execute each time the query runs (including refetches, retries if enabled, or cache invalidation). This could cause unintended preference updates. Consider moving this side effect to a useEffect that reacts to the query result's usedToken.

// In the consuming component or a wrapper:
const { data } = useGenericDelegatorFeeEstimation({...});

useEffect(() => {
    if (data?.usedToken) {
        updatePreferences({ gasTokenToUse: data.usedToken as GasTokenType });
    }
}, [data?.usedToken]);
packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (2)

234-234: Unnecessary dependency: delegationUrl in adapter's deps.

sendTransactionAdapter receives _delegationUrl as a parameter and doesn't use the delegationUrl prop from closure. Including it in the dependency array causes unnecessary recreation of the callback when the prop changes.

-        [sendTransaction, clauses, privyUIOptions, delegationUrl],
+        [sendTransaction, clauses, privyUIOptions],

362-364: Unnecessary dependency in useMemo.

sendTransactionPending is listed in the dependency array but isn't used in the memo body (only status is checked). This doesn't cause bugs but is misleading.

     const isWaitingForWalletConfirmation = useMemo(() => {
         return status === 'pending';
-    }, [sendTransactionPending, status]);
+    }, [status]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4163d8 and 2fafbf3.

📒 Files selected for processing (9)
  • packages/vechain-kit/src/components/AccountModal/Contents/ChooseName/ChooseNameSummaryContent.tsx (2 hunks)
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/Customization/CustomizationSummaryContent.tsx (2 hunks)
  • packages/vechain-kit/src/components/AccountModal/Contents/SendToken/SendTokenSummaryContent.tsx (2 hunks)
  • packages/vechain-kit/src/components/AccountModal/Contents/Swap/SwapTokenContent.tsx (2 hunks)
  • packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (1 hunks)
  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegator.ts (0 hunks)
  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts (5 hunks)
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (11 hunks)
  • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx (6 hunks)
💤 Files with no reviewable changes (1)
  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegator.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/components/AccountModal/Contents/Swap/SwapTokenContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SendToken/SendTokenSummaryContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/Customization/CustomizationSummaryContent.tsx
  • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx
  • packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/ChooseName/ChooseNameSummaryContent.tsx
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
🧠 Learnings (4)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : In VeChain Kit Version 2, use `useThor` instead of `useConnex` for contract interactions

Applied to files:

  • packages/vechain-kit/src/components/AccountModal/Contents/Swap/SwapTokenContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SendToken/SendTokenSummaryContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/Customization/CustomizationSummaryContent.tsx
  • packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/ChooseName/ChooseNameSummaryContent.tsx
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For single contract read operations, use the `useCallClause` hook with the pattern: import dependencies, define ABI and method as const, create query key function using `getCallClauseQueryKeyWithArgs`, and wrap with useCallClause including data transformation in the select option

Applied to files:

  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/Customization/CustomizationSummaryContent.tsx
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • packages/vechain-kit/src/providers/PrivyWalletProvider.tsx
  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For multiple parallel contract calls, use the `executeMultipleClausesCall` utility wrapped in a `useQuery` hook with the pattern: define query key function, use `executeMultipleClausesCall` in queryFn mapping items to clause objects, and transform results

Applied to files:

  • packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
🧬 Code graph analysis (4)
packages/vechain-kit/src/components/AccountModal/Contents/SendToken/SendTokenSummaryContent.tsx (1)
packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (1)
  • useGenericDelegatorFeeEstimation (16-79)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/Customization/CustomizationSummaryContent.tsx (1)
packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (1)
  • useGenericDelegatorFeeEstimation (16-79)
packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (1)
packages/vechain-kit/src/types/types.ts (1)
  • EnhancedClause (130-130)
packages/vechain-kit/src/components/AccountModal/Contents/ChooseName/ChooseNameSummaryContent.tsx (1)
packages/vechain-kit/src/hooks/generic-delegator/useGasEstimation.ts (1)
  • useGenericDelegatorFeeEstimation (16-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-size-check
  • GitHub Check: Lint & Build
  • GitHub Check: e2e-test
  • GitHub Check: deploy
🔇 Additional comments (9)
packages/vechain-kit/src/providers/PrivyWalletProvider.tsx (3)

241-251: Missing delegationUrl in sendTransaction dependency array.

The sendTransaction callback uses delegationUrl in the conditional check at line 190, but it's not included in the useCallback dependency array. This could cause stale closure issues if delegationUrl changes.

     }, [
         sendTransactionUsingGenericDelegator,
         genericDelegator,
         smartAccount,
         connectedWallet,
         delegatorUrl,
         buildClausesWithAuth,
         hasV1SmartAccount,
         smartAccountVersion,
         thor,
+        // Note: delegationUrl is passed per-call, not as a hook dependency
     ]);

However, since delegationUrl is passed per-call via the payload (line 170) and not from the outer scope, this is actually correct. The callback receives delegationUrl from its parameter, not from closure.


103-152: Well-structured delegation URL resolution in signAndSend.

The fallback chain delegationUrl ?? delegatorUrl at line 124 correctly prioritizes per-call delegation over the global delegator URL, enabling the one-off sponsored transaction feature.


189-195: Correct conditional logic for generic delegator bypass.

The condition genericDelegator && !delegationUrl correctly ensures that when a per-call delegationUrl is provided, the generic delegator path is bypassed in favor of the explicit delegation endpoint.

packages/vechain-kit/src/components/AccountModal/Contents/Profile/Customization/CustomizationSummaryContent.tsx (1)

28-28: Hook rename applied correctly.

The migration from useGasEstimation to useGenericDelegatorFeeEstimation is consistent with the broader refactoring across the codebase. The hook invocation signature and return value destructuring remain unchanged.

Also applies to: 190-196

packages/vechain-kit/src/components/AccountModal/Contents/ChooseName/ChooseNameSummaryContent.tsx (1)

27-28: Hook rename applied correctly.

The migration to useGenericDelegatorFeeEstimation is consistent with the codebase-wide refactoring. The clauses() function call pattern aligns with how domain claim hooks expose their clause builders.

Also applies to: 174-180

packages/vechain-kit/src/components/AccountModal/Contents/Swap/SwapTokenContent.tsx (1)

45-46: Hook rename with swap-specific parameters applied correctly.

The migration to useGenericDelegatorFeeEstimation correctly preserves the swap-specific parameters (sendingAmount, sendingTokenSymbol) which the hook uses to calculate whether the user has sufficient balance when the gas token matches the sending token.

Also applies to: 344-355

packages/vechain-kit/src/components/AccountModal/Contents/SendToken/SendTokenSummaryContent.tsx (1)

29-30: Hook rename with send-specific parameters applied correctly.

The migration to useGenericDelegatorFeeEstimation correctly includes sendingAmount and sendingTokenSymbol parameters, enabling the hook to verify the user has sufficient balance for both the transfer amount and gas fees when using the same token.

Also applies to: 204-212

packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (2)

169-182: LGTM!

The requestTransaction integration with conditional delegator configuration correctly handles both global fee delegation (delegateAllTransactions) and per-transaction delegation (delegationUrl). The fallback chain delegationUrl ?? feeDelegation?.delegatorUrl properly prioritizes the per-call URL.


53-61: The privyWalletProvider.sendTransaction interface already supports delegationUrl. The PrivyWalletProviderContextType interface in packages/vechain-kit/src/providers/PrivyWalletProvider.tsx (line 41) includes delegationUrl?: string as a parameter in the sendTransaction method signature, so the delegation will work correctly for Privy-connected wallets.


return useQuery<EstimationResponse & { usedToken: string }, Error>({
queryKey,
queryFn: async () => {
Copy link

@aikido-pr-checks aikido-pr-checks bot Dec 11, 2025

Choose a reason for hiding this comment

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

The queryFn in useGenericDelegatorFeeEstimation nests a for -> try -> if return path and accumulates lastError instead of using early continues/guard clauses to simplify loop control.

Details

🔧 How do I fix it?
Place parameter validation and guard clauses at the function start. Use early returns to reduce nesting levels and improve readability.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts (1)

8-14: Type tokens and usedToken as GasTokenType to avoid unsafe casts

Right now tokens is string[] and you repeatedly cast to GasTokenType; this loses type safety and can hide invalid values until runtime. Narrowing the API to GasTokenType[] (and usedToken: GasTokenType) lets TS catch mistakes at call sites and removes the need for as casts.

-export interface useGenericDelegatorFeeEstimationParams {
-    clauses: EnhancedClause[];
-    enabled?: boolean;
-    tokens: string[]; // Array of tokens to try in order
-    sendingAmount?: string; // Amount being sent
-    sendingTokenSymbol?: string; // Symbol of token being sent
-}
+export interface useGenericDelegatorFeeEstimationParams {
+    clauses: EnhancedClause[];
+    enabled?: boolean;
+    tokens: GasTokenType[]; // Array of tokens to try in order
+    sendingAmount?: string; // Amount being sent
+    sendingTokenSymbol?: GasTokenType; // Symbol of token being sent
+}
@@
-    return useQuery<EstimationResponse & { usedToken: string }, Error>({
+    return useQuery<EstimationResponse & { usedToken: GasTokenType }, Error>({
@@
-            for (const token of tokens) {
+            for (const token of tokens) {
@@
-                        clauses as TransactionClause[],
-                        token as GasTokenType,
+                        clauses as TransactionClause[],
+                        token,
@@
-                        updatePreferences({ gasTokenToUse: token as GasTokenType });
-                        return { ...estimation, usedToken: token };
+                        updatePreferences({ gasTokenToUse: token });
+                        return { ...estimation, usedToken: token };

Also applies to: 33-34, 39-45, 57-60

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fafbf3 and 3fe329c.

📒 Files selected for processing (2)
  • packages/vechain-kit/src/hooks/generic-delegator/index.ts (1 hunks)
  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts
  • packages/vechain-kit/src/hooks/generic-delegator/index.ts
🧠 Learnings (3)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For single contract read operations, use the `useCallClause` hook with the pattern: import dependencies, define ABI and method as const, create query key function using `getCallClauseQueryKeyWithArgs`, and wrap with useCallClause including data transformation in the select option

Applied to files:

  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For multiple parallel contract calls, use the `executeMultipleClausesCall` utility wrapped in a `useQuery` hook with the pattern: define query key function, use `executeMultipleClausesCall` in queryFn mapping items to clause objects, and transform results

Applied to files:

  • packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts
🧬 Code graph analysis (1)
packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts (7)
packages/vechain-kit/src/types/types.ts (1)
  • EnhancedClause (130-130)
packages/vechain-kit/src/hooks/thor/smartAccounts/useSmartAccount.ts (1)
  • useSmartAccount (51-60)
packages/vechain-kit/src/providers/VeChainKitProvider.tsx (1)
  • useVeChainKitConfig (204-210)
packages/vechain-kit/src/hooks/generic-delegator/useGasTokenSelection.ts (1)
  • useGasTokenSelection (9-84)
packages/vechain-kit/src/types/gasEstimation.ts (1)
  • EstimationResponse (36-43)
packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegator.ts (1)
  • estimateGas (17-30)
packages/vechain-kit/src/types/gasToken.ts (1)
  • GasTokenType (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint & Build
  • GitHub Check: e2e-test
  • GitHub Check: deploy
  • GitHub Check: build-size-check
🔇 Additional comments (1)
packages/vechain-kit/src/hooks/generic-delegator/index.ts (1)

1-4: Re-export of useGenericDelegatorFeeEstimation looks correct

The new barrel export aligns with the new hook implementation and lets consumers switch from useGasEstimation cleanly.

Comment on lines 23 to 32
const { connectedWallet, account } = useWallet();
const { data: smartAccount } = useSmartAccount(
connectedWallet?.address ?? '',
);
const { feeDelegation } = useVeChainKitConfig();
const { balances } = useTokenBalances(account?.address ?? '');
const { updatePreferences } = useGasTokenSelection();
// Only include essential data in query key to prevent unnecessary refetches
const queryKey = ['gas-estimation', JSON.stringify(clauses), JSON.stringify(tokens), sendingAmount, sendingTokenSymbol];

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Include signer/delegator context in the query key to avoid stale estimations

queryKey currently ignores the signer (smart account) and delegator URL, so gas estimations can be incorrectly reused when the connected wallet, smart account, or network changes while clauses/tokens stay the same. This can surface the wrong gas token or cost for a new account.

Consider folding the relevant context into the key (and keeping it simple rather than JSON.stringify-ing large objects):

-    // Only include essential data in query key to prevent unnecessary refetches
-    const queryKey = ['gas-estimation', JSON.stringify(clauses), JSON.stringify(tokens), sendingAmount, sendingTokenSymbol];
+    // Include signer and delegator context so estimations change when account/network changes
+    const queryKey = [
+        'gas-estimation',
+        smartAccount?.address,
+        feeDelegation?.genericDelegatorUrl,
+        tokens,
+        clauses,
+        sendingAmount,
+        sendingTokenSymbol,
+    ] as const;
@@
-        enabled: enabled && clauses.length > 0 && !!smartAccount?.address && !!feeDelegation?.genericDelegatorUrl && tokens.length > 0 && balances.length > 0,
+        enabled:
+            enabled &&
+            clauses.length > 0 &&
+            !!smartAccount?.address &&
+            !!feeDelegation?.genericDelegatorUrl &&
+            tokens.length > 0 &&
+            balances.length > 0,

Also, please confirm that useTokenBalances always returns balances as an array (defaulting to []), otherwise balances.length could throw. Based on learnings, conditional enabled guards are important to prevent unnecessary/invalid queries.

Also applies to: 35-37, 70-78

🤖 Prompt for AI Agents
packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts
lines 23-32 (and replicate same changes at 35-37 and 70-78): the query key
currently omits signer/smart-account and delegator context and uses
JSON.stringify on large objects; update the key to include simple identifiers
such as connectedWallet?.address, smartAccount?.address, and feeDelegation?.url
or id (avoid stringifying full objects), e.g. ['gas-estimation', clausesId or
clauses.length, tokensId or tokens.map(t=>t.symbol).join(','), sendingAmount,
sendingTokenSymbol, connectedWallet?.address, smartAccount?.address,
feeDelegation?.url]; also ensure useTokenBalances returns a safe default
(balances ?? []) or guard enabled checks with balances?.length to prevent
accessing length of undefined and add conditional enabled guards so queries only
run when wallet/smartAccount/delegator context is available.

Comment on lines 40 to 56
const estimation = await estimateGas(
smartAccount?.address ?? '',
feeDelegation?.genericDelegatorUrl ?? '',
clauses as TransactionClause[],
token as GasTokenType,
'medium',
);
// Check if user has enough balance for this token
const gasCost = estimation.transactionCost;
const tokenBalance = Number(balances.find(t => t.symbol === token)?.balance || 0);
// If sending the same token as gas token, need balance for both
// If no sendingAmount is provided, we're only checking for gas fees
const additionalAmount = (sendingAmount && sendingTokenSymbol && token === sendingTokenSymbol)
? Number(sendingAmount)
: 0;
const requiredBalance = gasCost + additionalAmount;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle missing/invalid transactionCost and avoid Number-based precision loss

EstimationResponse.transactionCost is optional; if it’s undefined, gasCost + additionalAmount becomes NaN, the balance check always fails, and the error message logs needs NaN, which is misleading. On top of that, Number(...) may lose precision for large token balances or amounts.

A more defensive approach could be:

-                    const estimation = await estimateGas(
+                    const estimation = await estimateGas(
@@
-                    // Check if user has enough balance for this token
-                    const gasCost = estimation.transactionCost;
-                    const tokenBalance = Number(balances.find(t => t.symbol === token)?.balance || 0);
+                    // Check if user has enough balance for this token
+                    const gasCost = estimation.transactionCost ?? 0;
+                    if (!Number.isFinite(gasCost) || gasCost <= 0) {
+                        lastError = new Error(
+                            `Delegator returned invalid transactionCost for ${token}: ${gasCost}`,
+                        );
+                        continue;
+                    }
+
+                    // TODO: if balances/sendingAmount are big-int/wei strings, prefer a bignumber util instead of Number()
+                    const tokenBalance = Number(
+                        balances.find((t) => t.symbol === token)?.balance ?? 0,
+                    );
@@
-                    const additionalAmount = (sendingAmount && sendingTokenSymbol && token === sendingTokenSymbol) 
-                        ? Number(sendingAmount)
-                        : 0;
+                    const additionalAmount =
+                        sendingAmount &&
+                        sendingTokenSymbol &&
+                        token === sendingTokenSymbol
+                            ? Number(sendingAmount)
+                            : 0;
@@
-                    const requiredBalance = gasCost + additionalAmount;
+                    const requiredBalance = gasCost + additionalAmount;

If balances and amounts are in wei/large integer space, consider switching to a shared bigint/bignumber helper instead of Number() to avoid rounding issues; that can be a follow-up refactor.

Also applies to: 63-68

🤖 Prompt for AI Agents
In
packages/vechain-kit/src/hooks/generic-delegator/useGenericDelegatorFeeEstimation.ts
around lines 40 to 56 (also apply same fix to 63-68): guard against undefined
estimation.transactionCost and stop using Number() for monetary values; convert
transactionCost and balance/amount strings to a safe big integer/bignumber
representation (or BigInt) before arithmetic, treat a missing transactionCost
explicitly (either default to 0 with a clear log/flag or return early with an
error), compute requiredBalance using the big-int/bignumber types, and use that
same type to compare against token balances so you never produce NaN or incur
precision loss.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (2)

90-126: Consider using stable object references in dependency arrays.

The useCallback hooks reference individual methods like buildTransactionWithDelegation.sendTransaction in dependency arrays. While this works if the hooks return stable method references, it's more idiomatic to depend on the entire hook result object and trust React's memoization.

     const handleBuildTransactionDelegatedWithModal = useCallback(async () => {
         setCurrentModalType('buildTransactionWithDelegation');
         openTransactionModal();
         await (buildTransactionWithDelegation.sendTransaction({}));
-    }, [buildTransactionWithDelegation.sendTransaction, openTransactionModal]);
+    }, [buildTransactionWithDelegation, openTransactionModal]);

Apply similar changes to other callbacks (handleUseSendTransactionWithToast, handleSendTransactionDelegatedWithModal, retry handlers).


202-221: Misleading nullish coalescing fallback for modal props.

The ?? fallback (e.g., buildTransactionWithDelegation.status ?? buildTransactionNoDelegation.status) won't execute as expected because hook results like status are always defined (even if the value is 'idle' or similar). The fallback implies a safety net that doesn't actually work.

Since currentModalType already determines which flow is active, simplify by removing the fallback:

             status={
                 currentModalType === 'buildTransactionWithDelegation'
-                    ? buildTransactionWithDelegation.status ?? buildTransactionNoDelegation.status
-                    : sendTransactionWithDelegation.status ?? sendTransactionNoDelegation.status
+                    ? buildTransactionWithDelegation.status
+                    : sendTransactionWithDelegation.status
             }
             txReceipt={
                 currentModalType === 'buildTransactionWithDelegation'
-                    ? buildTransactionWithDelegation.txReceipt ?? buildTransactionNoDelegation.txReceipt
-                    : sendTransactionWithDelegation.txReceipt ?? sendTransactionNoDelegation.txReceipt
+                    ? buildTransactionWithDelegation.txReceipt
+                    : sendTransactionWithDelegation.txReceipt
             }
             txError={
                 currentModalType === 'buildTransactionWithDelegation'
-                    ? buildTransactionWithDelegation.error ?? buildTransactionNoDelegation.error
-                    : sendTransactionWithDelegation.error ?? sendTransactionNoDelegation.error
+                    ? buildTransactionWithDelegation.error
+                    : sendTransactionWithDelegation.error
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe329c and 625a82f.

📒 Files selected for processing (1)
  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : Always conditionally enable queries using the `enabled` property to prevent unnecessary contract calls, checking for all required parameters: `enabled: !!requiredParam && !!otherRequiredParam`

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
🪛 Biome (2.1.2)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx

[error] 33-33: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 47-47: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e-test
  • GitHub Check: Lint & Build
  • GitHub Check: build-size-check
  • GitHub Check: deploy

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (1)

32-61: Critical: Hooks called inside nested functions violate React's Rules of Hooks.

The callUseBuildTransaction and callUseSendTransaction functions wrap hook calls inside regular functions. This violates React's Rules of Hooks which require hooks to be called unconditionally at the top level of the component. React cannot statically verify the call order, leading to potential bugs and broken linting.

Refactor to call hooks directly at the top level. If you need to reduce duplication, extract the shared configuration into objects/constants rather than wrapping hooks:

-    const callUseBuildTransaction = (delegationUrl?: string) => {
-        return useBuildTransaction({
+    const buildTransactionConfig = {
         clauseBuilder: () => {
             if (!account?.address) return [];
-
             return clauses;
         },
         refetchQueryKeys: [],
         onSuccess: () => {},
         onFailure: () => {},
         suggestedMaxGas: undefined,
-        delegationUrl: delegationUrl,
-    });}
+    };
 
-    const callUseSendTransaction = (delegationUrl?: string) => {
-        return useSendTransaction({
-            signerAccountAddress: account?.address,
-            delegationUrl: delegationUrl,
-        });
-    }
+    const buildTransactionNoDelegation = useBuildTransaction({
+        ...buildTransactionConfig,
+        delegationUrl: undefined,
+    });
 
-    const buildTransactionNoDelegation = callUseBuildTransaction();
+    const delegationUrl = ""; // YOUR DELEGATION URL HERE
 
-    const delegationUrl = ""; // YOUR DELEGATION URL HERE
-    
-    const buildTransactionWithDelegation = callUseBuildTransaction(delegationUrl);
+    const buildTransactionWithDelegation = useBuildTransaction({
+        ...buildTransactionConfig,
+        delegationUrl: delegationUrl,
+    });
 
-    const sendTransactionNoDelegation = callUseSendTransaction();
+    const sendTransactionNoDelegation = useSendTransaction({
+        signerAccountAddress: account?.address,
+        delegationUrl: undefined,
+    });
 
-    const sendTransactionWithDelegation = callUseSendTransaction();
+    const sendTransactionWithDelegation = useSendTransaction({
+        signerAccountAddress: account?.address,
+        delegationUrl: delegationUrl,
+    });
🧹 Nitpick comments (2)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (2)

84-88: Non-idiomatic dependency array: prefer whole object over method reference.

Referencing buildTransactionWithDelegation.sendTransaction in the dependency array can cause issues with referential stability. React's ESLint plugin typically warns against this pattern.

     const handleBuildTransactionDelegatedWithModal = useCallback(async () => {
         setCurrentModalType('useBuildTxWithDelegation');
         openTransactionModal();
         await (buildTransactionWithDelegation.sendTransaction({}));
-    }, [buildTransactionWithDelegation.sendTransaction, openTransactionModal]);
+    }, [buildTransactionWithDelegation, openTransactionModal]);

Apply the same pattern to other callbacks at lines 90-94, 96-100, 102-105, 112-115.


55-55: Placeholder delegation URL needs attention.

The empty string for delegationUrl means the "delegated" transaction flows won't actually perform fee delegation. Ensure this is intentional for demo purposes and consider adding a more prominent comment or TODO to guide developers.

-    const delegationUrl = ""; // YOUR DELEGATION URL HERE
+    // TODO: Replace with your actual delegation endpoint URL to enable fee delegation
+    // Example: "https://sponsor.vechain.energy/by/123"
+    const delegationUrl = ""; // YOUR DELEGATION URL HERE
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 625a82f and 4aeb7b9.

📒 Files selected for processing (1)
  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
🧠 Learnings (3)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For multiple parallel contract calls, use the `executeMultipleClausesCall` utility wrapped in a `useQuery` hook with the pattern: define query key function, use `executeMultipleClausesCall` in queryFn mapping items to clause objects, and transform results

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For single contract read operations, use the `useCallClause` hook with the pattern: import dependencies, define ABI and method as const, create query key function using `getCallClauseQueryKeyWithArgs`, and wrap with useCallClause including data transformation in the select option

Applied to files:

  • examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
🪛 Biome (2.1.2)
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx

[error] 33-33: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 47-47: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint & Build
  • GitHub Check: build-size-check
  • GitHub Check: e2e-test
  • GitHub Check: deploy


const sendTransactionNoDelegation = callUseSendTransaction();

const sendTransactionWithDelegation = callUseSendTransaction();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: sendTransactionWithDelegation is missing the delegation URL.

callUseSendTransaction() is called without the delegationUrl argument, so sendTransactionWithDelegation is functionally identical to sendTransactionNoDelegation. The delegation URL should be passed for this transaction to use fee delegation.

-    const sendTransactionWithDelegation = callUseSendTransaction();
+    const sendTransactionWithDelegation = callUseSendTransaction(delegationUrl);
🤖 Prompt for AI Agents
In
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
around line 61, the callUseSendTransaction() invocation for
sendTransactionWithDelegation is missing the delegationUrl argument so it
behaves the same as the no-delegation variant; update the call to pass the
proper delegation URL (e.g., callUseSendTransaction(delegationUrl) or the
correct variable/constant in scope) so this instance uses fee delegation.

Comment on lines 134 to +149
<Button
onClick={handleTransactionWithToast}
isLoading={isTransactionPending}
isDisabled={isTransactionPending}
isLoading={buildTransactionNoDelegation.isTransactionPending}
isDisabled={buildTransactionNoDelegation.isTransactionPending}
data-testid="tx-with-toast-button"
>
useBuildTransaction with toast (no delegation)
</Button>
<Button
onClick={handleBuildTransactionDelegatedWithModal}
isLoading={buildTransactionWithDelegation.isTransactionPending}
isDisabled={buildTransactionWithDelegation.isTransactionPending}
data-testid="tx-with-modal-button"
>
useBuildTransaction with modal (delegated)
</Button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Duplicate data-testid values across buttons.

Lines 138 and 146 both use data-testid="tx-with-toast-button" and data-testid="tx-with-modal-button" respectively, which are also used in lines 156 and 164. This will cause ambiguity in test selectors.

                     <Button
                         onClick={handleTransactionWithToast}
                         isLoading={buildTransactionNoDelegation.isTransactionPending}
                         isDisabled={buildTransactionNoDelegation.isTransactionPending}
-                        data-testid="tx-with-toast-button"
+                        data-testid="build-tx-with-toast-button"
                     >
                         useBuildTransaction with toast (no delegation)
                     </Button>
                     <Button
                         onClick={handleBuildTransactionDelegatedWithModal}
                         isLoading={buildTransactionWithDelegation.isTransactionPending}
                         isDisabled={buildTransactionWithDelegation.isTransactionPending}
-                        data-testid="tx-with-modal-button"
+                        data-testid="build-tx-with-modal-button"
                     >
                         useBuildTransaction with modal (delegated)
                     </Button>

Similarly for lines 152-167:

                     <Button
                         onClick={handleUseSendTransactionWithToast}
                         isLoading={sendTransactionNoDelegation.isTransactionPending}
                         isDisabled={sendTransactionNoDelegation.isTransactionPending}
-                        data-testid="tx-with-toast-button"
+                        data-testid="send-tx-with-toast-button"
                     >
                         useSendTransaction with toast (no delegation)
                     </Button>
                     <Button
                         onClick={handleSendTransactionDelegatedWithModal}
                         isLoading={sendTransactionWithDelegation.isTransactionPending}
                         isDisabled={sendTransactionWithDelegation.isTransactionPending}
-                        data-testid="tx-with-modal-button"
+                        data-testid="send-tx-with-modal-button"
                     >
                         useSendTransaction with modal (delegated)
                     </Button>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
around lines 134 to 149 (and similarly for lines 152 to 167), multiple Button
elements reuse the same data-testid values causing ambiguous test selectors;
change each Button to have a unique data-testid (e.g., append a descriptive
suffix like -no-delegation, -delegated, -with-modal, -with-toast or include
index/context) so every test id is unique and then update any tests that assert
on the old ids to use the new unique ids.

Comment on lines +203 to +217
status={
currentModalType === 'useBuildTxWithDelegation'
? buildTransactionWithDelegation.status ?? buildTransactionNoDelegation.status
: sendTransactionWithDelegation.status ?? sendTransactionNoDelegation.status
}
txReceipt={
currentModalType === 'useBuildTxWithDelegation'
? buildTransactionWithDelegation.txReceipt ?? buildTransactionNoDelegation.txReceipt
: sendTransactionWithDelegation.txReceipt ?? sendTransactionNoDelegation.txReceipt
}
txError={
currentModalType === 'useBuildTxWithDelegation'
? buildTransactionWithDelegation.error ?? buildTransactionNoDelegation.error
: sendTransactionWithDelegation.error ?? sendTransactionNoDelegation.error
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fallback logic with ?? may not work as intended for status values.

The ?? operator only falls back when the left operand is null or undefined. If status is a defined string like 'idle' or 'error', the fallback won't trigger. Since currentModalType already determines which flow is active, the fallback to buildTransactionNoDelegation / sendTransactionNoDelegation seems unintentional and could display stale state from a different transaction.

Consider removing the fallback or using explicit conditional logic:

                 status={
                     currentModalType === 'useBuildTxWithDelegation'
-                        ? buildTransactionWithDelegation.status ?? buildTransactionNoDelegation.status
-                        : sendTransactionWithDelegation.status ?? sendTransactionNoDelegation.status
+                        ? buildTransactionWithDelegation.status
+                        : sendTransactionWithDelegation.status
                 }
                 txReceipt={
                     currentModalType === 'useBuildTxWithDelegation'
-                        ? buildTransactionWithDelegation.txReceipt ?? buildTransactionNoDelegation.txReceipt
-                        : sendTransactionWithDelegation.txReceipt ?? sendTransactionNoDelegation.txReceipt
+                        ? buildTransactionWithDelegation.txReceipt
+                        : sendTransactionWithDelegation.txReceipt
                 }
                 txError={
                     currentModalType === 'useBuildTxWithDelegation'
-                        ? buildTransactionWithDelegation.error ?? buildTransactionNoDelegation.error
-                        : sendTransactionWithDelegation.error ?? sendTransactionNoDelegation.error
+                        ? buildTransactionWithDelegation.error
+                        : sendTransactionWithDelegation.error
                 }
🤖 Prompt for AI Agents
In
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
around lines 203 to 217, the code uses the nullish coalescing operator (??) to
fall back to the other transaction object’s fields, which will not trigger for
defined status strings (e.g., 'idle' or 'error') and can surface stale state
from the non-active flow; remove the fallback and select fields exclusively
based on currentModalType (i.e., return
buildTransactionWithDelegation.status/txReceipt/error when currentModalType ===
'useBuildTxWithDelegation', otherwise return sendTransactionWithDelegation.*),
or if you must keep a fallback only use an explicit undefined/null check (e.g.,
status === undefined || status === null ? other.status : status) so only truly
missing values fall back.

@Agilulfo1820 Agilulfo1820 merged commit 9f592ef into main Dec 11, 2025
9 checks passed
@Agilulfo1820 Agilulfo1820 deleted the mike/sponsor-specific-txs branch December 11, 2025 16:21
This was referenced Dec 12, 2025
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.

💡 [REQUEST] - Add native support for Fee Delegation in the transaction hooks

4 participants