-
Notifications
You must be signed in to change notification settings - Fork 8
Allow once-off sponsored transactions #550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds optional dApp-sponsored transaction support by introducing Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as TransactionExamples
participant Build as useBuildTransaction
participant Send as useSendTransaction
participant Provider as PrivyWalletProvider
participant Delegator as GasPayerService
participant Privy as Privy Service
User->>UI: trigger transaction
UI->>Build: buildTransactionWithDAppSponsored(dAppSponsoredUrl?)
Build->>Send: prepare send config (includes dAppSponsoredUrl)
UI->>Send: sendTransaction(clauses, dAppSponsoredUrl, privyUIOptions?)
Send->>Provider: sendTransaction(payload includes dAppSponsoredUrl)
Provider->>Provider: evaluate dAppSponsoredUrl
alt dAppSponsoredUrl provided
Provider->>Delegator: use provided dAppSponsoredUrl as gas-payer endpoint
else
Provider->>Delegator: use generic delegator endpoint
end
Delegator->>Privy: sign & submit (sponsor fees)
Privy->>Provider: tx result
Provider->>UI: return tx result (status/receipt/error)
UI->>User: display result / allow retry
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
🚀 Preview environment deployed!Preview URL: https://preview.vechainkit.vechain.org/mikesponsor-specific-txs |
|
Size Change: +3.89 kB (+0.07%) Total Size: 5.39 MB
ℹ️ View Unchanged
|
There was a problem hiding this 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-commentcan update existing deployment comments. The action handles both scenarios: ifcomment-idis 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:dAppSponsoredUrlparameter 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 andbuildTransactionBodyis called with only anisDelegatedflag, 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 unnecessarydAppSponsoredUrlparameter fromuseSendTransactionhook props.The
dAppSponsoredUrlparameter 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. ThesendTransactionAdapterfunction accepts and usesdAppSponsoredUrlas a call-time parameter instead. In useBuildTransaction, passingdAppSponsoredUrltouseSendTransactionat 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 fordAppSponsoredUrlis commented out.The
dAppSponsoredUrlis commented out, which means this demo doesn't actually exercise the sponsored transaction flow. Consider either:
- Adding a placeholder URL that developers can see in action, or
- 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
📒 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, useuseThorinstead ofuseConnexfor contract interactions
For single contract read operations, use theuseCallClausehook with the pattern: import dependencies, define ABI and method as const, create query key function usinggetCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use theexecuteMultipleClausesCallutility wrapped in auseQueryhook with the pattern: define query key function, useexecuteMultipleClausesCallin queryFn mapping items to clause objects, and transform results
For transaction building and sending, use theuseBuildTransactionhook 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 theenabledproperty to prevent unnecessary contract calls, checking for all required parameters:enabled: !!requiredParam && !!otherRequiredParam
Use theselectoption 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: usegetCallClauseQueryKeyWithArgsfor contract calls with arguments andgetCallClauseQueryKeyfor calls without arguments to ensure proper caching and invalidation
Use TypeScriptas constassertions for method names andas0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions
Files:
packages/vechain-kit/src/hooks/thor/transactions/useBuildTransaction.tsexamples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsxpackages/vechain-kit/src/providers/PrivyWalletProvider.tsxpackages/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.tsexamples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsxpackages/vechain-kit/src/providers/PrivyWalletProvider.tsxpackages/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.tspackages/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.tspackages/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 ofpull_request_targettrigger.The switch from
pull_requesttopull_request_targetchanges 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 forBRANCH_NAME.Moving
BRANCH_NAMEfrom 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 whendAppSponsoredUrlis provided.The condition
genericDelegator && !dAppSponsoredUrlensures 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:signAndSendcorrectly prioritizesdAppSponsoredUrl.The
dAppSponsoredUrl ?? delegatorUrlfallback 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 forwarddAppSponsoredUrl.The
sendTransactionAdapternow accepts_dAppSponsoredUrlas a second parameter and forwards it to the internalsendTransaction. This enables callers to specify a per-transaction sponsor URL.
52-60: Public API correctly updated fordAppSponsoredUrlparameter.The
UseSendTransactionReturnValue.sendTransactionsignature now includesdAppSponsoredUrlas an optional second parameter, aligning with the implementation and enabling consumers to pass the sponsor URL per transaction.
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
Outdated
Show resolved
Hide resolved
examples/next-template/src/app/components/features/TransactionExamples/TransactionExamples.tsx
Outdated
Show resolved
Hide resolved
…n-kit into mike/sponsor-specific-txs
There was a problem hiding this 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
buildTransactionWithDAppSponsoredhook duplicates the entire configuration from the firstuseBuildTransactionhook (lines 22-46), differing only in the commented-outdAppSponsoredUrl. While this duplication may aid clarity in a demo, extracting the commonclauseBuilderfunction 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
buildTransactionWithDAppSponsoredis always returned byuseBuildTransaction, itsisTransactionPendingproperty 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, andonTryAgainare likely unnecessary sincebuildTransactionWithDAppSponsoredis 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
📒 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, useuseThorinstead ofuseConnexfor contract interactions
For single contract read operations, use theuseCallClausehook with the pattern: import dependencies, define ABI and method as const, create query key function usinggetCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use theexecuteMultipleClausesCallutility wrapped in auseQueryhook with the pattern: define query key function, useexecuteMultipleClausesCallin queryFn mapping items to clause objects, and transform results
For transaction building and sending, use theuseBuildTransactionhook 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 theenabledproperty to prevent unnecessary contract calls, checking for all required parameters:enabled: !!requiredParam && !!otherRequiredParam
Use theselectoption 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: usegetCallClauseQueryKeyWithArgsfor contract calls with arguments andgetCallClauseQueryKeyfor calls without arguments to ensure proper caching and invalidation
Use TypeScriptas constassertions for method names andas0x${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.
useCallbackis 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.
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
dAppSponsoredUrlparameter 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
dAppSponsoredUrlparameter touseBuildTransaction,useSendTransaction, and thePrivyWalletProviderContextType, 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
TransactionExamplescomponent 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.yamlworkflow, 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_requesttopull_request_targetfor 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
Chores
✏️ Tip: You can customize this high-level summary in your review settings.