feat: switch fiat onramp to Transak (native VET support) - #649
Conversation
- Add useBuyCrypto, useFiatCheckout, useSubscription, useSubscriptionCheckout hooks - Add FiatCheckoutModal, PayWithFiatButton, SubscribeButton, SubscriptionCheckoutModal components - Add FiatOnramp content in AccountModal - Add subscription demo in PaymentsDemo example - Add crypto payment method to SubscriptionCheckout (VET/ERC20) - Extend SubscriptionPlan type with optional cryptoPayment field - Add i18n translation keys for fiat/crypto payments - Fix Privy v3 compatibility (optional clientId, cross-app-connect deps) - Mock fallback for useSubscription when API base URL not configured - Add Solana deps to root workspace (Privy v3 transitive)
…port) BREAKS: Replaces useBuyCrypto, useFiatCheckout, FiatCheckoutModal, PayWithFiatButton, FiatOnrampContent with Transak equivalents. Adds: - useTransakCheckout hook (dual-mode widget URL: apiKey-only for sandbox, widgetUrlBuilder for production) - TransakCheckoutModal, PayWithTransakButton components - TransakOnrampContent in AccountModal - @transak/ui-js-sdk dependency - transak config on VeChainKitProvider - Subscription fiat path opens Transak to fund, then creates sub Removes: - Privy useAddFunds / useBuyCrypto dependency - @stripe/crypto peer dep - All Privy/Stripe-specific translations
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis change adds Transak on-ramp support across VeChain Kit, the account modal, the example apps, and the Next.js backend route that creates secure widget URLs. ChangesTransak payment integration
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant PayWithTransakButton
participant useTransakCheckout
participant NextRoute
participant TransakAPI
PayWithTransakButton->>useTransakCheckout: Start checkout
useTransakCheckout->>NextRoute: POST widget parameters
NextRoute->>TransakAPI: Refresh token and create widget session
TransakAPI-->>NextRoute: Return secure widget URL
NextRoute-->>useTransakCheckout: Return widget URL
useTransakCheckout->>TransakAPI: Initialize embedded checkout
TransakAPI-->>PayWithTransakButton: Report success or error
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 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 |
- PayWithTransakButton: isOpen stays true on 'success' so the success screen is visible; only closes when user dismisses - useSubscriptionCheckout: removed unreachable createSubscription fallthrough (was only reachable for crypto-without-cryptoPayment, an impossible UI state); replaced with a clear error
Without this, the Buy button rendered unconditionally and only threw 'Transak is not configured' after the user filled the form and clicked Continue. Now it stays hidden unless the developer passes a transak config to VeChainKitProvider.
When the developer does not pass a Transak API key to VeChainKitProvider, kit-internal UI no longer surfaces fiat-only options: - useSubscriptionCheckout: derives hasFiat from config; default paymentMethod for non-crypto plans is 'crypto' when fiat is unavailable - SubscriptionCheckoutModal: 'Pay with Card' tab hidden unless hasFiat; plans with neither crypto nor fiat show an info note instead of the Subscribe button - en.json: 'No payment method available for this plan.' key
Transak deprecated direct widget URLs (apiKey in the query string); widgets now only load from a sessionId-based URL minted by the Create Widget URL API (POST /api/v2/auth/session), called from the partner backend with the API secret. - TransakConfig.widgetUrlBuilder is now required; apiKey optional - useTransakCheckout: removed buildDirectUrl fallback, errors clearly when widgetUrlBuilder is missing - hasFiat / Buy quick-action gating now checks widgetUrlBuilder - example: /api/transak/widget-url route does refresh-token -> create-widget-url with x-user-ip; wrapper wires widgetUrlBuilder - .env.example: TRANSAK_API_KEY / TRANSAK_API_SECRET server vars
The Transak SDK's default behavior appends its own full-screen overlay to document.body, outside the Chakra modal. Chakra's focus trap then detects focus escaping the modal and pulls it back whenever the user clicks into Transak's inputs, causing the email field to constantly lose focus. - useTransakCheckout: pass containerId so the widget renders as an iframe inside the host modal; expose TRANSAK_WIDGET_CONTAINER_ID; replace the SDK's close() with cleanup() (which actually removes the iframe in container mode) and clear the container on open/close/unmount - TransakCheckoutModal: render the embed container during processing and disable the modal focus trap (allowExternalFocus) while the widget is active
- Demo/button/hook/modal defaults changed from $50 to $20 (Transak's minimum card order), so sandbox testing is cheaper - TransakCheckoutModal: widget container height is now viewport-aware (calc(100vh - 240px), 420-620px) and mobile uses a near-fullscreen bottom sheet, so the widget's own scroll is the only scrollbar instead of a double scrollbar (modal + iframe)
Transak's minimum order with light KYC is $10; the demo and component defaults now use it instead of $20.
…onnect Privy config
|
Size Change: +96.9 kB (+1.09%) Total Size: 9.02 MB
ℹ️ View Unchanged
|
There was a problem hiding this comment.
Actionable comments posted: 19
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (7)
packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx-58-64 (1)
58-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winValidate the fiat amount before opening checkout.
Line 61 passes raw input to
widgetUrlBuilder. Theminattribute does not enforce validation before this call. Reject non-finite values and values below the Transak minimum before setting the processing state.As per coding guidelines, validate inputs in transaction and contract interaction functions and ensure amounts are positive.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx` around lines 58 - 64, Update handleBuy to parse and validate the fiat amount before calling startCheckout or setting the processing step; reject non-finite, non-positive values and values below the Transak minimum, then pass the validated amount to startCheckout.Source: Coding guidelines
docs/recipes/stripe-subscriptions.md-5-11 (1)
5-11: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the endpoint count and add a language to the fenced block.
Line 8 states "Implement the three endpoints below". Four endpoints are documented:
GET /api/subscriptions/plans,POST /api/subscriptions,GET /api/subscriptions/current, andDELETE /api/subscriptions/:id.markdownlint reports MD040 for the fenced block at line 103. Add a language identifier.
🛠️ Proposed fix
1. Install Stripe Node.js SDK: `npm install stripe` -2. Implement the three endpoints below +2. Implement the four endpoints below 3. Configure webhooks for billing events-``` +```http Authorization: Bearer <privy_access_token> ```Also applies to: 103-105
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/recipes/stripe-subscriptions.md` around lines 5 - 11, Update the Quick Start text to say four endpoints, and add the http language identifier to the fenced code block near the subscription request example so it satisfies Markdown linting.Source: Linters/SAST tools
packages/vechain-kit/src/languages/en.json-512-518 (1)
512-518: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRemove the duplicate translation keys.
Two keys are declared twice in this block. Biome reports both as
lint/suspicious/noDuplicateObjectKeyserrors, so the lint step fails.
"Purchase failed"at line 513 and line 537."Please complete the purchase in the Transak window."at line 517 and line 542.JSON parsing keeps the last occurrence, so the earlier entries are dead. Delete one occurrence of each key.
🛠️ Proposed fix
"VET purchased successfully": "VET purchased successfully", - "Purchase failed": "Purchase failed", "Buy VET with your preferred payment method. Powered by Transak.": "Buy VET with your preferred payment method. Powered by Transak.", "Your VET will arrive in your wallet shortly.": "Your VET will arrive in your wallet shortly.", - "Please complete the purchase in the Transak window.": "Please complete the purchase in the Transak window.", "Processing...": "Processing...",Also applies to: 536-542
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/languages/en.json` around lines 512 - 518, Remove one duplicate declaration of each identified key, “Purchase failed” and “Please complete the purchase in the Transak window.”, from the language resource while preserving one occurrence and its value for each key.Source: Linters/SAST tools
docs/recipes/stripe-subscriptions.md-1-3 (1)
1-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThe documentation does not cover the flows this PR ships.
This PR replaces the Stripe fiat onramp with Transak and removes the
@stripe/cryptopeer dependency. This new document is titled "Stripe Subscriptions Backend API", and its only payment example is the Stripe PaymentMethod idpm_card_visa(line 41).Line 3 names
useSubscriptionandSubscriptionModalas the consumers. It does not mentionuseSubscriptionCheckoutorSubscriptionCheckoutModal, which this PR adds and which drive the actual fiat and crypto payments. A backend author following this document does not learn that:
- The fiat charge happens in the Transak widget before
POST /api/subscriptionsruns, so the endpoint records an already-completed payment rather than charging a card.- The crypto path never calls the backend at all. It builds client-side state only.
Retitle and extend the document to describe the Transak and crypto paths, or state clearly that it covers a Stripe-only backend that this PR no longer wires up.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/recipes/stripe-subscriptions.md` around lines 1 - 3, Update the Stripe subscriptions documentation to match the shipped payment flows: retitle it or clearly scope it as legacy Stripe-only, and document the useSubscriptionCheckout and SubscriptionCheckoutModal paths. Explain that Transak completes the fiat charge before POST /api/subscriptions records it, while the crypto flow remains entirely client-side and does not call the backend.packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts-44-52 (1)
44-52: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winBoth doc comments describe the behavior incorrectly.
Line 44 states that
transakOpenis "Re-initialized every render". It is not.useTransakCheckoutreturnsopenas auseCallbackmemoized on[transakConfig, account?.address](useTransakCheckout.tsline 178).Lines 50-51 state that
hasFiatis true "when a Transak API key is configured". Line 78 computes it fromtransakConfig?.widgetUrlBuilder, which is a function, not an API key.These comments sit on an exported public type, so consumers read them.
🛠️ Proposed fix
- /** Re-initialized every render — call to open Transak widget for fiat funding */ + /** Opens the Transak widget for fiat funding. */ transakOpen: (params?: { fiatAmount?: string; fiatCurrency?: string; }) => void; transakStatus: 'idle' | 'processing' | 'success' | 'error'; - /** True when a Transak API key is configured on VeChainKitProvider. UI uses - * this to decide whether to surface the "Pay with Card" option. */ + /** True when `transak.widgetUrlBuilder` is configured on VeChainKitProvider. + * UI uses this to decide whether to surface the "Pay with Card" option. */ hasFiat: boolean;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts` around lines 44 - 52, Update the exported type comments for transakOpen and hasFiat to accurately describe their implementations: state that transakOpen is a memoized callback whose dependencies are transakConfig and account address, and that hasFiat indicates whether transakConfig.widgetUrlBuilder is configured. Do not refer to per-render reinitialization or a Transak API key.packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx-101-114 (1)
101-114: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winDefer the empty-plan loading view during checkout.
isLoadingincludesisTransactionPending, and the crypto checkout path setsstatus === 'processing'before awaiting confirmation. If this guard renders withavailablePlans.length === 0, it replaces the inline wallet confirmation view with the generic spinner. Keep loading states for in-progress transactions understatus === 'processing', or excludestatus === 'processing'from this early return.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx` around lines 101 - 114, The early loading return in SubscriptionCheckoutModal must not replace the inline wallet confirmation while a transaction is processing. Update the isLoading/availablePlans guard to exclude status === 'processing' (or otherwise defer that state), while preserving the existing generic spinner for other loading states with no available plans.packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx-244-255 (1)
244-255: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winUse separate Transak widget containers when
TransakCheckoutModalandSubscriptionCheckoutModalcan render together.
TRANSAK_WIDGET_CONTAINER_IDis shared by both modals, butuseTransakCheckoutcreates the instance withcontainerId: TRANSAK_WIDGET_CONTAINER_ID. If both modals are mounted at the same time, duplicate IDs make the SDK target only one container idempotently while the other renders a duplicate DOM id.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx` around lines 244 - 255, Update the Transak container usage in SubscriptionCheckoutModal and its corresponding useTransakCheckout configuration to use a subscription-specific container ID, distinct from TransakCheckoutModal’s TRANSAK_WIDGET_CONTAINER_ID. Ensure the rendered Box id and SDK containerId reference the same new identifier while preserving the existing processing/fiat flow.
🧹 Nitpick comments (3)
examples/next-template/.env.example (1)
8-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFix dotenv-linter warnings: key order and missing trailing newline.
dotenv-linterflagsNEXT_PUBLIC_NETWORK_TYPEandNEXT_PUBLIC_TRANSAK_API_KEYas out of alphabetical order, and this file has no blank line at the end.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/next-template/.env.example` around lines 8 - 13, Update the variable ordering in the environment template so NEXT_PUBLIC_TRANSAK_API_KEY precedes NEXT_PUBLIC_NETWORK_TYPE alphabetically, and ensure the file ends with a trailing newline.Source: Linters/SAST tools
packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx (1)
25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse configured path aliases for the new component imports.
These added relative imports bypass the project import convention.
packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx#L25-L25: replace the relativeTypesimport with its@components/...alias.packages/vechain-kit/src/components/AccountModal/Types/Types.ts#L27-L27: replace the relativeTransakOnrampContentPropsimport with its@components/...alias.packages/vechain-kit/src/components/AccountModal/AccountModal.tsx#L42-L42: replace the relativeTransakOnrampContentimport with its@components/...alias.As per coding guidelines, use path aliases for imports:
@/*for src root,@hooksfor hooks,@componentsfor components, and@utilsfor utils.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx` at line 25, Replace the relative imports in TransakOnrampContent.tsx (line 25), Types.ts (line 27), and AccountModal.tsx (line 42) with the configured `@components` path aliases, preserving the existing imported symbols and behavior.Source: Coding guidelines
packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts (1)
83-83: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueDerive the default payment method from
hasFiat.The initial state at line 83 and
resetat line 193 both setpaymentMethodto'fiat'unconditionally. Whentransak.widgetUrlBuilderis not configured,hasFiatis false and the default is an unavailable method. Ifsubscriberuns before a plan is selected, it enters the fiat branch and fails insideuseTransakCheckoutwith "Transak is not configured".
selectPlanandopenalready apply thehasFiatfallback. Apply the same rule to the initial and reset values.♻️ Proposed refactor
- const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>('fiat'); + const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>( + hasFiat ? 'fiat' : 'crypto', + ); @@ - setPaymentMethod('fiat'); + setPaymentMethod(hasFiat ? 'fiat' : 'crypto'); setCryptoSubscription(null); - }, []); + }, [hasFiat]);Also applies to: 189-195
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts` at line 83, Update the initial state in useSubscriptionCheckout and its reset logic to choose 'fiat' only when hasFiat is true, otherwise default to the existing non-fiat payment method used by selectPlan and open. Keep the current selectPlan and open fallback behavior consistent across initialization and reset.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/next-template/src/app/api/transak/widget-url/route.ts`:
- Around line 42-64: Protect the POST handler with authentication or an
effective rate limit before processing widgetParams or creating a Transak
session. Reuse the application's existing auth/rate-limiting mechanism and
reject unauthorized or over-limit requests without invoking the server-backed
Transak credentials.
- Around line 20-28: Update both outbound fetch calls to REFRESH_TOKEN_URL and
CREATE_WIDGET_URL in the route handler to enforce an explicit request timeout
using the project’s existing fetch timeout pattern or an AbortController-based
signal. Apply the same timeout behavior consistently to both calls and preserve
their current request handling and failure flow.
In
`@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx`:
- Around line 53-64: Update the TransakOnrampContent flow around
useTransakCheckout and the status value so that when status becomes idle after
the widget closes, step returns to the form state instead of remaining
processing. Preserve the existing success and error transitions and checkout
parameters.
In `@packages/vechain-kit/src/components/SubscriptionModal/SubscriptionModal.tsx`:
- Around line 44-49: Update the SubscriptionModal rendering condition to use
currentSubscription alone rather than requiring activePlan, so subscriptions
with unknown or archived plan IDs remain in the current-subscription section.
When displaying the plan name, use activePlan’s name when available and fall
back to currentSubscription.planId.
- Around line 88-102: Update SubscriptionModal’s loading, error, and main return
branches to follow SubscriptionCheckoutModal’s pattern: read darkMode and theme
from useVeChainKitConfig(), wrap each returned modal tree in
VechainKitThemeProvider with those values, and replace raw Chakra Modal usage
with BaseModal while preserving existing modal content, callbacks, and sizing.
- Around line 104-125: Update SubscriptionModal’s error branch to provide a
retry action using the hook’s refresh function: destructure refresh from
useSubscription, add a Retry button that invokes refresh alongside the existing
Close action, and ensure the error screen remains limited to load-failure
handling rather than permanently replacing the plan-selection flow after
createSubscription or cancelSubscription errors.
- Around line 51-67: Remove the direct createSubscription call from
handleSelectPlan in SubscriptionModal; route plan selection through
useSubscriptionCheckout and the supported Transak/crypto checkout flow, or
remove SubscriptionModal’s standalone export if
SubscriptionCheckoutModal/SubscribeButton are the only supported entry points.
Ensure no subscription is created without a valid payment instrument.
In `@packages/vechain-kit/src/hooks/payments/useSubscription.ts`:
- Around line 110-111: Standardize the subscription payment field contract
across useSubscription, useSubscriptionCheckout, SubscriptionModal, and the
Stripe subscription documentation. Since callers currently pass provider names,
rename paymentMethodId to paymentProvider and update the request body, types,
and all call sites so the backend receives provider values consistently.
- Around line 75-90: The useSubscription hook’s subscription requests lack
authentication and conflate unauthorized responses with no subscription. Add or
reuse an access-token provider in the subscription configuration, obtain the
token before each fetch, and attach Authorization: Bearer <token> to all four
requests; handle 401 distinctly from 404 while preserving the existing null
behavior for an actual missing subscription.
- Around line 66-72: Update the fetchPlans error path in useSubscription so
failures are exposed through the hook’s error state or rethrown for refresh to
handle, instead of only being logged and swallowed. Preserve the loading cleanup
and ensure SubscriptionModal consumers receive a non-null error when fetching
plans fails.
- Around line 104-108: Update the useEffect containing refresh in
useSubscription so the initial refresh runs even when apiBaseUrl is unset,
allowing fetchPlans to apply the fallback plans and populate availablePlans;
preserve the existing dependency handling.
In `@packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts`:
- Around line 92-96: Update the useTransakCheckout destructuring in
useSubscriptionCheckout to retain its close function, then invoke that Transak
close operation from the local close handler before or alongside onClose and
state reset. Preserve the existing modal-close behavior while ensuring the
widget iframe is removed when the subscription checkout closes.
- Around line 163-177: Update
packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts lines 163-177
in the onTxConfirmed callback to persist the confirmed on-chain payment through
the backend using the transaction id, rather than creating a client-generated
UserSubscription and storing it only via setCryptoSubscription; derive any
period end from plan.interval. Update
packages/vechain-kit/src/hooks/payments/useSubscription.ts lines 116-130 to
derive the mock period from the selected plan interval and require an explicit
opt-in flag before entering the mock branch when apiBaseUrl is absent.
- Around line 55-62: Replace the manual ERC-20 encoding in encodeTransferData
with viem encodeFunctionData or an existing contract factory ABI, removing
ERC20_TRANSFER_SELECTOR and manual padding. In the subscription checkout flow,
validate recipientAddress and tokenAddress with isAddress(), and reject amount
values that are not positive using BigInt comparisons before constructing or
submitting the transaction.
- Around line 98-117: Update the no-plan guard in the checkout callback using
planRef.current so it records an error, sets status to error, and invokes the
current onError callback instead of returning silently. Include the completed
Transak orderId in the error log for reconciliation, using the order context
available to this callback, while preserving the existing subscription flow when
a plan is selected.
- Around line 257-262: Update the fiat branch in the subscription checkout flow
to pass plan.currency to transakOpen, return immediately for zero-amount plans
without opening Transak, and reject amounts below the provider’s $10 minimum
with a clear error before invoking transakOpen. Preserve the existing checkout
behavior for valid fiat amounts.
- Around line 242-255: Guard the crypto payment branch in subscribe before
calling sendTransaction by requiring account?.address in addition to
paymentMethod === 'crypto' and plan.cryptoPayment; update subscribe’s dependency
array to include account?.address so the callback reflects wallet connectivity,
and preserve the existing error handling for valid crypto clauses.
In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts`:
- Around line 115-122: Update the walletAddress validation in the checkout flow
before it reaches widgetUrlBuilder: retain the missing-address error handling
and additionally reject malformed addresses using the existing isAddress
utility. For invalid values, set the error status, invoke onErrorRef.current,
and return without building the widget URL.
- Around line 99-181: Guard the instanceRef assignment in open with the same
generation check used by the setupTransak callbacks: assign the created instance
only when genRef.current still equals the local gen. If the call is stale, do
not overwrite the active instance reference; preserve the existing callback
guards and setup flow.
---
Minor comments:
In `@docs/recipes/stripe-subscriptions.md`:
- Around line 5-11: Update the Quick Start text to say four endpoints, and add
the http language identifier to the fenced code block near the subscription
request example so it satisfies Markdown linting.
- Around line 1-3: Update the Stripe subscriptions documentation to match the
shipped payment flows: retitle it or clearly scope it as legacy Stripe-only, and
document the useSubscriptionCheckout and SubscriptionCheckoutModal paths.
Explain that Transak completes the fiat charge before POST /api/subscriptions
records it, while the crypto flow remains entirely client-side and does not call
the backend.
In
`@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx`:
- Around line 58-64: Update handleBuy to parse and validate the fiat amount
before calling startCheckout or setting the processing step; reject non-finite,
non-positive values and values below the Transak minimum, then pass the
validated amount to startCheckout.
In
`@packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx`:
- Around line 101-114: The early loading return in SubscriptionCheckoutModal
must not replace the inline wallet confirmation while a transaction is
processing. Update the isLoading/availablePlans guard to exclude status ===
'processing' (or otherwise defer that state), while preserving the existing
generic spinner for other loading states with no available plans.
- Around line 244-255: Update the Transak container usage in
SubscriptionCheckoutModal and its corresponding useTransakCheckout configuration
to use a subscription-specific container ID, distinct from
TransakCheckoutModal’s TRANSAK_WIDGET_CONTAINER_ID. Ensure the rendered Box id
and SDK containerId reference the same new identifier while preserving the
existing processing/fiat flow.
In `@packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts`:
- Around line 44-52: Update the exported type comments for transakOpen and
hasFiat to accurately describe their implementations: state that transakOpen is
a memoized callback whose dependencies are transakConfig and account address,
and that hasFiat indicates whether transakConfig.widgetUrlBuilder is configured.
Do not refer to per-render reinitialization or a Transak API key.
In `@packages/vechain-kit/src/languages/en.json`:
- Around line 512-518: Remove one duplicate declaration of each identified key,
“Purchase failed” and “Please complete the purchase in the Transak window.”,
from the language resource while preserving one occurrence and its value for
each key.
---
Nitpick comments:
In `@examples/next-template/.env.example`:
- Around line 8-13: Update the variable ordering in the environment template so
NEXT_PUBLIC_TRANSAK_API_KEY precedes NEXT_PUBLIC_NETWORK_TYPE alphabetically,
and ensure the file ends with a trailing newline.
In
`@packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx`:
- Line 25: Replace the relative imports in TransakOnrampContent.tsx (line 25),
Types.ts (line 27), and AccountModal.tsx (line 42) with the configured
`@components` path aliases, preserving the existing imported symbols and behavior.
In `@packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts`:
- Line 83: Update the initial state in useSubscriptionCheckout and its reset
logic to choose 'fiat' only when hasFiat is true, otherwise default to the
existing non-fiat payment method used by selectPlan and open. Keep the current
selectPlan and open fallback behavior consistent across initialization and
reset.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 82e53994-7ceb-461e-bed7-1076456c1754
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (38)
cross-app-connect/package.jsoncross-app-connect/src/app/providers/PrivyProviderWrapper.tsxdocs/recipes/stripe-subscriptions.mdexamples/next-template/.env.exampleexamples/next-template/src/app/api/transak/widget-url/route.tsexamples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsxexamples/next-template/src/app/components/features/PaymentsDemo/index.tsexamples/next-template/src/app/pages/Home.tsxexamples/next-template/src/app/providers/VechainKitProviderWrapper.tsxexamples/next-template/tsconfig.jsonpackage.jsonpackages/vechain-kit/package.jsonpackages/vechain-kit/src/components/AccountModal/AccountModal.tsxpackages/vechain-kit/src/components/AccountModal/Components/QuickActionsSection.tsxpackages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsxpackages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/index.tspackages/vechain-kit/src/components/AccountModal/Contents/index.tspackages/vechain-kit/src/components/AccountModal/Types/Types.tspackages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsxpackages/vechain-kit/src/components/PayWithTransakButton/index.tspackages/vechain-kit/src/components/SubscribeButton/SubscribeButton.tsxpackages/vechain-kit/src/components/SubscribeButton/index.tspackages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsxpackages/vechain-kit/src/components/SubscriptionCheckoutModal/index.tspackages/vechain-kit/src/components/SubscriptionModal/SubscriptionModal.tsxpackages/vechain-kit/src/components/SubscriptionModal/index.tspackages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsxpackages/vechain-kit/src/components/TransakCheckoutModal/index.tspackages/vechain-kit/src/components/index.tspackages/vechain-kit/src/hooks/index.tspackages/vechain-kit/src/hooks/payments/index.tspackages/vechain-kit/src/hooks/payments/useSubscription.tspackages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.tspackages/vechain-kit/src/hooks/payments/useTransakCheckout.tspackages/vechain-kit/src/index.tspackages/vechain-kit/src/languages/en.jsonpackages/vechain-kit/src/providers/VeChainKitProvider.tsxpackages/vechain-kit/src/types/types.ts
There was a problem hiding this comment.
Pull request overview
This PR replaces the previous fiat onramp approach with a Transak-based checkout flow (supporting native VET/VTHO on the vechain network), and introduces subscription-related hooks/components plus example + documentation wiring to support the new flows in consuming apps.
Changes:
- Added Transak configuration/types on the provider and implemented an on-demand Transak widget checkout hook + modal/button UI.
- Added subscription hooks and UI (checkout modal + button) and updated the example app to demonstrate fiat (Transak) and crypto subscription paths.
- Updated Privy integration for v3 config shape and refreshed dependencies/docs related to Transak + subscription backends.
Reviewed changes
Copilot reviewed 39 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/vechain-kit/src/types/types.ts | Adds TransakConfig/widget params and subscription-related types. |
| packages/vechain-kit/src/providers/VeChainKitProvider.tsx | Exposes transak/subscriptions config via provider context; updates Privy config shape. |
| packages/vechain-kit/src/languages/en.json | Adds translation keys for Transak + subscription UI strings. |
| packages/vechain-kit/src/index.ts | Updates public exports for newly added payment/subscription components. |
| packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts | Implements Transak widget checkout hook with dynamic import + cleanup. |
| packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts | Implements subscription checkout orchestration for fiat (Transak) and crypto (on-chain) paths. |
| packages/vechain-kit/src/hooks/payments/useSubscription.ts | Adds subscription data hook with optional API-backed behavior and mock fallback. |
| packages/vechain-kit/src/hooks/payments/index.ts | Exports new payments hooks from the payments barrel. |
| packages/vechain-kit/src/hooks/index.ts | Re-exports payments hooks from the main hooks barrel. |
| packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx | Adds the Transak checkout modal that hosts the embedded widget. |
| packages/vechain-kit/src/components/TransakCheckoutModal/index.ts | Barrel export for TransakCheckoutModal. |
| packages/vechain-kit/src/components/SubscriptionModal/SubscriptionModal.tsx | Adds a subscription management modal powered by useSubscription. |
| packages/vechain-kit/src/components/SubscriptionModal/index.ts | Barrel export for SubscriptionModal. |
| packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx | Adds a subscription checkout modal supporting card (Transak) and crypto options. |
| packages/vechain-kit/src/components/SubscriptionCheckoutModal/index.ts | Barrel export for SubscriptionCheckoutModal. |
| packages/vechain-kit/src/components/SubscribeButton/SubscribeButton.tsx | Adds a drop-in subscribe button that opens the checkout modal. |
| packages/vechain-kit/src/components/SubscribeButton/index.ts | Barrel export for SubscribeButton. |
| packages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsx | Adds a drop-in Transak pay button that opens the Transak modal. |
| packages/vechain-kit/src/components/PayWithTransakButton/index.ts | Barrel export for PayWithTransakButton. |
| packages/vechain-kit/src/components/index.ts | Re-exports newly added payment/subscription components from the components barrel. |
| packages/vechain-kit/src/components/AccountModal/Types/Types.ts | Adds transak-onramp content type wiring for AccountModal. |
| packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx | Adds AccountModal content for buying VET via Transak. |
| packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/index.ts | Barrel export for Transak onramp content. |
| packages/vechain-kit/src/components/AccountModal/Contents/index.ts | Re-exports Transak onramp content from AccountModal contents barrel. |
| packages/vechain-kit/src/components/AccountModal/Components/QuickActionsSection.tsx | Adds “Buy” quick action and hides it when Transak is not configured. |
| packages/vechain-kit/src/components/AccountModal/AccountModal.tsx | Adds routing to render TransakOnrampContent. |
| packages/vechain-kit/package.json | Adds Transak SDK dependency and updates Privy version; adjusts build script. |
| package.json | Adds Solana-related dependencies at workspace root. |
| examples/next-template/tsconfig.json | Updates TS config formatting and JSX setting. |
| examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx | Wires optional Transak config + backend widget-url endpoint usage into the demo provider. |
| examples/next-template/src/app/pages/Home.tsx | Adds Payments demo section to the home page. |
| examples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsx | Adds demo UI for Transak purchase + subscription checkout. |
| examples/next-template/src/app/components/features/PaymentsDemo/index.ts | Barrel export for Payments demo component. |
| examples/next-template/src/app/api/transak/widget-url/route.ts | Implements server endpoint to mint Transak Secure Widget URLs with cached access token. |
| examples/next-template/.env.example | Documents env vars needed to enable Transak demo flow. |
| docs/recipes/transak-onramp.md | Documents the required backend contract for Transak Secure Widget URL minting. |
| docs/recipes/stripe-subscriptions.md | Documents backend contract expected by useSubscription + subscription UI. |
| cross-app-connect/src/app/providers/PrivyProviderWrapper.tsx | Updates Privy v3 embeddedWallets config shape for cross-app connect example. |
| cross-app-connect/package.json | Updates Privy/cross-app-provider dependency versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… restore typecheck gate - Remove useSubscription, useSubscriptionCheckout, SubscriptionModal, SubscriptionCheckoutModal, SubscribeButton and stripe-subscriptions docs (full signed-typed-message subscription flow lands in a follow-up PR) - Revert @privy-io/react-auth to 2.25.0 and @privy-io/cross-app-provider to ^0.3.4 in kit and cross-app-connect; drop root Solana deps; restore v2 provider shapes (Privy v3 bump deferred to its own PR) - Restore tsc --noEmit gate in kit build and add Typecheck step to CI - Remove subscription-only i18n keys
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/recipes/transak-onramp.md`:
- Around line 11-24: Update the fenced flow diagram in the documentation by
labeling its opening fence with the text language. Preserve the diagram content
and closing fence unchanged.
- Around line 47-62: Update the request example near the environment field to
use one concrete JSON string value, such as "staging", instead of the
TypeScript-style union; document that the field accepts staging or production
separately in the surrounding description.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8500c1e2-379d-4f1d-8789-86b75f6e7930
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (13)
.github/workflows/lint-build-test.yamldocs/recipes/transak-onramp.mdexamples/next-template/src/app/api/transak/widget-url/route.tsexamples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsxexamples/next-template/src/app/providers/VechainKitProviderWrapper.tsxpackages/vechain-kit/package.jsonpackages/vechain-kit/src/components/index.tspackages/vechain-kit/src/hooks/payments/index.tspackages/vechain-kit/src/hooks/payments/useTransakCheckout.tspackages/vechain-kit/src/index.tspackages/vechain-kit/src/languages/en.jsonpackages/vechain-kit/src/providers/VeChainKitProvider.tsxpackages/vechain-kit/src/types/types.ts
💤 Files with no reviewable changes (3)
- packages/vechain-kit/src/components/index.ts
- packages/vechain-kit/src/hooks/payments/index.ts
- packages/vechain-kit/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
- packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
- Add 10s timeouts to outbound Transak API calls and an in-memory rate limit on the widget-url route (unauthenticated minting protection) - Guard instanceRef assignment with the generation counter so a stale widget never overwrites the active instance - Validate walletAddress with isAddress before building the widget URL - Return to the form step when the Transak widget closes without success - Fix docs: fence language and valid JSON request example
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 29 changed files in this pull request and generated no new comments.
Suppressed comments (5)
examples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsx:21
t('Buy VET with Transak')is used as a translation key, but this key is not present in the translation resources shipped in this PR (so the UI will likely render the raw key string). Add the key to the demo app’s i18n resources (or the kit translations if that’s the intended source), or replace this with non-i18n text for the demo.
<Text fontWeight="bold" mb={2}>
{t('Buy VET with Transak')}
</Text>
examples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsx:15
t('Fiat & Crypto Payments Demo')is used as a translation key, but this key is not present in the translation resources shipped in this PR (so the UI will likely render the raw key string). Add the key to the demo app’s i18n resources (or the kit translations if that’s the intended source), or replace this with non-i18n text for the demo.
This issue also appears on line 19 of the same file.
<Heading size="md" mb={4}>
<b>{t('Fiat & Crypto Payments Demo')}</b>
</Heading>
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:21
openis implemented as anasyncfunction but is typed as returningvoid. This prevents consumers fromawaiting the checkout start (and makes error handling viatry/catchimpossible), which is surprising for an API that performs async work (dynamic import + network call).
open: (params?: {
fiatAmount?: string;
fiatCurrency?: string;
walletAddress?: string;
}) => void;
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:47
setupTransakregisters SDK-level event listeners (TransakSDK.on(...)) every timeopen()is called, but there is no corresponding unsubscribe in this hook. If a user opens/closes the on-ramp multiple times in a long-lived SPA session, listeners can accumulate and slowly increase memory/CPU overhead (even if older listeners early-return due to the generation guard). Consider registering SDK event listeners once (per imported SDK) and routing events through refs, or otherwise ensuring listeners don’t grow unbounded across repeated opens.
TransakSDK.on(
TransakSDK.EVENTS.TRANSAK_ORDER_SUCCESSFUL,
handlers.onOrderSuccessful,
);
TransakSDK.on(
examples/next-template/src/app/api/transak/widget-url/route.ts:129
userIpcan end up as an empty string when neitherx-forwarded-fornorx-real-ipis present. That makes the in-memory rate limiter effectively global for those requests (all callers share the "" bucket) and also sends an invalidx-user-ipheader to Transak (their API expects the end-user’s real IP). Consider deriving the IP fromreq.ipwhen available, and reject the request when you can’t determine a client IP.
const forwardedFor = req.headers.get('x-forwarded-for');
const userIp = forwardedFor?.split(',')[0]?.trim() ?? req.headers.get('x-real-ip') ?? '';
if (rateLimited(userIp)) {
… NEXT_PUBLIC_TRANSAK_API_URL
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/next-template/src/app/api/transak/widget-url/route.ts`:
- Around line 129-134: The userIp variable used in the rateLimited function is
derived from the untrusted X-Forwarded-For header, allowing attackers to rotate
header values and bypass the rate limit. Resolve the client identity from a
trusted source by either implementing trusted proxy middleware that validates
and provides the real client IP or by requiring authentication before this route
is accessible. Validate the resolved identity before passing it to the
rateLimited function to ensure it cannot be spoofed.
In `@packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts`:
- Around line 175-180: Update the open() flow in useTransakCheckout so the
generation value is captured before starting asynchronous work, then guard the
catch block with the same genRef.current check used before instance assignment.
When a newer generation exists, return without setting error status or invoking
onErrorRef.current; preserve existing error handling for the active generation.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 841fd9dc-5f1f-4c9e-a2b1-95f331a721c3
📒 Files selected for processing (6)
docs/recipes/transak-onramp.mdexamples/next-template/.env.exampleexamples/next-template/src/app/api/transak/widget-url/route.tsexamples/next-template/src/app/providers/VechainKitProviderWrapper.tsxpackages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsxpackages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
- docs/recipes/transak-onramp.md
- packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx
- Add transak prop to homepage VechainKitProviderWrapper (same pattern as next-template): Buy VET button appears in AccountModal when NEXT_PUBLIC_TRANSAK_API_KEY is set, calling the mini-server at NEXT_PUBLIC_TRANSAK_API_URL - Pass NEXT_PUBLIC_TRANSAK_API_KEY and NEXT_PUBLIC_TRANSAK_API_URL through deploy-preview.yaml so the homepage preview build inlines them - Document both vars in examples/homepage/.env.example
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx (1)
245-252: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winValidate the widget URL response at the API boundary.
The
as stringassertion does not validate JSON at runtime. A2xxresponse withoutdata.widgetUrlcan throw or passundefinedto the checkout. Reject malformed responses with a controlled error.Suggested response guard
- const json = await res.json(); + const json = await res.json().catch(() => null); if (!res.ok) { throw new Error( json?.error ?? 'Failed to create Transak widget URL', ); } - return json.data.widgetUrl as string; + const widgetUrl = json?.data?.widgetUrl; + if ( + typeof widgetUrl !== 'string' || + widgetUrl.length === 0 + ) { + throw new Error( + 'Invalid Transak widget URL response', + ); + } + return widgetUrl;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx` around lines 245 - 252, Update the response handling around the Transak widget URL request to validate that the successful JSON payload contains a non-empty string at data.widgetUrl before returning it. Replace the compile-time assertion in the response path with a controlled error for malformed 2xx responses, while preserving the existing res.ok error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/homepage/.env.example`:
- Around line 9-10: Update the NEXT_PUBLIC_TRANSAK_API_KEY documentation in the
environment example to use a production Transak key matching the “main” network
configured by VechainKitProviderWrapper, or change the provider configuration to
a matching staging/test network; keep the documented empty-value behavior for
hiding the Buy button.
In `@examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx`:
- Around line 217-220: Update the Transak request in VechainKitProviderWrapper
around NEXT_PUBLIC_TRANSAK_API_URL so it cannot fall back to an empty URL and
post unintentionally to the homepage origin. Configure a valid API URL for the
app or implement a same-origin proxy for /api/transak/widget-url, while
preserving the existing request behavior when the configured endpoint is
available.
---
Nitpick comments:
In `@examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx`:
- Around line 245-252: Update the response handling around the Transak widget
URL request to validate that the successful JSON payload contains a non-empty
string at data.widgetUrl before returning it. Replace the compile-time assertion
in the response path with a controlled error for malformed 2xx responses, while
preserving the existing res.ok error handling.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 116f50fc-d715-46c0-9556-3e9775093734
📒 Files selected for processing (3)
.github/workflows/deploy-preview.yamlexamples/homepage/.env.exampleexamples/homepage/src/app/providers/VechainKitProviderWrapper.tsx
1d704ce to
a822980
Compare
…ts docs - Both providers (homepage + next-template) pass NEXT_PUBLIC_TRANSAK_ENVIRONMENT in the fetch body (not as top-level config prop) to the mini-server - deploy-preview.yaml: NEXT_PUBLIC_TRANSAK_ENVIRONMENT='staging' - deploy-cloudfront.yaml: NEXT_PUBLIC_TRANSAK_ENVIRONMENT='production' - docs/recipes/transak-onramp.md: section 6 with credential isolation, x-api-key, x-user-ip, backend IP whitelisting, CORS, rate limiting, partner onboarding checklist; env vars table updated
a822980 to
3e752da
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.
Suppressed comments (6)
packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx:114
- The success-state “Done” button calls
onClosedirectly, bypassinghandleClose()and therefore skippingonReset(). For consumers that rely ononResetto clear status/error before closing, this can leave the modal stuck in a non-idle state when reopened.
actions={
<Button variant="vechainKitPrimary" onClick={onClose} w="full">
{t('Done')}
</Button>
}
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:92
removeEmbeddedWidget()does not advance the generation counter, so late Transak events from a closed widget (or an in-flightopen()that is later closed/unmounted) can still match the currentgenand update state after the user has closed the checkout.
const removeEmbeddedWidget = useCallback(() => {
instanceRef.current?.cleanup();
instanceRef.current = null;
document.getElementById(TRANSAK_WIDGET_CONTAINER_ID)?.replaceChildren();
}, []);
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:13
TRANSAK_WIDGET_CONTAINER_IDis a hard-coded DOM id, but it’s used in multiple components (TransakCheckoutModalandAccountModal/TransakOnrampContent). Rendering more than one Transak checkout in the DOM will create duplicate ids andgetElementById()may target the wrong container during init/cleanup.
/** DOM id of the container the Transak iframe is embedded into. Rendering the
* widget inside the host modal (instead of the SDK's own full-screen overlay)
* avoids focus-trap conflicts that make the widget flicker/unfocus on click. */
export const TRANSAK_WIDGET_CONTAINER_ID = 'vechain-kit-transak-widget-container';
examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx:151
- The Transak backend endpoint requires an
environmentvalue, but thiswidgetUrlBuilderonly sendsNEXT_PUBLIC_TRANSAK_ENVIRONMENT. When that env var is unset,environmentis omitted from the JSON (viaJSON.stringify), causing the endpoint to 400 even though the kit already derives an environment.
environment:
process.env
.NEXT_PUBLIC_TRANSAK_ENVIRONMENT as
| 'staging'
| 'production'
examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx:232
- Same issue as next-template: if
NEXT_PUBLIC_TRANSAK_ENVIRONMENTis unset,environmentis omitted from the request body and the/api/transak/widget-urlroute returns 400. The kit already supplies a derivedenvironmentvalue towidgetUrlBuilder; it should be forwarded (optionally overridden).
environment:
process.env
.NEXT_PUBLIC_TRANSAK_ENVIRONMENT as
| 'staging'
| 'production'
examples/next-template/src/app/api/transak/widget-url/route.ts:134
- If
x-forwarded-for/x-real-ipis missing,userIpbecomes an empty string. This both (1) makes the rate-limit bucket shared across all such requests and (2) forwards an invalidx-user-ipheader to Transak (which is required), likely causing hard-to-debug failures. Consider rejecting requests when the caller IP cannot be determined.
}
let body: { widgetParams?: Record<string, unknown>; environment?: unknown };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 });
}
…sak checkout catch - widget-url route: resolveClientIp prefers x-real-ip (written by trusted proxies, unspoofable from the client) over the leftmost x-forwarded-for entry; falls back to 'unknown' bucket when no public IP is available so attackers cannot trivially rotate headers to bypass the rate limit. - useTransakCheckout open(): guard the catch block with genRef.current !== gen so a superseded generation does not overwrite the active generation's error/status (matches the guard already used before instance assignment).
d6280c7 to
60cfbb9
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/recipes/transak-onramp.md`:
- Line 143: Update the architectural prerequisites summary in the on-ramp
documentation to distinguish Transak-enforced requirements from backend controls
such as CORS and rate limiting. Associate only Transak checks with Transak API
401/403 responses, and describe the endpoint’s CORS and rate-limiting
protections separately.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9a83c979-5980-479e-b746-9d9757aaca33
📒 Files selected for processing (8)
.github/workflows/deploy-cloudfront.yaml.github/workflows/deploy-preview.yamldocs/recipes/transak-onramp.mdexamples/homepage/src/app/providers/VechainKitProviderWrapper.tsxexamples/next-template/src/app/api/transak/widget-url/route.tsexamples/next-template/src/app/providers/VechainKitProviderWrapper.tsxpackages/vechain-kit/package.jsonpackages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- .github/workflows/deploy-preview.yaml
- examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx
- packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
- examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
- examples/next-template/src/app/api/transak/widget-url/route.ts
- packages/vechain-kit/package.json
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.
Suppressed comments (5)
examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx:151
widgetUrlBuilderreceives the kit-derivedenvironmentparam, but this wrapper ignores it and instead sendsNEXT_PUBLIC_TRANSAK_ENVIRONMENT(which can be undefined) to the backend. Since/api/transak/widget-urlrejects missing/invalid environment, the demo breaks unless that env var is always set.
widgetUrlBuilder: async ({
walletAddress,
fiatAmount,
fiatCurrency,
cryptoCurrency,
examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx:232
widgetUrlBuilderreceives the kit-derivedenvironmentparam, but this wrapper ignores it and instead sendsNEXT_PUBLIC_TRANSAK_ENVIRONMENT(which can be undefined) to the backend. Since/api/transak/widget-urlrejects missing/invalid environment, the demo breaks unless that env var is always set.
widgetUrlBuilder: async ({
walletAddress,
fiatAmount,
fiatCurrency,
cryptoCurrency,
network,
docs/recipes/transak-onramp.md:110
- The wiring example says the kit passes
environmentintowidgetUrlBuilder, but the snippet ignores that parameter and sendsNEXT_PUBLIC_TRANSAK_ENVIRONMENT(which may be unset) to the backend. This contradicts the intended auto-derivation behavior and the endpoint contract that requires a concrete environment.
widgetUrlBuilder: async ({
walletAddress,
fiatAmount,
fiatCurrency,
cryptoCurrency,
examples/next-template/src/app/api/transak/widget-url/route.ts:156
resolveClientIp()can return'unknown', which then gets forwarded as thex-user-ipheader to Transak. That’s not a valid IP and will likely cause hard-to-debug failures (and also makes all such callers share one rate-limit bucket). Consider failing fast with a clear 400 when no public client IP can be resolved.
const userIp = resolveClientIp(req);
if (rateLimited(userIp)) {
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts:21
openis implemented as anasyncfunction but the exportedUseTransakCheckoutResulttype declares it returnsvoid. This makes the public hook API misleading and prevents consumers fromawaiting it (even though it returns a promise at runtime).
open: (params?: {
fiatAmount?: string;
fiatCurrency?: string;
walletAddress?: string;
}) => void;
Summary
Replaces the Privy/Stripe fiat onramp with Transak, which natively supports VET + VTHO on the
vechainnetwork.Breaking changes (replaces #647)
useBuyCrypto,useFiatCheckout,FiatCheckoutModal,PayWithFiatButton,FiatOnrampContentuseTransakCheckout,TransakCheckoutModal,PayWithTransakButton,TransakOnrampContentfiatOnramp→transakonVeChainKitProvider@transak/ui-js-sdkadded;@stripe/cryptopeer dep removedTransak integration details
widgetUrlBuilderis a required async callback that mints a single-use Secure Widget URL (5 min TTL) through your backend — the API secret must never be exposed client-side. Seedocs/recipes/transak-onramp.mdfor the endpoint contract and a reference implementation inexamples/next-template.main→production,test/solo→staging), overridable viatransak.environment. Access-token caching is per-environment.@transak/ui-js-sdkloaded on demand (not bundled)useEffectreturn callsinstance.close()VechainKitThemeProviderso custom themes applyHousekeeping
tsc --noEmitgate on the kit build and added a Typecheck step tolint-build-test.yaml(kit types were previously unchecked in CI)useSubscription,useSubscriptionCheckout,SubscriptionModal,SubscribeButton) — a full signed-typed-message subscription flow will land in a follow-up PR (ERC-20 auto-pull only)@privy-io/react-auth2.25.0); the v3 bump is deferred to its own PRSummary by CodeRabbit
New Features
Documentation
Chores