Skip to content

feat: switch fiat onramp to Transak (native VET support) - #649

Merged
victhorbi merged 21 commits into
mainfrom
feat/transak-onramp
Aug 6, 2026
Merged

feat: switch fiat onramp to Transak (native VET support)#649
victhorbi merged 21 commits into
mainfrom
feat/transak-onramp

Conversation

@victhorbi

@victhorbi victhorbi commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the Privy/Stripe fiat onramp with Transak, which natively supports VET + VTHO on the vechain network.

Breaking changes (replaces #647)

  • Deleted: useBuyCrypto, useFiatCheckout, FiatCheckoutModal, PayWithFiatButton, FiatOnrampContent
  • Added: useTransakCheckout, TransakCheckoutModal, PayWithTransakButton, TransakOnrampContent
  • Config: fiatOnramptransak on VeChainKitProvider
  • Dep: @transak/ui-js-sdk added; @stripe/crypto peer dep removed

Transak integration details

  • Secure Widget URL flow (required): Transak deprecated direct widget URLs (API key in query string). widgetUrlBuilder is 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. See docs/recipes/transak-onramp.md for the endpoint contract and a reference implementation in examples/next-template.
  • Environment auto-derivation: staging/production is derived from the connected VeChain network (mainproduction, test/solostaging), overridable via transak.environment. Access-token caching is per-environment.
  • Dynamic import: @transak/ui-js-sdk loaded on demand (not bundled)
  • Generation counter: Ignores stale events from previous Transak sessions
  • React cleanup: useEffect return calls instance.close()
  • Smart-account aware: checkout pre-fills the active smart-account address
  • Theming: Transak components wrapped in VechainKitThemeProvider so custom themes apply

Housekeeping

  • Restored tsc --noEmit gate on the kit build and added a Typecheck step to lint-build-test.yaml (kit types were previously unchecked in CI)
  • Removed the half-implemented subscription UI (useSubscription, useSubscriptionCheckout, SubscriptionModal, SubscribeButton) — a full signed-typed-message subscription flow will land in a follow-up PR (ERC-20 auto-pull only)
  • Kept Privy on v2 (@privy-io/react-auth 2.25.0); the v3 bump is deferred to its own PR

Summary by CodeRabbit

  • New Features

    • Added Transak on-ramp support for purchasing VET with fiat currency.
    • Added payment buttons, checkout modals, wallet quick actions, and checkout status handling.
    • Added secure widget URL creation with environment-specific configuration, validation, rate limiting, and error handling.
    • Added localized payment messages and configurable Transak integration.
    • Added on-ramp purchasing demos to the example applications.
  • Documentation

    • Added setup and integration guidance for Transak on-ramp flows.
  • Chores

    • Added TypeScript validation to the build and test workflow.
    • Added staging and production deployment configuration.

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

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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.

Changes

Transak payment integration

Layer / File(s) Summary
Payment contracts and provider wiring
packages/vechain-kit/src/types/types.ts, packages/vechain-kit/src/providers/VeChainKitProvider.tsx, packages/vechain-kit/package.json, packages/vechain-kit/src/components/index.ts, packages/vechain-kit/src/hooks/*, packages/vechain-kit/src/index.ts, packages/vechain-kit/src/languages/en.json
Adds Transak configuration and widget parameter types, provider context support, the buy quick action, SDK dependency, translations, and public exports.
Transak checkout lifecycle
packages/vechain-kit/src/hooks/payments/*, packages/vechain-kit/src/components/TransakCheckoutModal/*, packages/vechain-kit/src/components/PayWithTransakButton/*
Adds checkout state management, secure widget URL loading, SDK lifecycle handling, cleanup, callbacks, status views, and payment button controls.
Account modal on-ramp
packages/vechain-kit/src/components/AccountModal/*
Adds the Buy quick action and the Transak account-modal flow for amount, currency, processing, success, error, retry, and navigation states.
Secure widget backend
examples/next-template/src/app/api/transak/*, examples/homepage/src/app/providers/*, examples/*/.env.example, docs/recipes/transak-onramp.md
Adds the backend widget URL route, provider fetch wiring, environment configuration, and the integration recipe.
Example application integration
examples/next-template/src/app/components/features/PaymentsDemo/*, examples/next-template/src/app/pages/Home.tsx, examples/next-template/tsconfig.json, .github/workflows/*, packages/vechain-kit/src/providers/VeChainKitProvider.tsx, packages/vechain-kit/src/types/types.ts
Adds the demo section, home-page wiring, typecheck step, deployment secrets, and example TypeScript config updates.

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

Possibly related PRs

Suggested reviewers: davidecarpini

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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing the fiat onramp with Transak and adding native VET support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/transak-onramp

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.

- 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.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Size Change: +96.9 kB (+1.09%)

Total Size: 9.02 MB

Filename Size Change
packages/vechain-kit/dist/index-B1iE13PB.d.cts 0 B -186 kB (removed) 🏆
packages/vechain-kit/dist/index-B1iE13PB.d.cts.map 0 B -50.8 kB (removed) 🏆
packages/vechain-kit/dist/index-B7W1mM8I.d.mts 0 B -5.63 kB (removed) 🏆
packages/vechain-kit/dist/index-B7W1mM8I.d.mts.map 0 B -2.99 kB (removed) 🏆
packages/vechain-kit/dist/index-C5rCUFaT.d.mts 0 B -186 kB (removed) 🏆
packages/vechain-kit/dist/index-C5rCUFaT.d.mts.map 0 B -49.5 kB (removed) 🏆
packages/vechain-kit/dist/index-nRf3KRll.d.cts 0 B -5.63 kB (removed) 🏆
packages/vechain-kit/dist/index-nRf3KRll.d.cts.map 0 B -2.99 kB (removed) 🏆
packages/vechain-kit/dist/index.cjs 1.16 MB +9.15 kB (+0.8%)
packages/vechain-kit/dist/index.cjs.map 2.9 MB +34.2 kB (+1.2%)
packages/vechain-kit/dist/index.mjs 1.11 MB +7.84 kB (+0.71%)
packages/vechain-kit/dist/index.mjs.map 2.83 MB +32.5 kB (+1.16%)
packages/vechain-kit/dist/utils-Bf4wG_2j.cjs.map 69.8 kB +1.53 kB (+2.25%)
packages/vechain-kit/dist/utils-Y7vw7K0X.mjs.map 69 kB +1.53 kB (+2.27%)
packages/vechain-kit/dist/index--hSO7Xv4.d.mts 5.63 kB +5.63 kB (new file) 🆕
packages/vechain-kit/dist/index--hSO7Xv4.d.mts.map 2.99 kB +2.99 kB (new file) 🆕
packages/vechain-kit/dist/index-BuYV7UXW.d.cts 5.63 kB +5.63 kB (new file) 🆕
packages/vechain-kit/dist/index-BuYV7UXW.d.cts.map 2.99 kB +2.99 kB (new file) 🆕
packages/vechain-kit/dist/index-dx1uONqz.d.cts 190 kB +190 kB (new file) 🆕
packages/vechain-kit/dist/index-dx1uONqz.d.cts.map 51.6 kB +51.6 kB (new file) 🆕
packages/vechain-kit/dist/index-obZevfEk.d.mts 190 kB +190 kB (new file) 🆕
packages/vechain-kit/dist/index-obZevfEk.d.mts.map 50.4 kB +50.4 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
packages/vechain-kit/dist/assets 4.1 kB 0 B
packages/vechain-kit/dist/assets-BpQtJMK4.mjs 51.4 kB 0 B
packages/vechain-kit/dist/assets-BpQtJMK4.mjs.map 74.7 kB 0 B
packages/vechain-kit/dist/assets-qmvJ4lSm.cjs 59 kB 0 B
packages/vechain-kit/dist/assets-qmvJ4lSm.cjs.map 76.2 kB 0 B
packages/vechain-kit/dist/assets/index.cjs 717 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 719 B 0 B
packages/vechain-kit/dist/index.d.cts 25 kB +510 B (+2.08%)
packages/vechain-kit/dist/index.d.mts 25 kB +510 B (+2.08%)
packages/vechain-kit/dist/utils 4.1 kB 0 B
packages/vechain-kit/dist/utils-Bf4wG_2j.cjs 27.4 kB 0 B
packages/vechain-kit/dist/utils-Y7vw7K0X.mjs 22.2 kB 0 B
packages/vechain-kit/dist/utils/index.cjs 2.02 kB 0 B
packages/vechain-kit/dist/utils/index.d.cts 3.1 kB 0 B
packages/vechain-kit/dist/utils/index.d.mts 3.1 kB 0 B
packages/vechain-kit/dist/utils/index.mjs 2.04 kB 0 B

compressed-size-action

@victhorbi
victhorbi marked this pull request as ready for review August 3, 2026 13:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 win

Validate the fiat amount before opening checkout.

Line 61 passes raw input to widgetUrlBuilder. The min attribute 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 win

Fix 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, and DELETE /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 win

Remove the duplicate translation keys.

Two keys are declared twice in this block. Biome reports both as lint/suspicious/noDuplicateObjectKeys errors, 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 win

The documentation does not cover the flows this PR ships.

This PR replaces the Stripe fiat onramp with Transak and removes the @stripe/crypto peer dependency. This new document is titled "Stripe Subscriptions Backend API", and its only payment example is the Stripe PaymentMethod id pm_card_visa (line 41).

Line 3 names useSubscription and SubscriptionModal as the consumers. It does not mention useSubscriptionCheckout or SubscriptionCheckoutModal, 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/subscriptions runs, 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 win

Both doc comments describe the behavior incorrectly.

Line 44 states that transakOpen is "Re-initialized every render". It is not. useTransakCheckout returns open as a useCallback memoized on [transakConfig, account?.address] (useTransakCheckout.ts line 178).

Lines 50-51 state that hasFiat is true "when a Transak API key is configured". Line 78 computes it from transakConfig?.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 win

Defer the empty-plan loading view during checkout.

isLoading includes isTransactionPending, and the crypto checkout path sets status === 'processing' before awaiting confirmation. If this guard renders with availablePlans.length === 0, it replaces the inline wallet confirmation view with the generic spinner. Keep loading states for in-progress transactions under status === 'processing', or exclude status === '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 win

Use separate Transak widget containers when TransakCheckoutModal and SubscriptionCheckoutModal can render together.

TRANSAK_WIDGET_CONTAINER_ID is shared by both modals, but useTransakCheckout creates the instance with containerId: 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 win

Fix dotenv-linter warnings: key order and missing trailing newline.

dotenv-linter flags NEXT_PUBLIC_NETWORK_TYPE and NEXT_PUBLIC_TRANSAK_API_KEY as 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 win

Use 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 relative Types import with its @components/... alias.
  • packages/vechain-kit/src/components/AccountModal/Types/Types.ts#L27-L27: replace the relative TransakOnrampContentProps import with its @components/... alias.
  • packages/vechain-kit/src/components/AccountModal/AccountModal.tsx#L42-L42: replace the relative TransakOnrampContent import with its @components/... alias.

As per coding guidelines, use path aliases for imports: @/* for src root, @hooks for hooks, @components for components, and @utils for 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 value

Derive the default payment method from hasFiat.

The initial state at line 83 and reset at line 193 both set paymentMethod to 'fiat' unconditionally. When transak.widgetUrlBuilder is not configured, hasFiat is false and the default is an unavailable method. If subscribe runs before a plan is selected, it enters the fiat branch and fails inside useTransakCheckout with "Transak is not configured".

selectPlan and open already apply the hasFiat fallback. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 57f0f51 and 69e57b3.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (38)
  • cross-app-connect/package.json
  • cross-app-connect/src/app/providers/PrivyProviderWrapper.tsx
  • docs/recipes/stripe-subscriptions.md
  • examples/next-template/.env.example
  • examples/next-template/src/app/api/transak/widget-url/route.ts
  • examples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsx
  • examples/next-template/src/app/components/features/PaymentsDemo/index.ts
  • examples/next-template/src/app/pages/Home.tsx
  • examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
  • examples/next-template/tsconfig.json
  • package.json
  • packages/vechain-kit/package.json
  • packages/vechain-kit/src/components/AccountModal/AccountModal.tsx
  • packages/vechain-kit/src/components/AccountModal/Components/QuickActionsSection.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/index.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/index.ts
  • packages/vechain-kit/src/components/AccountModal/Types/Types.ts
  • packages/vechain-kit/src/components/PayWithTransakButton/PayWithTransakButton.tsx
  • packages/vechain-kit/src/components/PayWithTransakButton/index.ts
  • packages/vechain-kit/src/components/SubscribeButton/SubscribeButton.tsx
  • packages/vechain-kit/src/components/SubscribeButton/index.ts
  • packages/vechain-kit/src/components/SubscriptionCheckoutModal/SubscriptionCheckoutModal.tsx
  • packages/vechain-kit/src/components/SubscriptionCheckoutModal/index.ts
  • packages/vechain-kit/src/components/SubscriptionModal/SubscriptionModal.tsx
  • packages/vechain-kit/src/components/SubscriptionModal/index.ts
  • packages/vechain-kit/src/components/TransakCheckoutModal/TransakCheckoutModal.tsx
  • packages/vechain-kit/src/components/TransakCheckoutModal/index.ts
  • packages/vechain-kit/src/components/index.ts
  • packages/vechain-kit/src/hooks/index.ts
  • packages/vechain-kit/src/hooks/payments/index.ts
  • packages/vechain-kit/src/hooks/payments/useSubscription.ts
  • packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts
  • packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
  • packages/vechain-kit/src/index.ts
  • packages/vechain-kit/src/languages/en.json
  • packages/vechain-kit/src/providers/VeChainKitProvider.tsx
  • packages/vechain-kit/src/types/types.ts

Comment thread examples/next-template/src/app/api/transak/widget-url/route.ts Outdated
Comment thread examples/next-template/src/app/api/transak/widget-url/route.ts
Comment thread packages/vechain-kit/src/components/SubscriptionModal/SubscriptionModal.tsx Outdated
Comment thread packages/vechain-kit/src/components/SubscriptionModal/SubscriptionModal.tsx Outdated
Comment thread packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts Outdated
Comment thread packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts Outdated
Comment thread packages/vechain-kit/src/hooks/payments/useSubscriptionCheckout.ts Outdated
Comment thread packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
Comment thread packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread packages/vechain-kit/src/index.ts Outdated
Comment thread packages/vechain-kit/src/hooks/payments/useSubscription.ts Outdated
Comment thread packages/vechain-kit/src/hooks/payments/useSubscription.ts Outdated
Comment thread packages/vechain-kit/src/languages/en.json
… 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 69e57b3 and 4cefe6b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • .github/workflows/lint-build-test.yaml
  • docs/recipes/transak-onramp.md
  • examples/next-template/src/app/api/transak/widget-url/route.ts
  • examples/next-template/src/app/components/features/PaymentsDemo/PaymentsDemo.tsx
  • examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
  • packages/vechain-kit/package.json
  • packages/vechain-kit/src/components/index.ts
  • packages/vechain-kit/src/hooks/payments/index.ts
  • packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
  • packages/vechain-kit/src/index.ts
  • packages/vechain-kit/src/languages/en.json
  • packages/vechain-kit/src/providers/VeChainKitProvider.tsx
  • packages/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

Comment thread docs/recipes/transak-onramp.md Outdated
Comment thread docs/recipes/transak-onramp.md
- 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
@Vombato
Vombato requested a review from Copilot August 4, 2026 08:26
Vombato
Vombato previously approved these changes Aug 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

  • open is implemented as an async function but is typed as returning void. This prevents consumers from awaiting the checkout start (and makes error handling via try/catch impossible), 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

  • setupTransak registers SDK-level event listeners (TransakSDK.on(...)) every time open() 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

  • userIp can end up as an empty string when neither x-forwarded-for nor x-real-ip is present. That makes the in-memory rate limiter effectively global for those requests (all callers share the "" bucket) and also sends an invalid x-user-ip header to Transak (their API expects the end-user’s real IP). Consider deriving the IP from req.ip when 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)) {

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4cefe6b and bc356b4.

📒 Files selected for processing (6)
  • docs/recipes/transak-onramp.md
  • examples/next-template/.env.example
  • examples/next-template/src/app/api/transak/widget-url/route.ts
  • examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/TransakOnramp/TransakOnrampContent.tsx
  • packages/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

Comment thread examples/next-template/src/app/api/transak/widget-url/route.ts
Comment thread packages/vechain-kit/src/hooks/payments/useTransakCheckout.ts
- 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx (1)

245-252: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate the widget URL response at the API boundary.

The as string assertion does not validate JSON at runtime. A 2xx response without data.widgetUrl can throw or pass undefined to 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

📥 Commits

Reviewing files that changed from the base of the PR and between bc356b4 and 8819a59.

📒 Files selected for processing (3)
  • .github/workflows/deploy-preview.yaml
  • examples/homepage/.env.example
  • examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx

Comment thread examples/homepage/.env.example
Comment thread examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx
Vombato
Vombato previously approved these changes Aug 5, 2026
…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
@victhorbi
victhorbi force-pushed the feat/transak-onramp branch from a822980 to 3e752da Compare August 5, 2026 15:41
@victhorbi
victhorbi requested a review from Vombato August 5, 2026 15:47
Vombato added 2 commits August 5, 2026 17:52
- deploy-preview uses NEXT_PUBLIC_TRANSAK_API_KEY_STAGING
- deploy-cloudfront uses NEXT_PUBLIC_TRANSAK_API_KEY_PROD

Keeps the prod site inert (Buy hidden) until the production key exists.
Re-applies 808f04a, lost when #653 was folded into this branch.
Comment thread .github/workflows/deploy-cloudfront.yaml
Comment thread .github/workflows/deploy-cloudfront.yaml
Comment thread .github/workflows/deploy-preview.yaml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 onClose directly, bypassing handleClose() and therefore skipping onReset(). For consumers that rely on onReset to 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-flight open() that is later closed/unmounted) can still match the current gen and 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_ID is a hard-coded DOM id, but it’s used in multiple components (TransakCheckoutModal and AccountModal/TransakOnrampContent). Rendering more than one Transak checkout in the DOM will create duplicate ids and getElementById() 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 environment value, but this widgetUrlBuilder only sends NEXT_PUBLIC_TRANSAK_ENVIRONMENT. When that env var is unset, environment is omitted from the JSON (via JSON.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_ENVIRONMENT is unset, environment is omitted from the request body and the /api/transak/widget-url route returns 400. The kit already supplies a derived environment value to widgetUrlBuilder; 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-ip is missing, userIp becomes an empty string. This both (1) makes the rate-limit bucket shared across all such requests and (2) forwards an invalid x-user-ip header 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).
@victhorbi
victhorbi force-pushed the feat/transak-onramp branch from d6280c7 to 60cfbb9 Compare August 5, 2026 16:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8819a59 and d6280c7.

📒 Files selected for processing (8)
  • .github/workflows/deploy-cloudfront.yaml
  • .github/workflows/deploy-preview.yaml
  • docs/recipes/transak-onramp.md
  • examples/homepage/src/app/providers/VechainKitProviderWrapper.tsx
  • examples/next-template/src/app/api/transak/widget-url/route.ts
  • examples/next-template/src/app/providers/VechainKitProviderWrapper.tsx
  • packages/vechain-kit/package.json
  • packages/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

Comment thread docs/recipes/transak-onramp.md

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

  • widgetUrlBuilder receives the kit-derived environment param, but this wrapper ignores it and instead sends NEXT_PUBLIC_TRANSAK_ENVIRONMENT (which can be undefined) to the backend. Since /api/transak/widget-url rejects 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

  • widgetUrlBuilder receives the kit-derived environment param, but this wrapper ignores it and instead sends NEXT_PUBLIC_TRANSAK_ENVIRONMENT (which can be undefined) to the backend. Since /api/transak/widget-url rejects 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 environment into widgetUrlBuilder, but the snippet ignores that parameter and sends NEXT_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 the x-user-ip header 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

  • open is implemented as an async function but the exported UseTransakCheckoutResult type declares it returns void. This makes the public hook API misleading and prevents consumers from awaiting it (even though it returns a promise at runtime).
    open: (params?: {
        fiatAmount?: string;
        fiatCurrency?: string;
        walletAddress?: string;
    }) => void;

@Vombato Vombato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@victhorbi
victhorbi merged commit 8718d6e into main Aug 6, 2026
11 checks passed
@victhorbi
victhorbi deleted the feat/transak-onramp branch August 6, 2026 08:40
@davidecarpini davidecarpini mentioned this pull request Aug 28, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants