Conversation
* fix(wallet): export Starknet embedded key when network is Starknet (#498)
* feat(wallet): implement Starknet wallet export functionality with HPKE encryption
- Added a new API endpoint for exporting Starknet embedded wallets, utilizing Privy's HPKE encryption.
- Introduced a modal for user interaction during the export process, allowing users to copy their private key securely.
- Updated context and hooks to manage the export modal state and integrate with the existing Starknet provider.
- Added necessary dependencies for HPKE encryption and decryption processes.
This feature enhances the wallet's functionality by enabling secure export of private keys for Starknet users.
* refactor(wallet): update authorization signature handling in Starknet export route
- Replaced the previous authorization signature generation method with a new approach using and .
- Enhanced error handling for signature generation failures, returning a 500 status with an appropriate error message.
- Cleaned up the code for better readability and maintainability by consolidating signature logic into a single section.
* feat(wallet): enhance ExportStarknetWalletModal with key verification
- Added expectedPublicKey prop to verify the exported key against the wallet's signer key.
- Implemented key verification logic to warn users if the exported key does not match the expected public key or if the derived address differs from the displayed address.
- Introduced a key verification warning message in the modal to inform users of potential mismatches.
- Updated context to pass the publicKey to the ExportStarknetWalletModal for verification purposes.
* feat(kyc): extract tiered KYC flow into clean branch (#496)
* feat(kyc): extract tiered KYC flow into clean branch
Port the tiered KYC API, UI, and migration changes onto latest main while preserving current main exports and dependency baseline to keep the PR isolated and reviewable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(build): lazy Supabase admin client and restore Starknet providers
- Defer Supabase client creation until first use so next build does not require secrets at bundle time.\n- Restore HomeTransactionFormModeProvider and StarknetProvider around KYC context tree.\n- Add https-proxy-agent for Twilio server external resolution.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(env): update Supabase keys and improve KYC handling
- Revised .env.example to clarify the use of Supabase secret keys and added comments for better guidance.
- Updated jest.setup.js to use the new SUPABASE_SECRET_KEY for testing.
- Enhanced KYC API routes to improve error handling and wallet address verification.
- Introduced new utility functions for KYC tier management and refined transaction handling logic.
- Improved user feedback in KYC and phone verification modals for better clarity and user experience.
* feat(swap): implement swap transaction precheck and enhance KYC handling
- Added a new endpoint for prechecking swap transactions to verify monthly KYC limits without inserting records.
- Introduced utility functions for swap transaction limit checks, improving error handling and user feedback.
- Updated KYC-related API routes to enhance wallet address verification and error messaging.
- Enhanced the .env.example file with additional configuration options for Dojah utility bill processing.
- Improved handling of KYC document uploads and error messages in the KYC verification process.
- Updated transaction form to support new swap functionality and ensure accurate limit checks based on user KYC tier.
* feat(api): enhance transaction handling and KYC validation
- Updated transaction-related API endpoints to support both onramp and offramp transaction types.
- Improved KYC handling by integrating new validation checks for phone numbers and ID information.
- Refactored transaction limit checks to accommodate new transaction types and ensure accurate KYC compliance.
- Enhanced error handling and user feedback in phone verification and KYC modals.
- Added support for keyboard search and filtering in dropdown components for better user experience.
- Updated configuration for server external packages to resolve dependency issues.
* feat(kyc): enhance KYC validation and transaction handling
- Updated .env.example with clearer comments for Smile Identity configuration.
- Modified submitSmileIDData function to include wallet address in headers for KYC submissions.
- Improved error handling in KYC routes to provide more specific feedback for verification attempts.
- Refactored transaction processing to support new transaction types and ensure accurate KYC compliance.
- Enhanced transaction status checks to include new error messages and validation for onramp/offramp transactions.
- Updated database migrations to accommodate changes in transaction types and KYC profile management.
* refactor(transactions): normalize transaction type handling and enhance Twilio verification timeout
- Updated transaction processing to use normalized transaction types for consistency.
- Introduced timeout handling for Twilio verification requests to prevent hanging operations.
- Improved error logging for Twilio verification failures, distinguishing between timeout and other errors.
- Adjusted database migration to enforce unique constraints on transaction records more effectively.
* refactor(profile): streamline KYC tier handling and improve display logic
- Replaced the previous tier expansion state management with a single state for expanded tier level.
- Updated tier display logic to use a new utility function for consistent tier labeling.
- Enhanced the TransactionLimitModal to reflect the current KYC tier accurately and conditionally render tier information.
- Improved user feedback in the ProfileDrawer and TransactionLimitModal for better clarity on KYC status and upgrade options.
* refactor(profile): update icons and KYC requirements for improved clarity
- Replaced AiPhone01Icon with TbPhoneCall in ProfileDrawer for consistency with other icons.
- Updated KYC tier requirements from "Phone verification" to "Phone number" for clearer user understanding.
* refactor(kyc): update KYC tier limits and improve user messaging
- Adjusted KYC tier monthly limits in .env.example and related documentation for clarity.
- Enhanced user messaging in TransactionLimitModal and other components to reflect updated KYC requirements.
- Renamed tier 0 from "Free" to "Unverified" for better understanding of user status.
- Improved handling of phone verification prompts in the transaction flow.
* fix(kyc): update user messaging for KYC verification prompts
- Changed the prompt from "Start" to "Get started" for improved clarity in the KYC verification process when conditions are not met.
* refactor(kyc): enhance TransactionLimitModal messaging and layout
- Updated the title and messaging in TransactionLimitModal to provide clearer instructions based on the user's KYC upgrade step.
- Improved conditional rendering for user limits and verification prompts, ensuring relevant information is displayed based on the user's verification status.
- Adjusted styling and layout for better visual clarity and user experience.
* feat(profile): add ProfileDrawer and integrate profile access in MobileDropdown and SettingsView
- Introduced ProfileDrawer component for user profile management.
- Updated MobileDropdown to include state management for ProfileDrawer and added an onOpenProfile handler.
- Enhanced SettingsView to provide a button for accessing the profile, improving user navigation and experience.
* fix(css): prevent iOS Safari auto-zoom on form fields
- Added CSS rules to ensure input, select, and textarea elements have a minimum font size of 16px on devices with a max-width of 640px, preventing unwanted zoom behavior in iOS Safari.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: sundayonah <sundayonah94@gmail.com>
* feat(utils): add formatUsdAmount function for formatting USD amounts in KYC UI
- Introduced a new utility function, formatUsdAmount, to format USD amounts with up to 4 decimal places and comma separation.
- Updated ProfileDrawer and TransactionLimitModal components to utilize formatUsdAmount for displaying monthly limits and transaction summaries, enhancing consistency in currency formatting across the application.
* feat(kyc): enhance KYC document handling and UI improvements (#511)
- Added CSS styles to prevent 100vh clipping for mobile in the KYC modal's smart camera component.
- Updated the KYC document upload process to only accept utility bills, removing bank statements and PDF support.
- Refined the formatUsdAmount function to limit decimal places to 2 for better consistency in KYC UI.
- Improved user feedback in KYC modals and transaction forms, ensuring clarity on document requirements and verification status.
- Introduced a new KYCStatusSnapshot interface for synchronous KYC status updates, enhancing the user experience during verification processes.
* feat: implement referral system with API endpoints and UI components (#433)
* feat: implement referral system with API endpoints and UI components
* Added new API routes for submitting and retrieving referral data.
* Introduced referral-related types and utility functions for handling referral codes.
* Created UI components for referral dashboard and call-to-action, enhancing user engagement.
* Updated mobile dropdown and main page content to include referral options and modals.
* Enhanced middleware to support new referral API routes.
* feat: enhance referral system and error handling
* Added optional role property to referral data structure for better clarity.
* Improved clipboard copy functionality with error handling for referral codes and links.
* Updated referral data retrieval to include role information for referrers and referred users.
* Enhanced error handling in the ReferralDashboard and ReferralDashboardView components for better user feedback.
* feat: improve referral API and dashboard functionality
* Refactored referral API routes to utilize a new method for retrieving user IDs from requests.
* Updated referral data retrieval to ensure accurate wallet address handling.
* Enhanced error handling for referral code generation and transaction volume checks.
* Improved user feedback in the ReferralDashboard and ReferralDashboardView components for better user experience.
* Standardized handling of referral amounts to ensure consistency across the application.
* feat: add referral volume and reward configuration to environment and API
* Introduced new environment variables for minimum qualifying volume and reward amount in the referral system.
* Updated types to include referral configuration parameters.
* Refactored referral claim logic to utilize new configuration values for volume checks and reward distribution.
* Enhanced referral data retrieval to reflect user-specific claim statuses and auto-claim functionality for eligible referrals.
* feat: enhance referral program configuration and error handling
* Updated .env.example to better document referral program variables.
* Improved KYC status verification in the referral claim API with enhanced error handling for better user feedback.
* Refactored referral configuration values to ensure proper parsing and validation in the application.
* refactor: rename avatar image utility function and update references
- Renamed `getAvatarImage` to `getAvatarImageFromAddress` for clarity in its purpose.
- Updated all references to the renamed function across components, ensuring consistent usage.
- Added comments to improve code documentation and understanding of avatar image retrieval logic.
* fix(referral): use config reward amount and normalize self-referral check
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(referral): address PR review feedback on modal UX and copy
Remove duplicate submit toast, show referral prompt on login without
requiring network modal, fix unescaped apostrophe, and standardize
qualifying volume copy and env default to $20.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(referral): make submit insert atomic and handle unique conflicts
Remove race-prone preflight read, return 409 on referred_wallet_address
unique violations, and add a case-insensitive unique index migration.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Isaac Onyemaechi <amaechiisaac450@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(kyc): update layout and styling for KYC modal and profile drawer (#512)
* refactor(kyc): update layout and styling for KYC modal and profile drawer
- Adjusted CSS properties for the smart camera component to improve height handling.
- Enhanced KYC modal layout with updated maximum height settings for better responsiveness.
- Modified ProfileDrawer to allow vertical scrolling, ensuring content accessibility without overflow issues.
- Improved styling for the smart camera component within the KYC modal for a more consistent user experience.
* fix(kyc): correct max-height CSS property in KYC modal layout
- Updated the max-height property in the KYC modal to use 'max-h-fit' for improved layout consistency and responsiveness.
- Ensured that the modal maintains a better visual structure when displaying content based on user needs.
---------
Co-authored-by: chibie <chibuotu@gmail.com>
* fix(privy): close getPrivyUserIdFromRequest function to improve code structure (#513)
* feat: enable on-ramp on Starknet. (#504)
* refactor(validation): streamline wallet address validation logic
- Removed Starknet-specific checks from various components and hooks, simplifying the handling of on-ramp conditions.
- Replaced the deprecated `isValidEvmAddressCaseInsensitive` function with a new `validateWalletAddress` function that supports both EVM and Starknet address formats.
- Updated the `RecipientDetailsForm` and `TransactionForm` components to utilize the new validation logic, enhancing code clarity and maintainability.
- Removed unused Starknet-related props and conditions across components to improve overall code efficiency.
* refactor(TransactionForm): update connected wallet address handling
* refactor(wallet): enhance useWalletAddress hook and update TransactionForm
* refactor(TransactionForm): update wallet address validation logic
---------
Co-authored-by: Chibuotu Amadi <chibuotu@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fixes (#515)
* feat(starknet): add Earn product via Vesu lending pools (#499)
* feat(starknet): add Earn product via Vesu lending pools
Adds an "Earn" action in the Starknet wallet drawer that lets users supply
USDC and USDT to Vesu lending pools through the starkzap SDK:
- USDC → Clearstar USDC Reactor pool
- USDT → Prime pool
Surfaces:
- New "Earn" action button in the wallet sidebar (Starknet only).
- Earn modal with deposit/withdraw tabs, token selector, live APR /
monthly / yearly projection, and a success view mirroring Transfer.
- New "Earn activity" tab next to Balances and Transactions, with a
per-token "Currently supplied" card and a date-grouped list of past
deposits/withdrawals. Clicking a row opens a detail page with a
Voyager link, mirroring the Transactions detail flow.
Integration:
- starkzap SDK (^3.0.0) for Vesu deposit/withdraw call preparation,
market metadata, and position reads.
- next.config aliases shim out starkzap's optional peer deps so Webpack
resolves cleanly without bloating node_modules.
- Reuses existing noblocks Privy primitives (getStarknetWallet, rawSign,
buildReadyAccount, setupPaymaster, deployReadyAccount). No changes to
wallet creation, auth, or signing pipelines.
Includes scripts/test-starkzap.ts: a read-only integration smoke test
that hits Vesu mainnet (~2s) to verify SDK call shapes and pool wiring.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(starknet/earn): drop client-supplied address, derive server-side
Address reviewer feedback from @0xLucqs on starkience/noblocks#1:
the deposit/withdraw routes accepted a wallet address from the request
body without verifying it matched the one derived by buildReadyAccount.
No attack vector (auth gates the route), but a buggy client could send
a mismatched address and credit a Vesu position to the wrong owner.
Cleanest fix per his suggestion: remove the field. Compute the canonical
address server-side via computeReadyAddress(walletPublicKey, classHash)
using the publicKey fetched from Privy in getStarknetWallet. That value
equals what buildReadyAccount derives, so mismatch is now impossible by
construction. Drops a redundant validateAndParseAddress block and the
unused starknet/validateAndParseAddress import; also drops the unused
deployReadyAccount import in withdraw.
Client (useEarnHandler) no longer sends address in deposit/withdraw
request bodies. UI readiness gate keeps the address check (it gates the
button, not the payload).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(starknet/earn): address CodeRabbit review on PR #499
- deposit/withdraw routes: log a warning when waitForTransaction soft-fails,
matching the existing pattern in app/api/starknet/transfer/route.ts
instead of silently swallowing the error
- EarnActivityDetails: add aria-label to icon-only copy button
- EarnActivityPanel: clickable row uses motion.button (with type="button"
and w-full/text-left/bg-transparent so the layout matches the prior
motion.div) so the row is keyboard-operable
- earn.ts: log getPositions failures via console.error before falling back
to the empty positions array, so production debugging can distinguish
"user has no position" from "fetch failed"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(starknet/earn): honest confirmation flag + lossless Starknet balance
CodeRabbit follow-up on PR #499:
1. Deposit / withdraw responses now include a `confirmed: boolean` field.
When `waitForTransaction` throws (network error, RPC hiccup), the
handler still returns `success: true` with the tx hash so the client
gets the optimistic UX, but `confirmed: false` so the response is
honest about the on-chain state. The client can ignore the field
today (preserves current UX) or surface it in a future pass.
2. Starknet wallet balances now carry `balancesInWei: Record<string,
bigint>` end-to-end:
- `fetchStarknetBalancesUnified` (app/utils.ts) was already computing
the per-token bigint internally; it now stores and returns it,
mirroring the EVM path.
- `fetchStarknetBalance` wrapper exposes the new field.
- `WalletBalances` interface (app/context/BalanceContext.tsx) declares
`balancesInWei?` as an optional field, so existing consumers are
unaffected.
- `EarnWalletForm` reads `allBalances.starknetWallet?.balancesInWei?.[token]`
directly instead of the lossy `parseAmountToBaseUnits(walletBalanceUnit.toString())`
round-trip. The Max button and amount validation now use exact base
units, eliminating precision loss at the 6th USDC decimal.
The `WalletBalances` change is backward-compatible (optional field with
a `BigInt("0")` fallback in the consumer); other balance consumers
(Transfer, swap, balance display) are unaffected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(balances): preserve balancesInWei end-to-end on EVM and Starknet
CodeRabbit follow-up on PR #499: the previous commit added
`balancesInWei?` to the WalletBalances interface and plumbed it through
the Starknet path, but EVM balances still discarded it in
`buildWalletBalancesFromRaw`. That made the JSDoc claim ("Populated by
Starknet and EVM fetchers") misleading and left a latent precision gap
for any future caller doing exact-integer math on EVM amounts.
Concretely:
- `buildWalletBalancesFromRaw` now accepts an optional `balancesInWei`
parameter and returns it on the WalletBalances object. The CNGN
conversion logic is unchanged; base units bypass the float corridor
entirely (correct, since CNGN <-> NGN is a display-only rate).
- All 7 call sites in BalanceContext.tsx that build WalletBalances from
a `fetchWalletBalance` result now pass `result.balancesInWei` through.
- The two patch sites in the `[cngnRate]` useEffect (re-applying the
rate after it resolves) preserve `balancesInWei` from the existing
WalletBalances object.
- `fetchStarknetBalancesUnified` (utils.ts) populates `balanceWei` on
each ChainBalanceEntry, matching the EVM entries shape so per-token
exact-integer math is available downstream.
All changes are backward-compatible: `balancesInWei` is optional on
WalletBalances, and existing consumers that read only `balances`
(display number) are unaffected. The roadmap to bridge EVM USDC into
Vesu via the Earn product can now read `allBalances.<chain>?.balancesInWei?.[token]`
on any network without further infrastructure changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(starknet/earn): return 502 when waitForTransaction confirmation fails
Addresses CodeRabbit's follow-up on PR #499 (after @onahprosper asked
for a recheck). The previous approach (return `success: true,
confirmed: false`) relied on a client-side check that does not exist
in useEarnHandler, so withdrawals where on-chain confirmation failed
were still being recorded as completed activity in the UI.
Switching to CodeRabbit's original suggestion of an HTTP 502 response:
the failure is now honest at the protocol level, the existing client
check (`!res.ok || !data?.success`) treats it correctly without any
client change, and the response body still carries `transactionHash`
so callers can point the user to the explorer.
Drops the now-unused `confirmed` flag from both deposit and withdraw
response shapes. Same simplification applied to both routes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(starknet/earn): drive token list from Paycrest aggregator
Addresses @onahprosper's review feedback on PR #499: replaces the
hard-coded EARN_TOKENS dropdown with a runtime fetch of Paycrest's
supported-token list (via the existing `getNetworkTokens("Starknet")`
helper, which already wraps `api.paycrest.io/v2/tokens` with cache +
fallback).
New `useEarnAvailableTokens()` hook in useEarnHandler.ts:
- Fetches Paycrest's Starknet token list on mount.
- Intersects with `EARN_TOKENS` (the registry of tokens we have Vesu
pool mappings for) so we never expose a token without a pool.
- Falls back to the full `EARN_TOKENS` list if Paycrest is unreachable
or returns no Starknet tokens.
EarnWalletForm.tsx now consumes the hook for both the dropdown items
and the `onSelect` handler (the previous `name === "USDT" ? ... : ...`
hardcode is replaced with a lookup against the dynamic list).
Effect: when Paycrest adds a new Starknet token AND we add a matching
Vesu pool mapping to `EARN_TOKEN_CONFIG`, the new token appears in
the UI with no further code change. The `EARN_TOKENS` registry remains
the source of truth for which tokens our backend can build calls for;
it is kept static (and exported) for type-level coverage and for the
position-refresh / activity-filter loops that should iterate every
supported token regardless of Paycrest's current advertisement.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Update app/api/starknet/earn/deposit/route.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* refactor(starknet): extract applySafetyMargin to shared utility; gate fee estimation on isDeployed
Addresses two CodeRabbit comments on PR #499:
1. Fee estimation runs on undeployed accounts without deployment context, and
the result is discarded on the deploy path.
In `app/api/starknet/earn/deposit/route.ts`, the fee-estimation block now
only runs when `isDeployed && !isSponsored`. On the first-deposit path
(account not yet deployed), `deployReadyAccount` already builds its own
`deploy_and_invoke` paymaster transaction with proper `deploymentData`
and performs its own deployment-aware fee estimation internally, so
estimating here without `deploymentData` produced an inaccurate fee that
was never used. Withdraw is unaffected because it already rejects
undeployed accounts upfront.
2. `withMargin15` was misleadingly named and duplicated across 5 sites.
The helper actually applies a 1.5x (50%) safety margin via
`(bi * 3 + 1) / 2`, not 15%. Extracted into a single shared
`applySafetyMargin(v)` export in `app/lib/starknet.ts` with a clarifying
doc comment. All 5 call sites updated to import and use it:
- `app/lib/starknet.ts:deployReadyAccount`
- `app/api/starknet/transfer/route.ts`
- `app/api/starknet/create-order/route.ts`
- `app/api/starknet/earn/deposit/route.ts`
- `app/api/starknet/earn/withdraw/route.ts`
The local duplicate copies are removed; the formula is unchanged. This
keeps the cross-route fee-margin behaviour consistent and gives the
helper an honest name that matches the math.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(earn): implement Earn feature with consent modal and UI components
- Introduced EarnConsentModal to handle user consent for using the Earn feature.
- Updated MobileDropdown and WalletDetails components to integrate Earn UI and manage user interactions.
- Enhanced state management for Earn activities and consent handling through useEarnAccess hook.
- Configured environment variables for Earn feature toggling.
* fix(earn): update EarnConsentModal risk copy and enhance MobileDropdown and WalletDetails components
* fix(earn): update risk copy in EarnConsentModal and enhance balance checks in EarnWalletForm and EarnHubView components
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Prosper <40717516+onahprosper@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Isaac Onyemaechi <amaechiisaac450@gmail.com>
Co-authored-by: Chibuotu Amadi <chibuotu@gmail.com>
* feat(kyc): enhance SmileID verification process and error handling (#516)
* feat(kyc): enhance SmileID verification process and error handling
- Introduced a retry mechanism for SmileID job submissions when database-related failures occur, improving user experience during backend outages.
- Added a classification function to categorize SmileID verification failures, providing clearer feedback on the nature of errors.
- Updated KycModal to display specific failure reasons and hints based on the error type, enhancing user guidance during document verification.
- Improved error handling in KycModal to set and display failure messages more effectively, ensuring users receive relevant information for troubleshooting.
* refactor(kyc): improve error classification and user feedback in SmileID verification
- Enhanced the classifySmileIdFailure function to better categorize database-related errors, improving clarity on failure reasons.
- Updated KycModal to provide more specific failure hints based on error types, enhancing user guidance during verification.
- Adjusted messaging for verification failures to ensure users receive relevant and actionable feedback.
* fix(kyc): improve attempt counter restoration for SmileID verification failures
- Updated logic to restore the attempt counter for users experiencing infrastructure outages, regardless of job type.
- Enhanced error handling to log failures when restoring the attempt counter, providing better visibility into issues during SmileID verification.
* feat(referrals): update referral components and replace ReferralDashboard with ReferralHubView (#517)
- Added support for "swap" transaction type in getTransactionHistoryTypeLabel function.
- Replaced ReferralDashboard with ReferralHubView in MobileDropdown and WalletDetails components for improved referral management.
- Removed unused ReferralDashboard and ReferralDashboardViewSkeleton components to streamline the codebase.
- Enhanced ReferralCTA to directly trigger the new referral view, improving user navigation.
* fix(wallet): restore network-aware address in settings dropdown
The desktop settings dropdown dropped the useWalletAddress() hook (KYC #496)
and inlined EVM-only logic, so on Starknet it showed the EVM smart-wallet
address instead of the Starknet Ready account. Restore the hook (which already
handles the Starknet branch) with the migration-based EVM fallback, matching
WalletDetails and MobileDropdown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(referral): gate modal on server-side referral status to prevent cross-device reappearance and delayed load (#518)
* fix(referral): gate modal on server-side referral status to prevent cross-device reappearance and delayed load
* address review comments
* address review comments
* fix(ui): show correct address and networks in wallet copy modal (#519)
* fix(ui): show correct address and networks in wallet copy modal
- Replace manual EVM-only walletAddress derivation in SettingsDropdown
with useWalletAddress() hook so the Starknet address is shown/copied
when on Starknet network
- Filter Starknet out of supportedNetworksDisplayed in
CopyAddressWarningModal when the active network is EVM, so Starknet
is never listed as a deposit option for an EVM address
- Restore scroll position after KYC "Let's go!" closes the dialog —
SmileID's camera component displaces document.scrollY; a
requestAnimationFrame scrollTo(0) fires after Headless UI unlocks scroll
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(settings): update wallet address logic to handle Starknet network
- Introduced useNetwork context to determine the selected network.
- Adjusted wallet address derivation to ensure that an EVM address is not shown when on the Starknet network, improving user experience and accuracy in address display.
- Simplified fallback logic for wallet address to prioritize network-aware handling.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* updated privacy policy and terms (#509)
* updated privacy policy and terms
* feat(kyc): add KYC policy page and update routing
- Introduced a new KYC policy page with comprehensive details on data handling and user privacy.
- Updated the main content to include the new KYC policy route.
- Enhanced the sitemap to include the KYC policy URL for better SEO.
- Modified the KycModal to link to the new KYC policy instead of the privacy policy.
- Added a CI workflow for continuous integration and testing on main and stable branches.
* feat(ci): configure environment variables for CI workflow
- Added environment variables for various services including database, API keys, and URLs to the CI workflow.
- Included dummy values for build-time variables that are not stored in repository secrets to ensure successful builds.
* fix(ci): update Privy SDK environment variables in CI workflow
- Changed dummy values for NEXT_PUBLIC_PRIVY_APP_ID and PRIVY_JWKS_URL to valid placeholders required by the Privy SDK.
- Ensured that the CI workflow is correctly configured for integration with the Privy service.
* refactor(kyc): update KYC verification process and improve UI components (#520)
* refactor(kyc): update KYC verification process and improve UI components
- Replaced the KYC status check with a direct query to Supabase for user verification, enhancing reliability.
- Updated error handling to provide clearer messages regarding KYC requirements.
- Refactored the CopyAddressWarningModal and EarnConsentModal components to improve checkbox styling and accessibility.
- Enhanced the KycModal to restore scroll position after closing, ensuring a smoother user experience.
- Added a useEffect in MobileDropdown to reset the view state when the dropdown closes, improving navigation consistency.
* refactor(ui): enhance checkbox accessibility and scroll restoration in modals
- Updated checkbox styling in CopyAddressWarningModal for improved focus visibility and accessibility.
- Introduced a new function in KycModal to restore scroll position after closing, enhancing user experience during modal transitions.
* feat(MobileDropdown): reset nested views on sheet close
- Added a useEffect hook to reset the current view to "wallet" and clear selected activities when the dropdown sheet closes. This ensures a consistent starting point for users when reopening the dropdown.
* refactor(referral): enhance claim qualification logic and error handling (#522)
* refactor(referral): enhance claim qualification logic and error handling
- Introduced a new `checkPartyQualification` function to streamline KYC and volume checks for both claimants and referees.
- Updated error responses to provide clearer messages based on the qualification subject (claimant or referee).
- Refactored the `tryClaimOne` function to utilize the new qualification checks, improving code readability and maintainability.
- Adjusted comments and documentation to reflect the changes in the claim process and qualification requirements.
* refactor(referral): improve error handling and qualification checks in claim process
- Enhanced the `checkPartyQualification` function to include timeout handling and improved error messages for failed rate lookups.
- Updated the `tryClaimOne` function to streamline qualification checks and provide clearer error responses when verification fails.
- Refactored the logic to ensure that invalid NGN rate payloads are properly handled, improving overall robustness of the claim process.
- Adjusted comments for better clarity on the qualification checks and their implications in the claim workflow.
---------
Co-authored-by: Isaac Onyemaechi <amaechiisaac450@gmail.com>
* refactor(MobileDropdown): streamline component structure and improve view handling
- Simplified the rendering logic within the MobileDropdown component by removing unnecessary motion animations and restructuring the JSX for better readability.
- Enhanced the handling of different views (wallet, referrals, earn) to ensure consistent overflow behavior and improved user experience.
- Updated the DialogPanel and view components to maintain a cleaner layout and better manage state transitions.
* refactor(TransferForm): improve recipient address validation and error handling
- Added `dirtyFields` to form state to track changes in the recipient address field.
- Introduced a new `showRecipientAddressError` variable to conditionally display error messages based on the recipient address's validity and its dirty state.
- Updated the `useEffect` hook to trigger validation only when the recipient address is not empty, enhancing performance and user experience.
* Audit of the entire kYC | Earn | Referral flow (#524)
* fix(kyc): reliable scroll restore and fresh tier status after verification
- Move scroll restoration out of KycModal's self-cancelling 500ms timer into
AnimatedModal via AnimatePresence onExitComplete (opt-in restoreScrollOnClose).
The old timer was cleared by the component's own unmount cleanup, racing the
exit animation, and only covered two of the modal's close paths.
- Restore the scroll position captured at open instead of jumping to top.
- Remove the outer AnimatePresence/conditional around the KycModal mounts so the
exit animation (and scroll restore) actually runs; drop TransactionLimitModal's
early return-null for the same reason.
- Force-refresh KYC status when ProfileDrawer and TransactionLimitModal open so
a tier upgrade landing via the SmileID webhook is not hidden by the context's
30s staleness cache.
- Sync KYCContext when KycModal's poller detects the tier upgrade (previously
the modal could show success while the rest of the app showed the old tier).
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix(kyc): enforce one verified identity per phone/ID and harden SmileID callback
- Add pending_phone_number staging: send-otp no longer overwrites the verified
phone_number (or de-verifies the profile); verify-otp promotes the pending
number only after the OTP is confirmed. Previously a user who started (but
never finished) verifying a replacement number kept their tier and the API
reported the unverified number as verified.
- Enforce one verified profile per phone number (send-otp pre-check, verify-otp
promotion guard) and per ID document (smile-id route), with partial unique
indexes created by migration when no historical duplicates exist. Previously
the same phone/ID could verify unlimited wallets, multiplying monthly limits.
- Harden the SmileID callback: reject stale/unparseable timestamps (the HMAC
only covers timestamp+partner_id, so captured signatures replay forever) and
confirm the job outcome via SmileID's signed job_status API before any tier
promotion instead of trusting unsigned body fields.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix(referral,earn): correct modal gating, pending totals, and per-user earn consent
Referral modal:
- Suppress for ANY referral relationship, not just role === 'referred', so
existing users who have referred others no longer see the new-user modal.
- Require a known account age within 30 days; previously a missing createdAt
skipped the new-user check entirely and let existing accounts through.
Network selector (root cause of 'referral popup only after refresh'): gate the
open trigger on Privy 'ready' and re-check once the embedded wallet address
settles, so a fresh signup reliably opens the network modal — the referral
modal chains off its close callback.
Referral Pending/Earned cards: coerce reward_amount to Number in the API totals
(Postgres numeric deserializes as string via PostgREST, so the reduce was
string-concatenating and corrupting total_pending), and harden the card
formatting against non-numeric values.
Earn disclaimer: key the 'seen' flag per Privy user id instead of a single
device-global key, so a second user on the same device (and brand-new signups)
see the risk disclosure instead of inheriting the first user's acceptance.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix: verification-pass corrections for KYC and network-modal gating
- smile-id route: keep idNumberToStore undefined (not null) when SmileID
returns no ID number, so the update no longer nulls out a previously stored
id_number (regression introduced with the uniqueness check).
- smile-id callback: accept SmileID's string 'true' job flags when confirming
job_status, matching how the rest of the codebase treats these fields.
- Network modal dismissal: wire the never-called markNetworkModalDismissed()
into NetworkSelectionModal.handleClose, and centralize the hasSeenNetworkModal
key in networkModalStore with a canonical lowercased form (legacy checksummed
keys still honored on read). Previously MigrationBannerWrapper's two gates
were both dead — the live store signal never fired and the storage fallback
lowercased a key written checksummed — so migration UI gated on network-modal
dismissal could not appear in-session, and the fresh-signup reset in Navbar
removed a key form that was never written.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix: update phone number placeholder text for consistency
- Changed the placeholder text in the PhoneVerificationModal from "enter your phone number" to "Enter your phone number" to ensure consistent capitalization and improve user experience.
* fix: address CodeRabbit review on PR #524
- verify-otp: gate phone promotion on the current pending_phone_number and
treat zero updated rows as a superseded verification, so a stale OTP can no
longer promote an old number over a newer pending request.
- NetworkSelectionModal: replace the sticky hasCheckedStorage boolean with a
wallet-scoped sentinel so eligibility re-evaluates after logout/login or a
wallet switch.
- networkModalStore: wrap localStorage access in try/catch helpers so
restricted-storage environments degrade gracefully instead of throwing.
- smileID: only accept 0/1 or an http(s) URL for SMILE_IDENTITY_SERVER(_MODE);
unrecognized values now surface the configuration error instead of silently
defaulting to sandbox.
- smile-id callback: the unsigned body is now used for routing only — the job
outcome AND the full_name/date_of_birth enrichment come exclusively from
SmileID's signed job_status response.
- migration: RAISE EXCEPTION (fail deploy) when historical duplicate verified
identities exist, instead of warning and skipping the unique indexes.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix: update phone number placeholder text for consistency
- Changed the placeholder text in the PhoneVerificationModal from "enter your phone number" to "Enter your phone number" to ensure consistent capitalization and improve user experience.
* fix: update Earn disclosure URL for improved accessibility
- Changed the Earn risk disclosure article link from a Google Document to a dedicated blog page on noblocks.xyz, enhancing user access to important information.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(ux): stop network modal re-opening on login and pin body scroll behind locked modals
- NetworkSelectionModal: pass the raw wallet address into
hasSeenNetworkModalFlag — pre-lowercasing it defeated the legacy
checksummed-key fallback, so the modal re-opened on login for every
pre-existing account (regression from the wallet-scoped sentinel fix).
Only the sentinel comparison is case-normalized now.
- AnimatedModal: replace restore-scroll-on-close with a body scroll lock
(position: fixed at the captured offset) held from open through the end of
the exit animation. The document can no longer be dragged to the bottom by
modal content (e.g. the SmileID camera), and the visible jump from the old
after-the-fact scrollTo restore is gone — the page never moves. Refcounted
so overlapping locked modals share one pin; released on exit complete with
an unmount safety net. Prop renamed restoreScrollOnClose -> lockBodyScroll.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* Update CODEOWNERS to include additional reviewers for pull requests (#525)
* Update CODEOWNERS to include additional reviewers for pull requests
* fix: apply CodeRabbit auto-fixes
Fixed 2 file(s) based on 1 unresolved review comment.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* fix(referral): carry ?ref= code from share links into the referral modal
Share links are generated as ?ref=NBXXXX (handleCopyLink) but nothing ever
consumed the parameter — the only reader of ref was the BlockFest hook — so
users arriving via a referral link still had to type the code manually.
- Capture ?ref= on landing in MainPageContent (before login, since OAuth can
drop the query string) and persist it as a pending code in localStorage.
- Pre-fill the ReferralInputModal input from the pending code when it opens,
and re-open the modal for a link-carried code even if this wallet dismissed
it before (clicking a referral link is explicit intent).
- Clear the pending code on successful submit and on explicit skip/backdrop
close so a declined code cannot keep re-triggering the modal.
- Shared format guard (NB + 4 alphanumerics) keeps BlockFest's ?ref=blockfest
flow untouched and replaces the inline regex in the modal.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix: address CodeRabbit review on PR #526
- AnimatedModal body lock: snapshot pre-existing inline body styles on first
acquire and restore them on final release instead of blanking them; acquire
the lock in an isomorphic layout effect so the pin lands before the open
modal's first paint (no gap for mount-time autofocus/camera scroll).
- networkModalStore: match the hasSeenNetworkModal key case-insensitively on
read and clear, so legacy keys in any historical address casing are honored
regardless of how Privy cases the address across hydration/login.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
* fix(kyc): update transaction status filters to accurately reflect monthly spend limits
- Adjusted the status filters for offramp and onramp transactions to include 'pending', 'fulfilling', 'fulfilled', and 'completed' statuses, ensuring that all relevant transactions contribute to the monthly spend limit.
- Updated the SQL migration to relax the transactions.status CHECK constraint, allowing for accurate status updates without constraint violations.
- Enhanced the logic in the transaction summary API to align with the new status definitions, improving the accuracy of spend calculations.
* refactor(kyc, referral): normalize currency casing and improve transaction filtering
- Introduced a normalization function for currency values to ensure consistent casing across different sources, addressing discrepancies between "cNGN" and "CNGN".
- Updated transaction filtering logic in both the KYC transaction summary and referral claim APIs to utilize Sets for improved performance and clarity.
- Adjusted transaction processing to apply normalization when handling currency values, enhancing the accuracy of USD calculations and overall transaction integrity.
* feat(login): implement body scroll pinning during Privy login flow
- Introduced a new hook, useLoginWithScrollPin, to manage body scroll locking while the Privy login dialog is active, preventing unwanted scrolling on mobile devices.
- Updated AnimatedModal to default lockBodyScroll to true, ensuring modals maintain scroll lock by default.
- Refactored Navbar and TransactionForm components to utilize the new loginWithScrollPin function for improved user experience during login.
* fix(login): enhance login flow with in-flight guard and error handling
- Added an in-flight guard to prevent multiple rapid login attempts while the pin is held.
- Implemented error handling during the login process to ensure proper release of the scroll lock in case of failures.
* Referrers should only require one qualifying transaction
* Replace Noblocks logos in Navbar with SVG image and adjust dropdown arrow position. Add new SVG logo file for Noblocks World Cup logo.
* feat(kyc): enhance KYC status and phone verification flow
- Updated the KYC status API to include the user's full name in the response.
- Modified the PhoneVerificationModal to conditionally display a full name input field based on the user's existing data.
- Improved user prompts and information displayed during the phone verification process to enhance clarity and user experience.
- Adjusted KYC context to manage full name state alongside other KYC details.
* feat(icons): add animated Noblocks logo and World Cup assets
- Introduced a new animated icon component that cycles through three states: the default Noblocks "n" icon, a spinning soccer ball, and the World Cup trophy.
- Created separate components for the Noblocks World Cup logo and the animated icon, ensuring cross-browser compatibility by injecting SVGs directly into the DOM.
- Updated the Navbar to utilize the new animated logo and World Cup logo components, enhancing the visual appeal and user experience.
- Added CSS animations for the spinning effect and implemented reduced motion preferences for accessibility.
* refactor(icons): enhance NoblocksAnimatedIcon for reduced motion support and update World Cup logo SVG
- Improved the NoblocksAnimatedIcon component to reactively handle changes in the user's motion preference, pausing animations when reduced motion is enabled.
- Updated the World Cup logo SVG to include a new animation class for counter-rotation, enhancing visual dynamics.
- Adjusted the fetch URL for the World Cup logo to ensure the latest version is used.
* feat(referral): implement referral program feature flag and UI integration (#531)
* feat(referral): implement referral program feature flag and UI integration
- Added a feature flag for the referral program in the environment configuration.
- Updated middleware to bypass authentication for referral routes when the feature is disabled.
- Integrated referral feature checks across various components and API routes to conditionally render referral UI and handle requests.
- Enhanced type definitions to include referral program status for better configuration management.
* refactor(referral): streamline referral feature integration and error handling
- Updated middleware to return a 404 response for referral routes when the feature is disabled, enhancing user feedback.
- Introduced a new utility function, isReferralEnabled, to centralize feature flag checks across components and API routes.
- Removed the deprecated referralFeature module, consolidating referral logic within the utils for better maintainability.
- Adjusted environment configuration to ensure consistent handling of the referral feature flag.
* refactor(referral): rename environment variable for referral feature flag
---------
Co-authored-by: chibie <chibuotu@gmail.com>
* fix(referral): enhance error handling in referrer unlock process
- Updated the ReferrerUnlockResult type to include error handling for profile fetch and verification failures.
- Modified the checkReferrerUnlock function to return specific error codes and messages for better clarity on failure reasons.
- Adjusted the tryClaimOne function to handle transient verification failures distinctly from genuine unlock status, improving user feedback.
* feat(utils): add normalizeStarknetAddressOrNull function and update E… (#538)
* feat(utils): add normalizeStarknetAddressOrNull function and update ExportStarknetWalletModal and StarknetContext to use it
- Introduced normalizeStarknetAddressOrNull to handle null or undefined addresses gracefully.
- Updated ExportStarknetWalletModal to utilize the new function for address normalization, improving address handling and user feedback.
- Refactored StarknetContext to normalize wallet addresses upon retrieval, ensuring consistent address formatting across the application.
* refactor(utils): remove normalizeStarknetAddressOrNull and update address normalization in components
* feat(kyc): implement unlimited monthly limit for tier 3 KYC users
- Updated KYC tier configuration to allow tier 3 users to have an unlimited monthly spend limit by accepting the "unlimited" sentinel in environment variables.
- Modified KYC-related components to display "Unlimited" for tier 3 users when applicable.
- Refactored limit-checking logic in the backend to accommodate the new unlimited tier, ensuring proper handling of transactions without caps.
- Added database migration to support the new unlimited limit functionality.
* feat(countries): replace REST API with local country data from libphonenumber-js (#540)
- Implemented a new method to build the full country list using libphonenumber-js, eliminating the need for a network call due to CORS issues with the previous REST Countries API.
- Updated fetchCountries function to utilize the local country data, ensuring a consistent and reliable list of countries with calling codes and flags.
- Enhanced phone validation logic to correctly handle country calling codes for regions sharing the same code, specifically for the US and Canada.
* feat(utils): add function to format first word of recipient name in t… (#500)
* feat(utils): add function to format first word of recipient name in title case
- Introduced `formatRecipientNameFirstWordForPill` to extract and title-case the first word of a recipient's name.
- Updated `TransactionStatus` component to utilize the new formatting function for improved display of recipient names.
* refactor(wallet): streamline state reset in ExportStarknetWalletModal
- Introduced a `resetExportState` function to encapsulate the logic for resetting modal state variables.
- Updated the `useEffect` hook to simplify the closing behavior of the modal.
- Enhanced the `AnimatePresence` component to call `resetExportState` on exit completion for better state management.
---------
Co-authored-by: chibie <chibuotu@gmail.com>
* fix(swap): rename ramp toggle to Buy/Sell and trim rate suffix (#514)
Replace the "On-ramp"/"Off-ramp" toggle labels in the swap modal with
"Buy"/"Sell", and drop the " ~ 1 {token}" suffix from the preview rate.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(wallet): restore Starknet localStorage cleanup on logout
Clear cached Starknet wallet keys on logout (regression from KYC #496).
Also widen Buy/Sell toggle padding from px-3 to px-4.
---------
Co-authored-by: chibie <chibuotu@gmail.com>
Co-authored-by: Prosper <40717516+onahprosper@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: sundayonah <sundayonah94@gmail.com>
Co-authored-by: Onah Prosper <prosperauthor@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Starkience <129275548+starkience@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
|
Bugbot is not enabled for this team, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
📝 WalkthroughWalkthroughThis PR introduces tiered KYC verification (phone OTP, SmileID Tier 2, Dojah Tier 3), a Starknet Earn (Vesu) lending feature, a referral program, and refactors aggregator/transaction/order API routes with wallet-JWT authorization. It also updates navbar branding, wallet sidebar UI, CI workflow, and env configuration. ChangesConfig, CI & Shared Helpers
API Refactor
Tiered KYC
Starknet Earn
Referral Program
UI Platform
Balance Context
Estimated code review effort: 5 (Critical) | ~180 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant KycModal
participant SmileIDAPI as "/api/kyc/smile-id"
participant SmileIDService as smileID.ts
participant Supabase
participant CallbackRoute as "/api/kyc/smile-id/callback"
User->>KycModal: submit ID capture
KycModal->>SmileIDAPI: POST images + id_info
SmileIDAPI->>Supabase: load user_kyc_profiles, increment attempts
SmileIDAPI->>SmileIDService: submitSmileIDJob(...)
SmileIDService-->>SmileIDAPI: job_id, user_id
SmileIDAPI-->>KycModal: pending/job accepted
SmileIDService-->>CallbackRoute: async job callback
CallbackRoute->>SmileIDService: getSmileIdJobStatus(userId, jobId)
CallbackRoute->>Supabase: update tier, verified, platform
KycModal->>SmileIDAPI: poll /api/kyc/status
SmileIDAPI-->>KycModal: tier updated -> success
sequenceDiagram
participant User
participant EarnWalletForm
participant DepositRoute as "/api/starknet/earn/deposit"
participant EarnLib as earn.ts
participant Paymaster
participant Starknet
User->>EarnWalletForm: enter amount, submit deposit
EarnWalletForm->>DepositRoute: POST walletId, publicKey, amount
DepositRoute->>EarnLib: prepareVesuDepositCalls(...)
DepositRoute->>Paymaster: setupPaymaster, estimate fee
DepositRoute->>Starknet: executePaymasterTransaction / deployReadyAccount
Starknet-->>DepositRoute: transactionHash, receipt
DepositRoute-->>EarnWalletForm: success + txHash
EarnWalletForm->>EarnLib: refreshPosition(token)
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
⚔️ Resolve merge conflicts
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
app/globals.cssParsing error: This experimental syntax requires enabling one of the following parser plugin(s): "decorators", "decorators-legacy". (1:0) 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 |
There was a problem hiding this comment.
Actionable comments posted: 9
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
.env.example (1)
188-189: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRestore the Starknet env docs in
.env.example
app/lib/starknet.tsstill readsNEXT_PUBLIC_STARKNET_RPC_URL,STARKNET_PAYMASTER_API_KEY, andSTARKNET_GAS_TOKEN_ADDRESS; removing this section leaves fresh setups without the required configuration hints.🤖 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 @.env.example around lines 188 - 189, Restore the Starknet environment documentation section in .env.example so new setups still see the required configuration hints. Re-add the entries for NEXT_PUBLIC_STARKNET_RPC_URL, STARKNET_PAYMASTER_API_KEY, and STARKNET_GAS_TOKEN_ADDRESS in the Starknet-related block, keeping the existing env-doc structure intact so app/lib/starknet.ts users can configure it correctly.app/components/WalletDetails.tsx (1)
432-443: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the Starknet wallet total in Earn mode.
showEarnUiis Starknet-only, so this branch now showscrossChainTotalwhenever Earn is enabled and the Starknet-specific balance branch never runs. If the header should still mirror the Starknet balance shown elsewhere, swap the conditions or render a separate Earn total.🤖 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 `@app/components/WalletDetails.tsx` around lines 432 - 443, The balance rendering in WalletDetails should preserve the Starknet wallet total when Earn mode is enabled, since the current showEarnUi branch prevents the selectedNetwork.chain.name === "Starknet" path from ever running. Update the conditional logic around the balance display so Starknet still shows its own total in the header, either by checking the Starknet-specific branch before showEarnUi or by rendering a separate Earn total explicitly in this section.app/components/KycModal.tsx (1)
479-522: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winButton copy mismatch: disclaimer references "Sign and continue" but the CTA says "Accept and continue".
✏️ Suggested fix
- By clicking “Sign and continue” below, you are agreeing to the terms and policies above. + By clicking “Accept and continue” below, you are agreeing to the terms and policies above.🤖 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 `@app/components/KycModal.tsx` around lines 479 - 522, The CTA text in KycModal is inconsistent with the disclaimer copy: the terms text says “Sign and continue” while the primary action button says “Accept and continue.” Update the wording in the KycModal component so both references use the same action phrase, and make sure the button label and surrounding disclaimer match each other exactly.
🟡 Minor comments (13)
app/components/PhoneVerificationModal.tsx-603-609 (1)
603-609: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winReplace
w-mdwith a real width utility.w-mdisn’t defined here, so the class is ignored and the Cancel button keeps the basemin-w-fitsizing. Useflex-1orw-fullif it should match the Continue button’s layout.🤖 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 `@app/components/PhoneVerificationModal.tsx` around lines 603 - 609, The Cancel button in PhoneVerificationModal uses an undefined width class, so replace the w-md utility on the button with a real sizing class such as flex-1 or w-full to match the Continue button layout. Update the button markup in PhoneVerificationModal so the existing secondaryBtnClasses still apply while the width comes from a valid utility class.app/lib/_starkzap-unused-shim.ts-11-23 (1)
11-23: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
new Account(...)skips the shim error.Accountis backed by a proxy whose target is a plain object, sonew Account(...)throwsTypeError: Account is not a constructorbefore thegettrap runs. That bypasses the intended peer-dependency message; use a constructable target plus aconstructtrap (andapplyif it can also be called) so instantiation fails with the same error.🤖 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 `@app/lib/_starkzap-unused-shim.ts` around lines 11 - 23, The shim currently only handles property access, so `new Account(...)` bypasses the intended peer-dependency message and fails with a generic constructor error instead. Update the proxy in `_starkzap-unused-shim.ts` so the exported `shim`/`Account` value is constructable, and add a `construct` trap (and `apply` if the shim may be called as a function) that throws the same descriptive error as the existing `get` trap. Keep the error text centralized so `Account` instantiation and access paths all surface the same optional-dependency message..github/workflows/ci.yml-35-36 (1)
35-36: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winAdd
persist-credentials: falseto checkout.
actions/checkout@v4persists theGITHUB_TOKENcredential to disk by default. Since this job only runspnpm install/lint/buildand doesn't need git push/write, disable persistence to reduce the blast radius if a compromised dependency (e.g. a maliciouspostinstallscript) tries to exfiltrate it.🔒 Proposed fix
- name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false🤖 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 @.github/workflows/ci.yml around lines 35 - 36, The Checkout repository step using actions/checkout@v4 should disable credential persistence because this CI job only needs read access. Update the checkout configuration to set persist-credentials to false in the workflow so the GITHUB_TOKEN is not written to disk. Use the Checkout repository step in the workflow to locate the change.Source: Linters/SAST tools
app/lib/config.ts-53-64 (1)
53-64: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winNo non-negativity check for referral USD amounts.
Unlike
kyc-tier-limits.ts'sparseNonNegativeNumber(added in this same PR), these two parsers only verifyNumber.isFinite. A misconfigured or malformedNEXT_PUBLIC_REFERRAL_REWARD_AMOUNT_USD(e.g."-5") would silently become a negative on-chain reward/qualifying-volume amount.🛡️ Proposed fix
referralMinQualifyingVolumeUsd: (() => { const parsed = parseFloat( process.env.NEXT_PUBLIC_REFERRAL_MIN_QUALIFYING_VOLUME_USD ?? "", ); - return Number.isFinite(parsed) ? parsed : 0; + return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0; })(), referralRewardAmountUsd: (() => { const parsed = parseFloat( process.env.NEXT_PUBLIC_REFERRAL_REWARD_AMOUNT_USD ?? "", ); - return Number.isFinite(parsed) ? parsed : 0; + return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0; })(),🤖 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 `@app/lib/config.ts` around lines 53 - 64, The referral amount parsers in config.ts only check Number.isFinite, so negative values can slip through for referralMinQualifyingVolumeUsd and referralRewardAmountUsd. Update these inline parsers to use the same non-negative validation approach as parseNonNegativeNumber from kyc-tier-limits.ts, returning 0 or rejecting invalid input when the env var parses to a negative number, and keep the logic localized to the two referral config fields so misconfigured NEXT_PUBLIC_REFERRAL_* values cannot produce negative amounts.app/components/MainPageContent.tsx-393-399 (1)
393-399: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the same wallet source for the network-modal gate
user.wallet.addresscan differ from the activewalletAddressused by the rest of this flow, so this check should use the same source as the referral storage key. Otherwise the referral modal can bypass or miss the network-modal deferral for some wallet types.🤖 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 `@app/components/MainPageContent.tsx` around lines 393 - 399, The network-modal gate in MainPageContent is checking user.wallet.address, but the rest of this flow uses the active walletAddress source. Update this deferral check to use the same wallet identifier as the referral storage key and related modal logic, so the network-modal behavior stays consistent across wallet types.app/api/kyc/tier3-verify/route.ts-116-116 (1)
116-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Number(...) ?? 0does not guard againstNaN.
Number()never returnsnull/undefined, so??is dead here; iftierisundefined,currentTierbecomesNaN. It happens to still fail the!== 2check (returning 403), but the intent of defaulting to0isn't achieved. Use|| 0.🔧 Proposed fix
- const currentTier = Number(currentProfile.tier) ?? 0; + const currentTier = Number(currentProfile.tier) || 0;🤖 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 `@app/api/kyc/tier3-verify/route.ts` at line 116, The currentTier assignment in the tier3 verification route uses Number(currentProfile.tier) ?? 0, but nullish coalescing never catches NaN, so the intended default to 0 is not applied. Update the currentTier calculation in the tier3-verify route to use a fallback that handles invalid or missing tier values, such as a logical OR default, while keeping the existing tier check logic intact.app/api/aggregator.ts-507-599 (1)
507-599: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve HTTP status in the browser proxy path
fetchOrderDetailsturns non-2xx proxy responses into a plainError, soTransactionsContext.reconcileTransactionStatuseswon’t treat 404s as ignorableAxiosErrors. Attach the status to the thrown error or rethrow anAxiosError-shaped error so downstream 404 handling still works.🤖 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 `@app/api/aggregator.ts` around lines 507 - 599, In fetchOrderDetails, the browser proxy branch currently throws a plain Error on non-2xx responses, which strips the HTTP status needed by TransactionsContext.reconcileTransactionStatuses. Update the axios.get handling so the thrown error preserves the response status, either by rethrowing an AxiosError-shaped error or by attaching the status to the error object. Keep the same behavior in the gatewayLookup and non-gateway paths, but ensure downstream 404 checks can still detect the status from the error.app/context/TransactionsContext.tsx-211-216 (1)
211-216: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win404-ignore only covers the onramp path.
fetchV2SenderPaymentOrderByIdthrows anAxiosError, butfetchOrderDetailsrethrows a plainErrorfor HTTP >=400, so offramp 404s still reachconsole.erroron every poll. Preserve the status on that error or broaden the guard here.🤖 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 `@app/context/TransactionsContext.tsx` around lines 211 - 216, The 404 suppression in the transaction status reconciliation only catches Axios errors, so offramp 404s coming through fetchOrderDetails still get logged on every poll. Update either fetchOrderDetails / fetchV2SenderPaymentOrderById to preserve the HTTP status on the thrown error, or broaden the catch in the TransactionsContext reconciliation path to recognize the 404 from that flow. Use the existing symbols fetchV2SenderPaymentOrderById, fetchOrderDetails, and the console.error block in the transaction status polling logic to locate the fix.app/api/starknet/earn/withdraw/route.ts-89-100 (1)
89-100: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winClient
publicKeyis required but never used.
publicKeyis validated as required, yet the account address and signer are derived fromwalletPublicKeyreturned bygetStarknetWallet(walletId)(Lines 129–130, 152). The client-suppliedpublicKeyis dead input here. Either drop the requirement or use it (e.g., cross-check against the server-derived key).🤖 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 `@app/api/starknet/earn/withdraw/route.ts` around lines 89 - 100, The withdraw route currently treats client-supplied publicKey as required even though Starknet account details are built from getStarknetWallet(walletId) and walletPublicKey in route.ts. Update the handler in the withdraw route to either remove publicKey from the required-field validation and request contract entirely, or actually use it to verify against the server-derived walletPublicKey before proceeding. Make the change consistently in the validation block and any downstream logic that references the withdrawal signer/account setup.app/components/EarnDisclosureBanner.tsx-3-11 (1)
3-11: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winImport
Reactbefore usingReact.FC
app/components/EarnDisclosureBanner.tsx:11usesReact.FCbut the module doesn’t importReact, andallowUmdGlobalAccessisn’t enabled. Addimport React from "react";or switch toimport type { FC } from "react"to avoid a TypeScript error.🤖 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 `@app/components/EarnDisclosureBanner.tsx` around lines 3 - 11, The EarnDisclosureBanner component uses React.FC without importing React, which will fail type-checking in this module. Update the top of EarnDisclosureBanner.tsx by either adding a React import before the EarnDisclosureBannerProps/React.FC usage, or changing the component typing to use a type-only FC import from react; keep the fix aligned with the EarnDisclosureBanner export and its props interface.app/components/wallet-mobile-modal/WalletView.tsx-83-97 (1)
83-97: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRemove the unreachable tab reset —
showEarnUiis only true whenselectedNetwork.chain.name === "Starknet", soshowEarnUi && selectedNetwork.chain.name !== "Starknet"can’t fire here. Drop this effect, or flip the condition if the intent is to clear the tab when leaving Starknet.🤖 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 `@app/components/wallet-mobile-modal/WalletView.tsx` around lines 83 - 97, The tab-reset effect in WalletView is unreachable because showEarnUi and selectedNetwork.chain.name are already constrained so the condition can never be true. Remove the useEffect entirely if it is redundant, or update the logic around walletTab/setWalletTab to reset when the network changes away from Starknet if that is the intended behavior.app/components/ProfileDrawer.tsx-331-335 (1)
331-335: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winReplace this avatar
<img>withnext/image
This repo extendsnext/core-web-vitals, so@next/next/no-img-elementwill flag this and can fail lint/build in CI.🤖 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 `@app/components/ProfileDrawer.tsx` around lines 331 - 335, The avatar in ProfileDrawer should use Next’s optimized image component instead of a raw img element to satisfy next/core-web-vitals linting. Update the avatar markup in ProfileDrawer to replace the current img placeholder with next/image, keeping the same src, alt, and sizing/rounding behavior so the existing layout is preserved. Make sure the component imports and uses Image from next/image wherever this avatar is rendered.app/components/SettingsDropdown.tsx-167-167 (1)
167-167: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the embedded wallet address when clearing session keys.
localStorage["userId"]is written fromuser.wallet.address, and bothhasSeenNetworkModal-*andrecipientsMigrated-*are keyed from that value. Passing the network-awarewalletAddresscan leave those EVM-keyed entries behind on Starknet logout; useuser?.wallet?.addressfor cleanup or make the helper take the exact key source.🤖 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 `@app/components/SettingsDropdown.tsx` at line 167, The session cleanup in SettingsDropdown’s clearUserSessionData call is using the network-aware walletAddress fallback instead of the embedded wallet address that was used to write the localStorage keys. Update the logout cleanup path to use user?.wallet?.address for the session key source (or adjust clearUserSessionData to accept the exact key origin) so the hasSeenNetworkModal-* and recipientsMigrated-* entries are removed consistently.
🤖 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 `@app/api/kyc/smile-id/route.ts`:
- Around line 187-200: Normalize the SmileID biometric status checks in the KYC
route before setting verificationSuccess. In the smile-id route’s
enhanced/biometric branching, raw job_complete and job_success can be string
values, so replace the direct boolean check in the biometric path with the same
flag() normalization used in the callback flow. Keep the logic in the
smileIdResult/actions handling and ensure only a truly completed-and-successful
biometric job can promote Tier 2.
In `@app/api/kyc/tier3-verify/route.ts`:
- Around line 338-358: The outer catch in tier3-verify leaves uploaded KYC
documents orphaned and still consumes a verification attempt when
verifyUtilityBill throws. Update the route’s error handling around
verifyUtilityBill so that any thrown Dojah/network/non-2xx failure triggers
cleanup via storage.from(KYC_BUCKET).remove([path]) using the uploaded file
path, and restore the attempt counter on infrastructure failures just like the
smile-id flow. Keep the existing success and explicit-failure paths intact, but
ensure the catch block and any helper around verifyUtilityBill consistently roll
back both the uploaded object and the incremented attempt count when
verification cannot complete.
In `@app/api/kyc/transaction-summary/route.ts`:
- Around line 91-108: The onramp transaction summary logic in
transaction-summary/route.ts is treating unsupported destination currencies with
a simple amount_received fallback, which does not match the KYC limit procedure.
Update the onramp handling in the transactions mapping so it mirrors the same
branch used by the limit calculation for non-stable, non-CNGN currencies: use
the amount_sent/rate-style calculation for those rows instead of defaulting to
amount_received. Keep the existing normalization and filtering around
TRACKED_SWAP_CURRENCIES and the onrampTransactions mapping, but align the
spending fields so dailySpent/monthlySpent stay consistent with the procedure.
In `@app/api/referral/claim/route.ts`:
- Around line 385-446: The claim row handling in the referral claim flow is not
locking ownership before the transfer path, so concurrent callers can reuse the
same pending row and double-pay; update the logic around the
pendingClaimRow/pendingClaim setup to atomically transition the row into a
unique processing state with a conditional update that only succeeds from
pending or failed. If the transition returns no row, abort with a conflict/retry
response, and only continue to writeContract after the caller has exclusively
won the claim lock.
In `@app/api/referral/referral-data/route.ts`:
- Around line 279-291: The auto-claim fetch in the referral-data route is
building its base URL from request-controlled origin/host headers, which can
leak the Authorization token to an attacker. Update the self-call in
referral-data route to use a trusted server-configured base URL instead of
request.headers.get("origin") / host, and keep the Authorization forwarding only
for that safe internal target. Use the existing claimableReferrals block and the
fetch call to /api/referral/claim as the place to fix this.
In `@app/api/starknet/earn/deposit/route.ts`:
- Around line 238-286: The deposit POST in route.ts is not idempotent, so
retries can submit the same transaction twice before
`account.executePaymasterTransaction` or `deployReadyAccount` completes. Add a
persisted idempotency key or request record in the POST handler before the
transaction call, keyed off the incoming request so duplicate submissions can be
detected and short-circuited. Update the flow around `deployReadyAccount`,
`account.executePaymasterTransaction`, and the
`trackApiError`/`waitForTransaction` branches to return the prior result for
repeated requests instead of sending a second on-chain transaction.
In `@app/components/EarnWalletForm.tsx`:
- Around line 71-82: The withdraw flow is incorrectly limited to only
`availableEarnTokens`, so tokens with an existing non-zero supplied position can
disappear from the withdraw tab and selection. Update `EarnWalletForm` to union
the advertised tokens from `useEarnAvailableTokens` with any token from current
`positions` that still has `suppliedBaseUnits > 0`, and use that combined list
for the withdraw-side dropdown/tab and the `onSelect` logic. Keep the fix
localized around `tokenDropdownItems`, the withdraw tab rendering, and the
withdraw selection handlers in `EarnWalletForm`.
In `@app/components/MobileDropdown.tsx`:
- Around line 266-284: Reset the Earn-related mobile sheet state in
MobileDropdown when showEarnUi becomes false, since currentView can otherwise
remain stuck on earn, earn-deposit, or earn-withdraw and render nothing. Add the
same fallback pattern used in WalletDetails for the showEarnUi / currentView
handling, and make sure the reset also clears any selected Earn activity state
so the view returns to a valid non-Earn screen.
In `@app/lib/payment-order-id.ts`:
- Around line 40-56: The prefix parsing in payment-order-id.ts is too permissive
and can misread sender UUIDs as chain-prefixed order IDs. Update the parsing
flow in the transactionId handling logic to call isSenderPaymentOrderUuid()
before applying the /^(\d+)-(.+)$/ chain-prefix regex, and only attempt the
chainId/orderId split when the value is not a sender UUID. Keep the existing
canonicalTransactionId and returned shape in the parse routine unchanged for
valid non-UUID inputs.
---
Outside diff comments:
In @.env.example:
- Around line 188-189: Restore the Starknet environment documentation section in
.env.example so new setups still see the required configuration hints. Re-add
the entries for NEXT_PUBLIC_STARKNET_RPC_URL, STARKNET_PAYMASTER_API_KEY, and
STARKNET_GAS_TOKEN_ADDRESS in the Starknet-related block, keeping the existing
env-doc structure intact so app/lib/starknet.ts users can configure it
correctly.
In `@app/components/KycModal.tsx`:
- Around line 479-522: The CTA text in KycModal is inconsistent with the
disclaimer copy: the terms text says “Sign and continue” while the primary
action button says “Accept and continue.” Update the wording in the KycModal
component so both references use the same action phrase, and make sure the
button label and surrounding disclaimer match each other exactly.
In `@app/components/WalletDetails.tsx`:
- Around line 432-443: The balance rendering in WalletDetails should preserve
the Starknet wallet total when Earn mode is enabled, since the current
showEarnUi branch prevents the selectedNetwork.chain.name === "Starknet" path
from ever running. Update the conditional logic around the balance display so
Starknet still shows its own total in the header, either by checking the
Starknet-specific branch before showEarnUi or by rendering a separate Earn total
explicitly in this section.
---
Minor comments:
In @.github/workflows/ci.yml:
- Around line 35-36: The Checkout repository step using actions/checkout@v4
should disable credential persistence because this CI job only needs read
access. Update the checkout configuration to set persist-credentials to false in
the workflow so the GITHUB_TOKEN is not written to disk. Use the Checkout
repository step in the workflow to locate the change.
In `@app/api/aggregator.ts`:
- Around line 507-599: In fetchOrderDetails, the browser proxy branch currently
throws a plain Error on non-2xx responses, which strips the HTTP status needed
by TransactionsContext.reconcileTransactionStatuses. Update the axios.get
handling so the thrown error preserves the response status, either by rethrowing
an AxiosError-shaped error or by attaching the status to the error object. Keep
the same behavior in the gatewayLookup and non-gateway paths, but ensure
downstream 404 checks can still detect the status from the error.
In `@app/api/kyc/tier3-verify/route.ts`:
- Line 116: The currentTier assignment in the tier3 verification route uses
Number(currentProfile.tier) ?? 0, but nullish coalescing never catches NaN, so
the intended default to 0 is not applied. Update the currentTier calculation in
the tier3-verify route to use a fallback that handles invalid or missing tier
values, such as a logical OR default, while keeping the existing tier check
logic intact.
In `@app/api/starknet/earn/withdraw/route.ts`:
- Around line 89-100: The withdraw route currently treats client-supplied
publicKey as required even though Starknet account details are built from
getStarknetWallet(walletId) and walletPublicKey in route.ts. Update the handler
in the withdraw route to either remove publicKey from the required-field
validation and request contract entirely, or actually use it to verify against
the server-derived walletPublicKey before proceeding. Make the change
consistently in the validation block and any downstream logic that references
the withdrawal signer/account setup.
In `@app/components/EarnDisclosureBanner.tsx`:
- Around line 3-11: The EarnDisclosureBanner component uses React.FC without
importing React, which will fail type-checking in this module. Update the top of
EarnDisclosureBanner.tsx by either adding a React import before the
EarnDisclosureBannerProps/React.FC usage, or changing the component typing to
use a type-only FC import from react; keep the fix aligned with the
EarnDisclosureBanner export and its props interface.
In `@app/components/MainPageContent.tsx`:
- Around line 393-399: The network-modal gate in MainPageContent is checking
user.wallet.address, but the rest of this flow uses the active walletAddress
source. Update this deferral check to use the same wallet identifier as the
referral storage key and related modal logic, so the network-modal behavior
stays consistent across wallet types.
In `@app/components/PhoneVerificationModal.tsx`:
- Around line 603-609: The Cancel button in PhoneVerificationModal uses an
undefined width class, so replace the w-md utility on the button with a real
sizing class such as flex-1 or w-full to match the Continue button layout.
Update the button markup in PhoneVerificationModal so the existing
secondaryBtnClasses still apply while the width comes from a valid utility
class.
In `@app/components/ProfileDrawer.tsx`:
- Around line 331-335: The avatar in ProfileDrawer should use Next’s optimized
image component instead of a raw img element to satisfy next/core-web-vitals
linting. Update the avatar markup in ProfileDrawer to replace the current img
placeholder with next/image, keeping the same src, alt, and sizing/rounding
behavior so the existing layout is preserved. Make sure the component imports
and uses Image from next/image wherever this avatar is rendered.
In `@app/components/SettingsDropdown.tsx`:
- Line 167: The session cleanup in SettingsDropdown’s clearUserSessionData call
is using the network-aware walletAddress fallback instead of the embedded wallet
address that was used to write the localStorage keys. Update the logout cleanup
path to use user?.wallet?.address for the session key source (or adjust
clearUserSessionData to accept the exact key origin) so the
hasSeenNetworkModal-* and recipientsMigrated-* entries are removed consistently.
In `@app/components/wallet-mobile-modal/WalletView.tsx`:
- Around line 83-97: The tab-reset effect in WalletView is unreachable because
showEarnUi and selectedNetwork.chain.name are already constrained so the
condition can never be true. Remove the useEffect entirely if it is redundant,
or update the logic around walletTab/setWalletTab to reset when the network
changes away from Starknet if that is the intended behavior.
In `@app/context/TransactionsContext.tsx`:
- Around line 211-216: The 404 suppression in the transaction status
reconciliation only catches Axios errors, so offramp 404s coming through
fetchOrderDetails still get logged on every poll. Update either
fetchOrderDetails / fetchV2SenderPaymentOrderById to preserve the HTTP status on
the thrown error, or broaden the catch in the TransactionsContext reconciliation
path to recognize the 404 from that flow. Use the existing symbols
fetchV2SenderPaymentOrderById, fetchOrderDetails, and the console.error block in
the transaction status polling logic to locate the fix.
In `@app/lib/_starkzap-unused-shim.ts`:
- Around line 11-23: The shim currently only handles property access, so `new
Account(...)` bypasses the intended peer-dependency message and fails with a
generic constructor error instead. Update the proxy in
`_starkzap-unused-shim.ts` so the exported `shim`/`Account` value is
constructable, and add a `construct` trap (and `apply` if the shim may be called
as a function) that throws the same descriptive error as the existing `get`
trap. Keep the error text centralized so `Account` instantiation and access
paths all surface the same optional-dependency message.
In `@app/lib/config.ts`:
- Around line 53-64: The referral amount parsers in config.ts only check
Number.isFinite, so negative values can slip through for
referralMinQualifyingVolumeUsd and referralRewardAmountUsd. Update these inline
parsers to use the same non-negative validation approach as
parseNonNegativeNumber from kyc-tier-limits.ts, returning 0 or rejecting invalid
input when the env var parses to a negative number, and keep the logic localized
to the two referral config fields so misconfigured NEXT_PUBLIC_REFERRAL_* values
cannot produce negative amounts.
---
Nitpick comments:
In `@app/api/kyc/transaction-summary/route.ts`:
- Around line 73-89: The two Supabase reads in transactionSummaryRoute are
independent, so awaiting them one after the other in the transaction-summary
route adds avoidable latency. Update the logic around the supabaseAdmin queries
for swapTransactions and onrampTransactions to start both requests at the same
time and await them together with Promise.all, while preserving the existing
error handling for swapError and onrampError and keeping the current query
filters unchanged.
In `@app/api/referral/claim/route.ts`:
- Around line 622-627: The referral claim loop in the claim route processes
every item sequentially via tryClaimOne, which can exceed the request timeout
when many referrals are pending. Update the logic around the pendingReferrals
iteration to bound how many claims are attempted per invocation (for example,
cap the batch size or stop after a fixed number of successes/attempts), and
leave the rest for a subsequent run or background processing. Keep the change
localized to the claim handler and tryClaimOne flow so the timeout risk is
reduced without altering individual transfer handling.
In `@app/api/referral/referral-data/route.ts`:
- Around line 197-200: The fallback in toAmount is hardcoded to 1.0, which can
diverge from the configured referral reward amount used elsewhere in the claim
flow. Update the fallback logic in the referral-data route so toAmount uses the
same config.referralRewardAmountUsd value when reward_amount is null or invalid,
keeping displayed totals aligned with actual payouts.
In `@app/api/referral/submit/route.ts`:
- Around line 89-98: The referral code validation in the submit route duplicates
the shared pattern, so update the `route.ts` handler to import and use
`REFERRAL_CODE_PATTERN` from `app/lib/pendingReferralCode.ts` instead of the
inline regex. Keep the existing `normalizedCode` check and error response, but
route the validation through the shared constant so the backend and
`ReferralModal.tsx` stay aligned.
In `@app/api/v1/payment-orders/`[id]/route.ts:
- Around line 55-127: The GET handler in payment-orders route returns mixed
error envelopes, which breaks message propagation in the client. Standardize the
error responses in the `isSenderPaymentOrderUuid` config failure branch to match
the `{ status: "error", message }` shape used by the validation branches so
`fetchOrderDetails` can read the message consistently. Update the
`trackApiError` path and the `NextResponse.json` payload in the `route.ts`
handler to use one envelope format across all early returns.
In `@app/components/EarnActivityDetails.tsx`:
- Around line 20-59: The Earn components are duplicating pool config and
formatting helpers, so the copied values can drift apart. Move POOL_INFO,
formatBaseUnits, and safeBigInt into a shared client-safe helper module that
does not import the heavy SDK, and update EarnActivityDetails, EarnWalletForm,
and EarnActivityPanel to use that single source of truth. Keep the existing
lightweight/local-import rationale, but centralize the shared logic under one
set of symbols so updates to pool addresses, URLs, or formatting stay
consistent.
In `@app/components/EarnActivityPanel.tsx`:
- Around line 15-62: The Earn formatting and APY helpers are duplicated between
EarnActivityPanel and EarnHubView, so consolidate them into one shared helper
module and import from both places. Move TOKEN_DECIMALS, TOKEN_FACTOR,
APR_REFRESH_MS, formatBaseUnits, formatPercent, safeBigInt, and projectEarnings
into a shared utility, then update both components to use the shared symbols so
formatting and earnings math stay consistent in one place.
In `@app/components/EarnConsentModal.tsx`:
- Around line 69-77: The DialogPanel in EarnConsentModal currently uses an
inline window.innerWidth check for maxWidth, which duplicates the existing
responsive behavior and won’t update on resize. Move this sizing logic into
Tailwind responsive classes on the DialogPanel so it matches the rest of the
component’s sm: breakpoint usage, and remove the inline style-based width
condition entirely.
In `@app/components/EarnWalletForm.tsx`:
- Around line 42-56: The earn amount formatting logic is duplicated between
EarnWalletForm and EarnActivityDetails, so extract the shared helpers into a
common earn formatting utility module. Move the equivalent of formatBaseUnits
and the BigInt parsing/safety helper used by parseAmountToBaseUnits/safeBigInt
into a reusable module, then update EarnWalletForm and EarnActivityDetails to
import and use those shared functions to keep formatting behavior consistent.
In `@app/components/KycModal.tsx`:
- Around line 1092-1138: The Tier3 upload flow duplicates the same extension and
size validation in both handleTier3FileChange and handleTier3Drop inside
renderTier3Upload. Extract that shared JPG/JPEG/PNG and 5 MB validation into one
reusable helper near ALLOWED_TIER3_EXTENSIONS and TIER3_MAX_BYTES, then have
both handlers call it and only keep their input/drop-specific file assignment
and reset behavior.
In `@app/components/KycPolicy.tsx`:
- Around line 254-258: The `KycPolicy` component is using `next/link` for a
`mailto:` address, which should be a plain anchor instead. Update the email link
in `KycPolicy` (and the other matching `mailto:` occurrences) to use an `<a>`
tag with the same href and styling, leaving `next/link` only for internal
navigation.
- Around line 1-379: The KycPolicy component duplicates nearly the same legal
boilerplate already present in PrivacyPolicy, so future edits can drift out of
sync. Refactor the shared policy text into a single reusable source (for
example, section data rendered by both KycPolicy and PrivacyPolicy) and keep
only document-specific formatting differences in each component. Use the
KycPolicy and PrivacyPolicy components as the main reference points when
consolidating the repeated sections.
In `@app/components/MobileDropdown.tsx`:
- Around line 242-264: There are two overlapping reset effects in MobileDropdown
that both set currentView to "wallet" when isOpen becomes false; keep only the
broader reset effect and remove the earlier one. Consolidate the close/reset
behavior in the single useEffect that already handles selectedEarnActivity,
earnActivityReturnView, selectedTransaction, and isNetworkListOpen so the wallet
reset happens in one place and future changes stay consistent.
In `@app/components/Navbar.tsx`:
- Around line 173-192: The conditional in Navbar’s logo rendering is now
redundant because both the `IS_MAIN_PRODUCTION_DOMAIN` branches render the same
`NobblocksWorldCupLogo` and `NobblocksAnimatedIcon`; either collapse the
branches into a single shared block or restore distinct production/beta logos if
the flag is still needed. Also update the `classNames` usage for the arrow in
`Navbar` so the `IS_MAIN_PRODUCTION_DOMAIN` offset logic and its comment match
the actual rendered logo state, since the current “beta logo” adjustment is
stale.
In `@app/components/NetworkSelectionModal.tsx`:
- Line 30: The commented-out legacy code in NetworkSelectionModal is dead code
and should be removed now that the networkModalStore-based flow is used. Delete
the leftover NetworkSelectionModal signature comment and the old handleClose
implementation, and make sure the active component logic stays centered around
the NetworkSelectionModal component and its current store-driven handlers.
In `@app/components/NoblocksAnimatedIcon.tsx`:
- Around line 34-37: The stripSize helper currently only matches SVGs that start
exactly with <svg, so it can miss assets with an XML prolog, BOM, or leading
comments. Update stripSize in NoblocksAnimatedIcon.tsx to locate and modify the
first svg opening tag anywhere near the start of the string, and keep the
width/height removal logic in that helper so re-exported assets still have their
size stripped consistently.
- Around line 108-126: The `NoblocksAnimatedIcon` component injects fetched SVG
strings via `dangerouslySetInnerHTML` for `ball` and `trophy`, so sanitize those
markup strings before rendering to close off XSS risk. Update the
SVG-fetching/assignment path used by `NoblocksAnimatedIcon` to pass the returned
markup through `DOMPurify.sanitize()` (or equivalent) before it reaches the
`<span dangerouslySetInnerHTML>` blocks, keeping the current phase/animation
behavior unchanged.
In `@app/components/NoblocksWorldCupLogo.tsx`:
- Around line 49-55: The NoblocksWorldCupLogo component is using the same unsafe
innerHTML injection pattern as NoblocksAnimatedIcon, so sanitize the fetched SVG
markup before rendering it. Update the logic around the svg payload and the span
that uses dangerouslySetInnerHTML so the markup is passed through
DOMPurify.sanitize() first, keeping the existing same-origin/static-asset
behavior while removing the static-analysis warning.
- Line 18: The current cache in NoblocksWorldCupLogo only stores the resolved
markup, so concurrent mounts can still trigger duplicate fetches before it is
populated. Update the caching logic around cachedMarkup and the fetch/parse path
in the component so an in-flight promise is shared and reused by all callers
until it resolves, then replace it with the final markup string. Make sure the
logic in the component’s fetch helper and render path dedupes simultaneous
requests without changing the existing cachedMarkup behavior after resolution.
In `@app/components/PhoneVerificationModal.tsx`:
- Around line 544-576: The OTP inputs in PhoneVerificationModal currently only
handle one digit per box and do not support SMS autofill or pasting a full code.
Update the mapped input fields in the OTP entry section to enable one-time-code
autofill and numeric mobile input, and add paste handling so a full 6-digit code
can be distributed across the inputs instead of only filling the first one.
In `@app/components/ReferralModal.tsx`:
- Around line 123-138: The referral code field in ReferralModal is missing an
accessible label association because the label and input are siblings without a
link. Update the label/input pair in ReferralModal so the label uses htmlFor and
the input has a matching id, keeping the existing “Enter referral code” text and
the referralCode state behavior unchanged.
In `@app/components/wallet-mobile-modal/ReferralHubView.tsx`:
- Around line 27-83: Replace the `any` usage in `ReferralHubView` with the
shared referral response types from the `referral-data` flow so `referralData`
and `filteredReferrals` are strongly typed. Update the `useState` for
`referralData` and the `filter` callback to use the `ReferralData` shape (and
the referral item type) instead of `any`, so accesses like `referral_code`,
`referrals`, `status`, and other fields are compile-time checked.
In `@app/components/wallet-mobile-modal/WalletView.tsx`:
- Around line 105-197: The sticky tab bar and network dropdown markup in
WalletView is duplicated from the similar section in WalletDetails, so
consolidate this repeated UI into a shared component such as WalletTabBar or
NetworkTabSelector. Move the tab buttons, network picker, and
AnimatePresence/motion dropdown behavior into that reusable component, then have
WalletView and WalletDetails pass in the selected tab, network state, and
callbacks so there is a single source of truth for this interaction.
In `@app/context/BalanceContext.tsx`:
- Around line 117-121: The repeated buildWalletBalancesFromRaw(rawBalances,
rate, balancesInWei) call pattern in BalanceContext should be consolidated to
reduce duplication and avoid missing future fields. Update
buildWalletBalancesFromRaw to accept a single object or fetch-result shape that
carries both balances and balancesInWei, then refactor each call site to pass
that unified value instead of three positional arguments. Use the existing
buildWalletBalancesFromRaw helper and the surrounding balance-fetch flow in
BalanceContext to keep the change consistent across all call sites.
In `@app/context/StarknetContext.tsx`:
- Around line 104-106: The empty-string ternary before calling
toCanonicalStarknetAddress in StarknetContext is redundant because the helper
already normalizes nullish and empty input. Simplify the address assignment by
passing storedAddress directly into toCanonicalStarknetAddress and let that
function handle empty values, keeping the change localized to the address
normalization logic in StarknetContext.
In `@app/hooks/useEarnHandler.ts`:
- Around line 204-334: The Earn API calls in refreshPosition, deposit, and
withdraw can hang indefinitely because they use fetch without a timeout. Add an
AbortController-based timeout around the fetches in useEarnHandler.ts so these
requests fail fast and reject instead of leaving the UI waiting forever. Update
the refreshPosition, deposit, and withdraw callbacks together, and ensure any
timeout cleanup is handled consistently in these helper methods.
In `@app/lib/earn.ts`:
- Around line 187-202: `getSupplyApy` currently calls
`provider.getMarkets(ChainId.MAINNET)` on every lookup, which makes the hot
`getVesuPosition` path refetch the full market list repeatedly. Add a
short-lived in-memory cache for the markets result (or for the `getSupplyApy`
lookup) with a small TTL, and reuse it before calling `provider.getMarkets`
again. Keep the change localized around `getSupplyApy` and the `getMarkets`
fetch so repeated `/api/starknet/earn/position` requests avoid redundant
external calls.
In `@app/lib/networkModalStore.ts`:
- Around line 29-44: The case-insensitive localStorage key scanning is
duplicated between hasSeenNetworkModalFlag and clearNetworkModalSeen, so extract
the shared matching logic into a helper such as findMatchingKeys(target: string)
and reuse it from both places. Keep the target computation based on
NETWORK_MODAL_STORAGE_KEY_PREFIX and walletAddress in one place, and centralize
the localStorage iteration plus try/catch handling inside the helper. Update
both hasSeenNetworkModalFlag and clearNetworkModalSeen to call the shared helper
instead of reimplementing the scan.
In `@app/lib/phone-verification.ts`:
- Around line 122-136: The KudiSMS response handling in phone-verification
should check response.ok or response.status before calling response.json(),
since a non-2xx response may return a non-JSON body and cause an exception.
Update the logic around the response parsing in the KudiSMS send flow to handle
HTTP failures explicitly first, then only parse JSON when safe, and keep the
existing success/failure return shape in the same code path.
In `@app/lib/smileCameraTheme.ts`:
- Around line 288-306: The continuous requestAnimationFrame loop in
startSmileCameraAlertStyleFix is re-running applySmileCameraFixes(host) every
frame, which creates an unnecessary hot path. Change the tick scheduling so the
Smile camera style fix only reapplies on a throttled interval or in response to
DOM changes via a MutationObserver, and keep the cleanup in the returned
teardown function by canceling the scheduled work and calling
uninstallOverrideSheets(host).
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3012d16c-9859-4f5f-9081-65ac9967338a
⛔ Files ignored due to path filters (16)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpublic/icons/placeholder.pngis excluded by!**/*.pngpublic/images/avatar/Avatar.pngis excluded by!**/*.pngpublic/images/avatar/Avatar1.pngis excluded by!**/*.pngpublic/images/avatar/Avatar2.pngis excluded by!**/*.pngpublic/images/avatar/Avatar3.pngis excluded by!**/*.pngpublic/images/avatar/Avatar4.pngis excluded by!**/*.pngpublic/images/avatar/Avatar5.pngis excluded by!**/*.pngpublic/images/avatar/Avatar6.pngis excluded by!**/*.pngpublic/images/avatar/Avatar7.pngis excluded by!**/*.pngpublic/images/referral-cta-dollar.pngis excluded by!**/*.pngpublic/images/referral-cta.pngis excluded by!**/*.pngpublic/images/referral-graphic.pngis excluded by!**/*.pngpublic/logos/noblocks-worldcup-logo.svgis excluded by!**/*.svgpublic/logos/worldcup/ball.svgis excluded by!**/*.svgpublic/logos/worldcup/trophy.svgis excluded by!**/*.svg
📒 Files selected for processing (140)
.env.example.github/CODEOWNERS.github/workflows/ci.ymlapp/api/aggregator.tsapp/api/blockfest/cashback/route.tsapp/api/internal/set-wallet-context/route.tsapp/api/kyc/smile-id/callback/route.tsapp/api/kyc/smile-id/id_types.jsonapp/api/kyc/smile-id/route.tsapp/api/kyc/status/route.tsapp/api/kyc/tier3-verify/route.tsapp/api/kyc/transaction-summary/route.tsapp/api/phone/send-otp/route.tsapp/api/phone/verify-otp/route.tsapp/api/referral/claim/route.tsapp/api/referral/referral-data/route.tsapp/api/referral/submit/route.tsapp/api/starknet/create-order/route.tsapp/api/starknet/earn/deposit/route.tsapp/api/starknet/earn/position/route.tsapp/api/starknet/earn/withdraw/route.tsapp/api/starknet/export-wallet/route.tsapp/api/starknet/transfer/route.tsapp/api/v1/payment-orders/[id]/route.tsapp/api/v1/transactions/[id]/route.tsapp/api/v1/transactions/route.tsapp/api/v1/transactions/swap-precheck/route.tsapp/api/v1/wallets/deprecate/route.tsapp/components/AnimatedComponents.tsxapp/components/AppLayout.tsxapp/components/CopyAddressWarningModal.tsxapp/components/EarnActivityDetails.tsxapp/components/EarnActivityPanel.tsxapp/components/EarnConsentModal.tsxapp/components/EarnDisclosureBanner.tsxapp/components/EarnWalletForm.tsxapp/components/ExportStarknetWalletModal.tsxapp/components/FlexibleDropdown.tsxapp/components/FundWalletForm.tsxapp/components/KycModal.tsxapp/components/KycPolicy.tsxapp/components/MainPageContent.tsxapp/components/MobileDropdown.tsxapp/components/Navbar.tsxapp/components/NetworkSelectionModal.tsxapp/components/NoblocksAnimatedIcon.tsxapp/components/NoblocksWorldCupLogo.tsxapp/components/PhoneVerificationModal.tsxapp/components/PrivacyPolicy.tsxapp/components/ProfileDrawer.tsxapp/components/ReferralCTA.tsxapp/components/ReferralDashboardSkeleton.tsxapp/components/ReferralModal.tsxapp/components/SettingsDropdown.tsxapp/components/TransactionLimitModal.tsxapp/components/TransferForm.tsxapp/components/WalletDetails.tsxapp/components/index.tsapp/components/kyc/DocumentRequirementsModal.tsxapp/components/kyc/index.tsapp/components/recipient/RecipientDetailsForm.tsxapp/components/wallet-mobile-modal/EarnActivityDetailView.tsxapp/components/wallet-mobile-modal/EarnHubView.tsxapp/components/wallet-mobile-modal/ReferralHubView.tsxapp/components/wallet-mobile-modal/SettingsView.tsxapp/components/wallet-mobile-modal/WalletView.tsxapp/components/wallet-mobile-modal/index.tsapp/context/BalanceContext.tsxapp/context/KYCContext.tsxapp/context/MigrationContext.tsxapp/context/StarknetContext.tsxapp/context/TransactionsContext.tsxapp/context/index.tsapp/globals.cssapp/hooks/useEarnAccess.tsapp/hooks/useEarnHandler.tsapp/hooks/useLoginWithScrollPin.tsapp/hooks/useSwapButton.tsapp/kyc-policy/kyc-client.tsxapp/kyc-policy/page.tsxapp/lib/_starkzap-unused-shim.tsapp/lib/config.tsapp/lib/countries.tsapp/lib/dojah.tsapp/lib/earn.tsapp/lib/earnConsent.tsapp/lib/earnFeature.tsapp/lib/kyc-tier-limits.tsapp/lib/kyc-upgrade-path.tsapp/lib/networkModalStore.tsapp/lib/payment-order-id.tsapp/lib/pendingReferralCode.tsapp/lib/phone-validation.tsapp/lib/phone-verification.tsapp/lib/privy.tsapp/lib/smileCameraTheme.tsapp/lib/smileID.tsapp/lib/smileIdIdValidation.tsapp/lib/starknet.tsapp/lib/supabase.tsapp/lib/swap-transaction-limit-server.tsapp/lib/transaction-wallet-auth.tsapp/lib/validation.tsapp/mainContent.tsxapp/pages/TransactionForm.tsxapp/pages/TransactionPreview.tsxapp/pages/TransactionStatus.tsxapp/privacy-policy/privacy-client.tsxapp/providers.tsxapp/sitemap.tsapp/terms/terms-client.tsxapp/types.tsapp/types/smart-camera-web.d.tsapp/utils.tsdocs/environment-variables.mddocs/transaction-history-setup.mdjest.setup.jsmiddleware.tsnext.config.mjspackage.jsonpatches/@privy-io__react-auth@3.1.0.patchscripts/package.jsonscripts/test-starkzap.tssupabase/.gitignoresupabase/config.tomlsupabase/migrations/20260508140001_create_transactions_table.sqlsupabase/migrations/20260508140002_create_user_kyc_profiles.sqlsupabase/migrations/20260508140003_create_wallet_migrations.sqlsupabase/migrations/20260508140004_create_saved_recipients.sqlsupabase/migrations/20260508140005_create_refund_accounts.sqlsupabase/migrations/20260508140006_create_blockfest_participants.sqlsupabase/migrations/20260508140007_create_blockfest_cashback_claims.sqlsupabase/migrations/20260508140010_create_kyc_documents_storage_bucket.sqlsupabase/migrations/20260611120000_kyc_identity_hardening.sqlsupabase/migrations/20260612130000_fix_kyc_limit_status_filters.sqlsupabase/migrations/20260616000000_add_referrer_rewards_unlocked_at.sqlsupabase/migrations/20260617120000_tier3_unlimited_kyc_limit.sqlsupabase/migrations/add_referrals_referred_wallet_unique.sqlsupabase/migrations/create_transactions_table.sqlsupabase/seed.sql
💤 Files with no reviewable changes (1)
- app/components/AppLayout.tsx
| const actions = smileIdResult?.Actions; | ||
| const isEnhancedKyc = actions?.Verify_ID_Number !== undefined; | ||
| const isBiometricKyc = !isEnhancedKyc; | ||
|
|
||
| let verificationSuccess = false; | ||
|
|
||
| if (isEnhancedKyc) { | ||
| // Enhanced KYC: Check if ID verification passed | ||
| verificationSuccess = actions.Verify_ID_Number === "Verified"; | ||
| } else if (isBiometricKyc) { | ||
| // Biometric KYC: job is complete — check whether it succeeded | ||
| verificationSuccess = | ||
| smileIdResult.job_complete && smileIdResult.job_success; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Files ==\n'
git ls-files | rg '(^|/)(app/api/kyc/smile-id/route\.ts|.*smile.*id.*\.(ts|tsx|js|jsx)|.*Smile.*ID.*\.(ts|tsx|js|jsx)|.*smile.*\.md)$' || true
printf '\n== Outline route.ts ==\n'
ast-grep outline app/api/kyc/smile-id/route.ts --view expanded || true
printf '\n== Relevant slices route.ts ==\n'
nl -ba app/api/kyc/smile-id/route.ts | sed -n '120,240p'
printf '\n== Search for flag helper and SmileID response access ==\n'
rg -n 'flag\(|job_complete|job_success|Actions|ResultText|result\.Actions|result\.ResultText|jobStatus\.result' app api lib src . -g '!node_modules' || trueRepository: paycrest/noblocks
Length of output: 624
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== route.ts (1-260) ==\n'
cat -n app/api/kyc/smile-id/route.ts | sed -n '1,260p'
printf '\n== callback/route.ts (1-260) ==\n'
cat -n app/api/kyc/smile-id/callback/route.ts | sed -n '1,260p'
printf '\n== smileIdIdValidation.ts ==\n'
cat -n app/lib/smileIdIdValidation.ts | sed -n '1,260p'Repository: paycrest/noblocks
Length of output: 28379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== app/lib/smileID.ts (1-320) ==\n'
cat -n app/lib/smileID.ts | sed -n '1,320p'
printf '\n== Search for job_complete/job_success typing ==\n'
rg -n 'job_complete|job_success|Actions|ResultText|result\?' app/lib/smileID.ts app/api/kyc/smile-id -g '!node_modules' || trueRepository: paycrest/noblocks
Length of output: 9548
🌐 Web query:
SmileID submit_job return_job_status response shape Actions ResultText job_complete job_success result.Actions
💡 Result:
When using the Smile ID API, the response returned by the job status check provides a comprehensive JSON object detailing the outcome of the requested job [1][2][3]. The top-level response structure includes fields such as job_complete (a boolean indicating if a final status has been reached) and job_success (a boolean indicating if the match or authentication was successful) [1][2][3]. The result object contains the core outcome data [1][2][3]: - ResultText: A human-readable textual value of the job outcome [1][4][5]. - SmileJobID: The internal Smile ID reference number for the job [1][4]. - PartnerParams: An object containing the original user_id, job_id, and job_type passed in the request [1][2][3]. - ConfidenceValue: A numerical representation of the confidence in the final determination [1][2]. Additionally, the Actions object provides a granular breakdown of the specific checks performed by Smile ID [4][5]. This object contains key-value pairs where the keys represent individual checks (e.g., Liveness_Check, Selfie_Check, Selfie_To_Registered_Selfie_Compare) and the values indicate the status of those checks (e.g., Passed, Not Applicable, Completed) [4][5][6]. Depending on your request configuration, the response may also include an image_links object containing signed URLs for captured media, provided the appropriate flags were set during the submission or status query [1][2][7][8].
Citations:
- 1: https://docs.usesmileid.com/further-reading/job-status
- 2: https://docs.usesmileid.com/integration-options/rest-api/utilities.md
- 3: https://docs.usesmileid.com/integration-options/rest-api/utilities
- 4: https://docs.usesmileid.com/products/for-individuals-kyc/biometric-authentication
- 5: https://docs.usesmileid.com/products/for-individuals-kyc/identity-lookup.md
- 6: https://docs.usesmileid.com/integration-options/server-to-server/javascript/products/document-verification
- 7: https://docs.usesmileid.com/integration-options/server-to-server/python/utilities
- 8: https://docs.usesmileid.com/integration-options/server-to-server/javascript/utilities
🌐 Web query:
smile-identity-core submit_job return_job_status response result.Actions result.ResultText top-level Actions
💡 Result:
In the Smile Identity API, the job response structure provides details about the outcome of a job. When using the job status endpoint (often accessed via methods like getJobStatus in the Core SDKs), the response contains several key fields that describe the final result [1][2][3]. Key response fields include: ResultText: A human-readable string indicating the outcome of the job (e.g., "ID Number Validated", "Document Verified") [1][4][2][5]. Actions: An object containing the specific results of individual checks performed by Smile Identity during the job [4][2][5]. For example, in an Enhanced KYC job, this object might include keys like Verify_ID_Number or Return_Personal_Info, with values such as "Verified" or "Returned" [2][5]. In document verification, it may include fields like Human_Review_Compare or Verify_Document [4]. While your query mentioned result.Actions and result.ResultText, note that depending on the specific product (e.g., Enhanced KYC vs. standard Job Status), these fields may appear either at the top level of the response object or nested within a result object [1][2][5]. You should always refer to the specific product documentation for the exact JSON schema, as structural nuances can vary based on the request type [2][5]. To retrieve this data in your application, you typically configure options such as return_job_status=true when calling the submission or status methods [6][7][3]. This ensures the API returns the full job result rather than just an acknowledgment of submission [1][3].
Citations:
- 1: https://docs.usesmileid.com/integration-options/rest-api/utilities.md
- 2: https://docs.usesmileid.com/integration-options/server-to-server/java/products/enhanced-kyc
- 3: https://docs.usesmileid.com/integration-options/server-to-server/java/utilities
- 4: https://docs.usesmileid.com/products/for-individuals-kyc/document-verification/document-verification
- 5: https://docs.usesmileid.com/products/for-individuals-kyc/identity-lookup
- 6: https://docs.usesmileid.com/integration-options/server-to-server/python/utilities
- 7: https://docs.usesmileid.com/integration-options/server-to-server/python/utilities.md
Normalize SmileID status flags before promoting Tier 2. job_complete and job_success can arrive as "true"/"false" strings, so the raw && here can treat a failed biometric job as success and promote the user. Reuse the same flag() normalization used in the callback route.
🤖 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 `@app/api/kyc/smile-id/route.ts` around lines 187 - 200, Normalize the SmileID
biometric status checks in the KYC route before setting verificationSuccess. In
the smile-id route’s enhanced/biometric branching, raw job_complete and
job_success can be string values, so replace the direct boolean check in the
biometric path with the same flag() normalization used in the callback flow.
Keep the logic in the smileIdResult/actions handling and ensure only a truly
completed-and-successful biometric job can promote Tier 2.
| } catch (err) { | ||
| const raw = err instanceof Error ? err.message : String(err); | ||
| console.error("[tier3-verify] unexpected error", err); | ||
| // Dojah often returns JSON in the thrown message; avoid double-encoding for clients. | ||
| let message = raw; | ||
| if (raw.trim().startsWith("{")) { | ||
| try { | ||
| const parsed = JSON.parse(raw) as { error?: string; message?: string }; | ||
| message = | ||
| (typeof parsed.error === "string" && parsed.error) || | ||
| (typeof parsed.message === "string" && parsed.message) || | ||
| raw; | ||
| } catch { | ||
| // keep raw | ||
| } | ||
| } | ||
| return NextResponse.json( | ||
| { success: false, error: message || "Verification failed" }, | ||
| { status: 500 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Uploaded document is orphaned when verifyUtilityBill throws.
The file is uploaded at Line 202 and the signed URL created before verifyUtilityBill runs (Line 248). On a thrown Dojah error (timeout, network, non-2xx) control jumps to this outer catch, which returns 500 but never calls storage.from(KYC_BUCKET).remove([path]) — unlike the explicit-failure paths (Lines 231, 258, 311, 322). Each such failure leaves an unreferenced object in the private KYC bucket.
Also note the attempt counter incremented at Line 160 is not restored on these infrastructure failures, so a Dojah timeout still burns one of the user's 5 attempts (the smile-id route deliberately restores the counter for infrastructure outages).
🤖 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 `@app/api/kyc/tier3-verify/route.ts` around lines 338 - 358, The outer catch in
tier3-verify leaves uploaded KYC documents orphaned and still consumes a
verification attempt when verifyUtilityBill throws. Update the route’s error
handling around verifyUtilityBill so that any thrown Dojah/network/non-2xx
failure triggers cleanup via storage.from(KYC_BUCKET).remove([path]) using the
uploaded file path, and restore the attempt counter on infrastructure failures
just like the smile-id flow. Keep the existing success and explicit-failure
paths intact, but ensure the catch block and any helper around verifyUtilityBill
consistently roll back both the uploaded object and the incremented attempt
count when verification cannot complete.
| const error = swapError ?? onrampError; | ||
| const transactions = [ | ||
| // The tracked-currency filter lives here rather than in the query because | ||
| // PostgREST cannot compare a column case-insensitively in .in(). | ||
| ...(swapTransactions ?? []) | ||
| .map((tx) => ({ | ||
| ...tx, | ||
| kind: "offramp" as const, | ||
| from_currency: normalizeCurrency(tx.from_currency), | ||
| })) | ||
| .filter((tx) => TRACKED_SWAP_CURRENCIES.has(tx.from_currency)), | ||
| ...(onrampTransactions ?? []).map((tx) => ({ | ||
| ...tx, | ||
| kind: "onramp" as const, | ||
| from_currency: normalizeCurrency(tx.from_currency), | ||
| to_currency: normalizeCurrency(tx.to_currency), | ||
| })), | ||
| ]; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for the full set of supported onramp to_currency values
rg -n "to_currency" --type=ts -C2 app
rg -n "TRACKED_SWAP_CURRENCIES|STABLE_TO_CURRENCIES" --type=tsRepository: paycrest/noblocks
Length of output: 11782
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== route outline =="
ast-grep outline app/api/kyc/transaction-summary/route.ts --view expanded || true
echo
echo "== route slice around transaction assembly and usdAmount logic =="
sed -n '1,230p' app/api/kyc/transaction-summary/route.ts
echo
echo "== currency set definitions/usages =="
rg -n "TRACKED_SWAP_CURRENCIES|STABLE_TO_CURRENCIES|USD_STABLE|normalizeCurrency" app -C 2Repository: paycrest/noblocks
Length of output: 11608
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== transaction creation route snippets =="
sed -n '1,420p' app/api/v1/transactions/route.ts
echo
echo "== searches for onramp currency constraints/validation =="
rg -n "onramp|toCurrency|to_currency|fromCurrency|from_currency|USDC|USDT|CUSD|CNGN" app lib supabase db prisma -g '!**/node_modules/**' -C 2
echo
echo "== SQL/schema files mentioning transactions or currency constraints =="
fd -a -t f '.*(sql|tsx?|ts)$' . | rg 'migrations|supabase|schema|transaction|currency|database' -nRepository: paycrest/noblocks
Length of output: 50374
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== usages of transaction-summary endpoint =="
rg -n "/api/kyc/transaction-summary|transaction-summary" app supabase -g '!**/node_modules/**' -C 3
echo
echo "== nearby KYC summary / limit-related code =="
rg -n "dailySpent|monthlySpent|hasCngnTxs|partial" app supabase -g '!**/node_modules/**' -C 2Repository: paycrest/noblocks
Length of output: 14191
Align unsupported onramp currencies with the KYC limit logic
app/api/kyc/transaction-summary/route.ts falls back to amount_received for onramp rows whose to_currency is neither stable nor CNGN, but the KYC limit procedure counts those rows via amount_sent / rate. That can overstate dailySpent/monthlySpent for onramp currencies outside STABLE_TO_CURRENCIES ∪ {CNGN} without hitting hasCngnTxs. Mirror the procedure’s branch here instead of the 1:1 fallback.
🤖 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 `@app/api/kyc/transaction-summary/route.ts` around lines 91 - 108, The onramp
transaction summary logic in transaction-summary/route.ts is treating
unsupported destination currencies with a simple amount_received fallback, which
does not match the KYC limit procedure. Update the onramp handling in the
transactions mapping so it mirrors the same branch used by the limit calculation
for non-stable, non-CNGN currencies: use the amount_sent/rate-style calculation
for those rows instead of defaulting to amount_received. Keep the existing
normalization and filtering around TRACKED_SWAP_CURRENCIES and the
onrampTransactions mapping, but align the spending fields so
dailySpent/monthlySpent stay consistent with the procedure.
| let pendingClaimRow: typeof existingClaim; | ||
|
|
||
| if (existingClaim?.status === "pending") { | ||
| pendingClaimRow = existingClaim; | ||
| } else if (existingClaim?.status === "failed") { | ||
| const { data: retried, error: retryErr } = await supabaseAdmin | ||
| .from("referral_claims") | ||
| .update({ status: "pending", tx_hash: null, updated_at: new Date().toISOString() }) | ||
| .eq("id", existingClaim.id) | ||
| .select() | ||
| .single(); | ||
| if (retryErr || !retried) { | ||
| return { success: false, code: "CLAIM_RETRY_FAILED", message: "Unable to retry this claim." }; | ||
| } | ||
| pendingClaimRow = retried; | ||
| } else { | ||
| const { data: inserted, error: insertError } = await supabaseAdmin | ||
| .from("referral_claims") | ||
| .insert({ | ||
| referral_id: referralId, | ||
| wallet_address: walletAddress, | ||
| reward_amount: referralRewardAmountUsd, | ||
| status: "pending", | ||
| }) | ||
| .select() | ||
| .single(); | ||
|
|
||
| if (insertError && isUniqueViolation(insertError)) { | ||
| const { data: raced, error: raceErr } = await supabaseAdmin | ||
| .from("referral_claims") | ||
| .select("*") | ||
| .eq("referral_id", referralId) | ||
| .eq("wallet_address", walletAddress) | ||
| .single(); | ||
| if (raceErr || !raced) { | ||
| return { success: false, code: "CLAIM_CONFLICT", message: "Unable to resolve claim row." }; | ||
| } | ||
| if (raced.status === "completed") { | ||
| return { success: true, txHash: raced.tx_hash, amount: raced.reward_amount }; | ||
| } | ||
| if (raced.status === "failed") { | ||
| const { data: reopened, error: reopenErr } = await supabaseAdmin | ||
| .from("referral_claims") | ||
| .update({ status: "pending", tx_hash: null, updated_at: new Date().toISOString() }) | ||
| .eq("id", raced.id) | ||
| .select() | ||
| .single(); | ||
| if (reopenErr || !reopened) { | ||
| return { success: false, code: "CLAIM_RETRY_FAILED", message: "Unable to retry this claim." }; | ||
| } | ||
| pendingClaimRow = reopened; | ||
| } else { | ||
| pendingClaimRow = raced; | ||
| } | ||
| } else if (insertError || !inserted) { | ||
| return { success: false, code: "CLAIM_CREATION_FAILED", message: "Unable to create claim record." }; | ||
| } else { | ||
| pendingClaimRow = inserted; | ||
| } | ||
| } | ||
|
|
||
| const pendingClaim = pendingClaimRow!; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the file first, then inspect the relevant sections.
ast-grep outline app/api/referral/claim/route.ts --view expanded
echo "---- lines around tryClaimOne / claim creation ----"
sed -n '340,480p' app/api/referral/claim/route.ts
echo "---- lines around GET / POST handlers ----"
sed -n '480,700p' app/api/referral/claim/route.ts
echo "---- search for claim status transitions and writeContract usage ----"
rg -n "writeContract|status:\s*\"processing\"|status:\s*\"pending\"|status:\s*\"failed\"|status:\s*\"completed\"|referral_claims" app/api/referral/claim/route.ts app -g '!**/node_modules/**'Repository: paycrest/noblocks
Length of output: 18695
Add an atomic claim lock before writeContract
pending rows are reused without any exclusive state transition, so two concurrent callers can both reach the transfer path and pay the same referral twice (for example, auto-claim GET and manual POST). Move the row into a unique processing state with an UPDATE ... WHERE status IN ('pending','failed') RETURNING *-style claim, and continue only if one caller wins.
🤖 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 `@app/api/referral/claim/route.ts` around lines 385 - 446, The claim row
handling in the referral claim flow is not locking ownership before the transfer
path, so concurrent callers can reuse the same pending row and double-pay;
update the logic around the pendingClaimRow/pendingClaim setup to atomically
transition the row into a unique processing state with a conditional update that
only succeeds from pending or failed. If the transition returns no row, abort
with a conflict/retry response, and only continue to writeContract after the
caller has exclusively won the claim lock.
| if (claimableReferrals.length > 0) { | ||
| const authHeader = request.headers.get("Authorization"); | ||
| const origin = request.headers.get("origin") || `https://${request.headers.get("host")}`; | ||
| fetch(`${origin}/api/referral/claim`, { | ||
| method: "GET", | ||
| headers: { | ||
| ...(authHeader ? { Authorization: authHeader } : {}), | ||
| "x-user-id": userId!, | ||
| }, | ||
| }).catch((e) => | ||
| console.error("Auto-claim background request failed:", e), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Host-header injection can exfiltrate the bearer token.
origin is derived from client-controlled origin/host headers, then the Authorization header is forwarded to ${origin}/api/referral/claim. An attacker who sets a spoofed Host (or Origin) header causes this server to send the user's bearer token to an attacker-controlled host. Build the self-call base URL from a trusted, server-configured value (e.g., an env var / NEXT_PUBLIC_APP_URL) rather than request headers.
🔒 Suggested change
- const authHeader = request.headers.get("Authorization");
- const origin = request.headers.get("origin") || `https://${request.headers.get("host")}`;
+ const authHeader = request.headers.get("Authorization");
+ const origin = process.env.NEXT_PUBLIC_APP_URL; // trusted, server-configured base URL
+ if (origin) {
fetch(`${origin}/api/referral/claim`, {
method: "GET",
headers: {
...(authHeader ? { Authorization: authHeader } : {}),
"x-user-id": userId!,
},
}).catch((e) =>
console.error("Auto-claim background request failed:", e),
);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (claimableReferrals.length > 0) { | |
| const authHeader = request.headers.get("Authorization"); | |
| const origin = request.headers.get("origin") || `https://${request.headers.get("host")}`; | |
| fetch(`${origin}/api/referral/claim`, { | |
| method: "GET", | |
| headers: { | |
| ...(authHeader ? { Authorization: authHeader } : {}), | |
| "x-user-id": userId!, | |
| }, | |
| }).catch((e) => | |
| console.error("Auto-claim background request failed:", e), | |
| ); | |
| } | |
| if (claimableReferrals.length > 0) { | |
| const authHeader = request.headers.get("Authorization"); | |
| const origin = process.env.NEXT_PUBLIC_APP_URL; // trusted, server-configured base URL | |
| if (origin) { | |
| fetch(`${origin}/api/referral/claim`, { | |
| method: "GET", | |
| headers: { | |
| ...(authHeader ? { Authorization: authHeader } : {}), | |
| "x-user-id": userId!, | |
| }, | |
| }).catch((e) => | |
| console.error("Auto-claim background request failed:", e), | |
| ); | |
| } | |
| } |
🤖 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 `@app/api/referral/referral-data/route.ts` around lines 279 - 291, The
auto-claim fetch in the referral-data route is building its base URL from
request-controlled origin/host headers, which can leak the Authorization token
to an attacker. Update the self-call in referral-data route to use a trusted
server-configured base URL instead of request.headers.get("origin") / host, and
keep the Authorization forwarding only for that safe internal target. Use the
existing claimableReferrals block and the fetch call to /api/referral/claim as
the place to fix this.
| let result; | ||
| try { | ||
| if (!isDeployed) { | ||
| result = await deployReadyAccount({ | ||
| walletId, | ||
| publicKey: walletPublicKey, | ||
| classHash, | ||
| userJwt: token, | ||
| userId: authUserId, | ||
| origin, | ||
| calls, | ||
| }); | ||
| } else { | ||
| result = await account.executePaymasterTransaction( | ||
| calls, | ||
| paymasterDetails, | ||
| maxFee, | ||
| ); | ||
| } | ||
| } catch (error: any) { | ||
| trackApiError(request, ROUTE, "POST", error, 500, { | ||
| wallet_address: walletAddress, | ||
| }); | ||
| return NextResponse.json( | ||
| { error: error.message || "Failed to execute transaction" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| const txReceipt = await account.waitForTransaction( | ||
| result.transaction_hash, | ||
| ); | ||
| if (!txReceipt.isSuccess()) { | ||
| return NextResponse.json( | ||
| { error: "Transaction reverted on-chain" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
| } catch { | ||
| return NextResponse.json( | ||
| { | ||
| error: | ||
| "Transaction submitted but confirmation failed. Please verify status in the explorer.", | ||
| transactionHash: result.transaction_hash, | ||
| }, | ||
| { status: 502 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file around the cited lines
wc -l app/api/starknet/earn/deposit/route.ts
sed -n '1,340p' app/api/starknet/earn/deposit/route.ts
# Search for obvious idempotency/duplicate-submission handling in the codebase
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
-e 'idempot|dedup|duplicate|retry|request.?id|idempotency.?key|transactionHash|transaction_hash|waitForTransaction|prepareVesuDepositCalls|executePaymasterTransaction|deployReadyAccount' \
app api lib src .Repository: paycrest/noblocks
Length of output: 50374
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant route and any persistence/state used to prevent duplicate deposits
sed -n '1,340p' app/api/starknet/earn/deposit/route.ts
printf '\n--- related files ---\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
-e 'deposit.*(record|log|idempot|dedup|status|txHash|requestId|nonce)|idempot|request.?id|retry|transaction_hash|waitForTransaction|executePaymasterTransaction|prepareVesuDepositCalls' \
app/api/starknet/earn -SRepository: paycrest/noblocks
Length of output: 10356
Add idempotency to the deposit POST This can submit the same deposit twice if the client retries after a lost response; the 502 branch only helps once a transaction hash already exists. Persist an idempotency key or request record before executePaymasterTransaction and short-circuit duplicates.
🤖 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 `@app/api/starknet/earn/deposit/route.ts` around lines 238 - 286, The deposit
POST in route.ts is not idempotent, so retries can submit the same transaction
twice before `account.executePaymasterTransaction` or `deployReadyAccount`
completes. Add a persisted idempotency key or request record in the POST handler
before the transaction call, keyed off the incoming request so duplicate
submissions can be detected and short-circuited. Update the flow around
`deployReadyAccount`, `account.executePaymasterTransaction`, and the
`trackApiError`/`waitForTransaction` branches to return the prior result for
repeated requests instead of sending a second on-chain transaction.
| const { allBalances, refreshBalance } = useBalance(); | ||
| const { positions, refreshPosition, deposit, withdraw } = useEarnHandler(); | ||
| const { tokens: availableEarnTokens } = useEarnAvailableTokens(); | ||
|
|
||
| const tokenDropdownItems = useMemo( | ||
| () => | ||
| availableEarnTokens.map((t) => ({ | ||
| name: t, | ||
| imageUrl: `/logos/${t.toLowerCase()}-logo.svg`, | ||
| })), | ||
| [availableEarnTokens], | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Files ==\n'
git ls-files 'app/components/EarnWalletForm.tsx' 'app/**/useEarnAvailableTokens*' 'app/**/useEarnHandler*' 'app/**/useBalance*' 'app/**/useEarn*' | sed 's#^`#-` #'
printf '\n== Outline: EarnWalletForm.tsx ==\n'
ast-grep outline app/components/EarnWalletForm.tsx --view expanded || true
printf '\n== Search for availability and positions sources ==\n'
rg -n "useEarnAvailableTokens|useEarnHandler|availableEarnTokens|positions\\b|setToken\\(" app -S
printf '\n== Locate Paycrest/Starknet token availability logic ==\n'
rg -n "Paycrest|Starknet|advertis|available token|availableTokens|EARN_TOKENS|suppliedBaseUnits" app -S
printf '\n== Read relevant files (small slices) ==\n'
for f in \
app/components/EarnWalletForm.tsx \
app/**/useEarnAvailableTokens* \
app/**/useEarnHandler* \
app/**/useBalance*; do
[ -e "$f" ] || continue
echo
echo "--- $f ---"
wc -l "$f"
doneRepository: paycrest/noblocks
Length of output: 43005
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== EarnWalletForm.tsx: token selection and withdraw gating =='
sed -n '70,190p' app/components/EarnWalletForm.tsx
echo
sed -n '400,500p' app/components/EarnWalletForm.tsx
echo
echo '== useEarnHandler.ts: advertised-token logic =='
sed -n '1,70p' app/hooks/useEarnHandler.tsRepository: paycrest/noblocks
Length of output: 10116
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('app/components/EarnWalletForm.tsx')
text = p.read_text()
for needle in [
'const tokenDropdownItems',
'const withdrawableTokens',
'const selectableTokens',
'hasAnySuppliedBalance',
'setToken(match)',
'availableEarnTokens.find',
]:
idx = text.find(needle)
print(f'\n## {needle} @ {idx}')
if idx != -1:
start = max(0, idx - 500)
end = min(len(text), idx + 900)
print(text[start:end])
print('\n== useEarnHandler.ts excerpt ==')
p2 = Path('app/hooks/useEarnHandler.ts')
text2 = p2.read_text()
for needle in ['getNetworkTokens("Starknet")', 'const intersection', 'setTokens(intersection.length > 0 ? intersection : EARN_TOKENS)']:
idx = text2.find(needle)
print(f'\n## {needle} @ {idx}')
if idx != -1:
start = max(0, idx - 300)
end = min(len(text2), idx + 700)
print(text2[start:end])
PYRepository: paycrest/noblocks
Length of output: 9140
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== EarnWalletForm.tsx around hasAnySuppliedBalance and submit logic =='
sed -n '150,240p' app/components/EarnWalletForm.tsx
echo
sed -n '240,330p' app/components/EarnWalletForm.tsx
echo
sed -n '330,520p' app/components/EarnWalletForm.tsx
echo
echo '== Search uses of hasAnySuppliedBalance =='
rg -n "hasAnySuppliedBalance" app/components/EarnWalletForm.tsx app -SRepository: paycrest/noblocks
Length of output: 13222
Include existing supplied tokens in withdraw selection availableEarnTokens gates the withdraw tab, dropdown, and onSelect, so a user with a non-zero position in a token that drops out of the current Starknet list can’t select it or withdraw it. Union the advertised tokens with any token that still has suppliedBaseUnits > 0 before building the withdraw-side list. app/components/EarnWalletForm.tsx:71-82, 161-173, 419-430
🤖 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 `@app/components/EarnWalletForm.tsx` around lines 71 - 82, The withdraw flow is
incorrectly limited to only `availableEarnTokens`, so tokens with an existing
non-zero supplied position can disappear from the withdraw tab and selection.
Update `EarnWalletForm` to union the advertised tokens from
`useEarnAvailableTokens` with any token from current `positions` that still has
`suppliedBaseUnits > 0`, and use that combined list for the withdraw-side
dropdown/tab and the `onSelect` logic. Keep the fix localized around
`tokenDropdownItems`, the withdraw tab rendering, and the withdraw selection
handlers in `EarnWalletForm`.
| const { client } = useSmartWallets(); | ||
|
|
||
| const showEarnUi = isEarnUiVisible(selectedNetwork.chain.name); | ||
| const { | ||
| isConsentModalOpen: isEarnConsentModalOpen, | ||
| requestEarnAccess, | ||
| handleConsentAccepted: handleEarnConsentAccepted, | ||
| dismissConsent: dismissEarnConsent, | ||
| } = useEarnAccess(); | ||
|
|
||
| const onEarnAccessAction = ( | ||
| action: "earn-modal" | "earn-tab" | "earn-hub", | ||
| ) => { | ||
| if (action === "earn-hub" || action === "earn-modal") { | ||
| setCurrentView("earn"); | ||
| } | ||
| }; | ||
|
|
||
| const walletBalanceUsd = crossChainTotal ?? 0; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File list ==\n'
git ls-files | rg '^(app/components/MobileDropdown\.tsx|app/components/WalletDetails\.tsx)$'
printf '\n== Outline: MobileDropdown.tsx ==\n'
ast-grep outline app/components/MobileDropdown.tsx --view expanded || true
printf '\n== Outline: WalletDetails.tsx ==\n'
ast-grep outline app/components/WalletDetails.tsx --view expanded || true
printf '\n== Relevant occurrences in MobileDropdown.tsx ==\n'
rg -n --context 3 'showEarnUi|currentView|earn-deposit|earn-withdraw|earn-activity-detail|setCurrentView|setSelectedEarnActivity|isEarnUiVisible' app/components/MobileDropdown.tsx
printf '\n== Relevant occurrences in WalletDetails.tsx ==\n'
rg -n --context 3 'sidebarView|showEarnUi|earn-deposit|earn-withdraw|earn-activity-detail|setSidebarView|isEarnUiVisible' app/components/WalletDetails.tsxRepository: paycrest/noblocks
Length of output: 13857
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== MobileDropdown effects touching network/view state ==\n'
rg -n --context 2 'useEffect\(|selectedNetwork|showEarnUi|currentView|setCurrentView\("wallet"\)|setSelectedEarnActivity\(null\)' app/components/MobileDropdown.tsx
printf '\n== MobileDropdown state declarations for view state ==\n'
sed -n '53,120p' app/components/MobileDropdown.tsx
printf '\n== MobileDropdown close/reset effects ==\n'
sed -n '236,270p' app/components/MobileDropdown.tsxRepository: paycrest/noblocks
Length of output: 10041
Reset the mobile Earn view when showEarnUi turns false
currentView can stay on earn, earn-deposit, or earn-withdraw after a network change, so those branches stop matching and the sheet body goes empty. Add the same fallback used in WalletDetails.tsx here, and clear any selected Earn activity as part of the reset.
🤖 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 `@app/components/MobileDropdown.tsx` around lines 266 - 284, Reset the
Earn-related mobile sheet state in MobileDropdown when showEarnUi becomes false,
since currentView can otherwise remain stuck on earn, earn-deposit, or
earn-withdraw and render nothing. Add the same fallback pattern used in
WalletDetails for the showEarnUi / currentView handling, and make sure the reset
also clears any selected Earn activity state so the view returns to a valid
non-Earn screen.
| const trimmed = transactionId.trim(); | ||
| if (!trimmed) { | ||
| return { orderId: "", chainId: null, canonicalTransactionId: "" }; | ||
| } | ||
|
|
||
| const match = /^(\d+)-(.+)$/.exec(trimmed); | ||
| if (match) { | ||
| const chainId = Number(match[1]); | ||
| const orderId = match[2].trim(); | ||
| if (Number.isInteger(chainId) && chainId > 0 && orderId.length > 0) { | ||
| return { | ||
| orderId, | ||
| chainId, | ||
| canonicalTransactionId: `${chainId}-${orderId}`, | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP '\bparseEvmChainPrefixedOrderId\s*\(' --type=ts -C3Repository: paycrest/noblocks
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file and related symbols first.
ast-grep outline app/lib/payment-order-id.ts --view expanded
# Find the helper definition and all references to related parser/formatter helpers.
rg -n "parseEvmChainPrefixedOrderId|isSenderPaymentOrderUuid|resolve.*PaymentOrder|SENDER_ORDER_UUID_RE|payment-order-id" app -S -C 2
# Inspect the target file with line numbers.
cat -n app/lib/payment-order-id.ts | sed -n '1,220p'Repository: paycrest/noblocks
Length of output: 6090
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the helper implementation and any exported entry points in the file.
cat -n app/lib/payment-order-id.ts | sed -n '1,260p'
# Find all in-repo references to the public helpers from this module.
rg -n "isGatewayOrderId|isSenderPaymentOrderUuid|resolvePaymentOrderId|resolve.*OrderId|canonicalTransactionId" app -S -C 2Repository: paycrest/noblocks
Length of output: 9695
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the module in small chunks to understand the parsing flow.
sed -n '1,220p' app/lib/payment-order-id.ts | cat -n
# Search for all imports/usages of this module.
rg -n "from ['\"].*payment-order-id['\"]|payment-order-id" . -S -g '!**/node_modules/**'Repository: paycrest/noblocks
Length of output: 2857
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the cashback route around the parser call and request validation.
sed -n '60,170p' app/api/blockfest/cashback/route.ts | cat -nRepository: paycrest/noblocks
Length of output: 4585
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect any docs or tests that describe accepted transactionId shapes for the cashback endpoint.
rg -n "blockfest cashback|transactionId|parseEvmChainPrefixedOrderId|sender payment order|UUID" app test tests -S -C 2Repository: paycrest/noblocks
Length of output: 14683
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Probe the parser semantics for a few representative inputs.
python3 - <<'PY'
import re
uuid_re = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', re.I)
prefix_re = re.compile(r'^(\d+)-(.+)$')
samples = [
"12345678-90ab-4cde-8f01-234567890abc",
"8453-12345678-90ab-4cde-8f01-234567890abc",
"0xabc",
" 12345678-90ab-4cde-8f01-234567890abc ",
]
for s in samples:
t = s.strip()
m = prefix_re.match(t)
if m and int(m.group(1)) > 0 and m.group(2).strip():
out = (m.group(2).strip(), int(m.group(1)), f"{int(m.group(1))}-{m.group(2).strip()}")
else:
out = (t, None, t)
print(s, "uuid?", bool(uuid_re.match(t)), "->", out)
PYRepository: paycrest/noblocks
Length of output: 618
Guard UUIDs before the chain-prefix parse ^(\d+)-(.+)$ still misparses sender UUIDs whose first block is all digits (for example 12345678-90ab-4cde-8f01-234567890abc) as { chainId: 12345678, orderId: "90ab-..." }. That breaks lookup for a valid subset of UUID inputs; check isSenderPaymentOrderUuid() before applying the prefix regex.
🧰 Tools
🪛 OpenGrep (1.23.0)
[ERROR] 45-45: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🤖 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 `@app/lib/payment-order-id.ts` around lines 40 - 56, The prefix parsing in
payment-order-id.ts is too permissive and can misread sender UUIDs as
chain-prefixed order IDs. Update the parsing flow in the transactionId handling
logic to call isSenderPaymentOrderUuid() before applying the /^(\d+)-(.+)$/
chain-prefix regex, and only attempt the chainId/orderId split when the value is
not a sender UUID. Keep the existing canonicalTransactionId and returned shape
in the parse routine unchanged for valid non-UUID inputs.
fix(wallet): export Starknet embedded key when network is Starknet (fix(wallet): export Starknet embedded key when network is Starknet #498)
feat(wallet): implement Starknet wallet export functionality with HPKE encryption
This feature enhances the wallet's functionality by enabling secure export of private keys for Starknet users.
feat(kyc): extract tiered KYC flow into clean branch (feat(kyc): extract tiered KYC flow into clean branch #496)
feat(kyc): extract tiered KYC flow into clean branch
Port the tiered KYC API, UI, and migration changes onto latest main while preserving current main exports and dependency baseline to keep the PR isolated and reviewable.
feat: implement referral system with API endpoints and UI components (feat: implement referral system with API endpoints and UI components #433)
feat: implement referral system with API endpoints and UI components
Added new API routes for submitting and retrieving referral data.
Introduced referral-related types and utility functions for handling referral codes.
Created UI components for referral dashboard and call-to-action, enhancing user engagement.
Updated mobile dropdown and main page content to include referral options and modals.
Enhanced middleware to support new referral API routes.
feat: enhance referral system and error handling
Added optional role property to referral data structure for better clarity.
Improved clipboard copy functionality with error handling for referral codes and links.
Updated referral data retrieval to include role information for referrers and referred users.
Enhanced error handling in the ReferralDashboard and ReferralDashboardView components for better user feedback.
feat: improve referral API and dashboard functionality
Refactored referral API routes to utilize a new method for retrieving user IDs from requests.
Updated referral data retrieval to ensure accurate wallet address handling.
Enhanced error handling for referral code generation and transaction volume checks.
Improved user feedback in the ReferralDashboard and ReferralDashboardView components for better user experience.
Standardized handling of referral amounts to ensure consistency across the application.
feat: add referral volume and reward configuration to environment and API
Introduced new environment variables for minimum qualifying volume and reward amount in the referral system.
Updated types to include referral configuration parameters.
Refactored referral claim logic to utilize new configuration values for volume checks and reward distribution.
Enhanced referral data retrieval to reflect user-specific claim statuses and auto-claim functionality for eligible referrals.
feat: enhance referral program configuration and error handling
Updated .env.example to better document referral program variables.
Improved KYC status verification in the referral claim API with enhanced error handling for better user feedback.
Refactored referral configuration values to ensure proper parsing and validation in the application.
refactor: rename avatar image utility function and update references
getAvatarImagetogetAvatarImageFromAddressfor clarity in its purpose.fix(referral): use config reward amount and normalize self-referral check
fix(referral): address PR review feedback on modal UX and copy
Remove duplicate submit toast, show referral prompt on login without requiring network modal, fix unescaped apostrophe, and standardize qualifying volume copy and env default to $20.
Remove race-prone preflight read, return 409 on referred_wallet_address unique violations, and add a case-insensitive unique index migration.
refactor(kyc): update layout and styling for KYC modal and profile drawer (refactor(kyc): update layout and styling for KYC modal and profile drawer #512)
refactor(kyc): update layout and styling for KYC modal and profile drawer
fix(privy): close getPrivyUserIdFromRequest function to improve code structure (fix(privy): close getPrivyUserIdFromRequest function to improve code structure #513)
feat: enable on-ramp on Starknet. (feat: enable on-ramp on Starknet. #504)
refactor(validation): streamline wallet address validation logic
isValidEvmAddressCaseInsensitivefunction with a newvalidateWalletAddressfunction that supports both EVM and Starknet address formats.RecipientDetailsFormandTransactionFormcomponents to utilize the new validation logic, enhancing code clarity and maintainability.refactor(TransactionForm): update connected wallet address handling
refactor(wallet): enhance useWalletAddress hook and update TransactionForm
refactor(TransactionForm): update wallet address validation logic
fixes (fixes #515)
feat(starknet): add Earn product via Vesu lending pools (feat(starknet): add Earn product via Vesu lending pools #499)
feat(starknet): add Earn product via Vesu lending pools
Adds an "Earn" action in the Starknet wallet drawer that lets users supply USDC and USDT to Vesu lending pools through the starkzap SDK:
Surfaces:
Integration:
Includes scripts/test-starkzap.ts: a read-only integration smoke test that hits Vesu mainnet (~2s) to verify SDK call shapes and pool wiring.
Address reviewer feedback from @0xLucqs on starkience#1: the deposit/withdraw routes accepted a wallet address from the request body without verifying it matched the one derived by buildReadyAccount. No attack vector (auth gates the route), but a buggy client could send a mismatched address and credit a Vesu position to the wrong owner.
Cleanest fix per his suggestion: remove the field. Compute the canonical address server-side via computeReadyAddress(walletPublicKey, classHash) using the publicKey fetched from Privy in getStarknetWallet. That value equals what buildReadyAccount derives, so mismatch is now impossible by construction. Drops a redundant validateAndParseAddress block and the unused starknet/validateAndParseAddress import; also drops the unused deployReadyAccount import in withdraw.
Client (useEarnHandler) no longer sends address in deposit/withdraw request bodies. UI readiness gate keeps the address check (it gates the button, not the payload).
CodeRabbit follow-up on PR #499:
Deposit / withdraw responses now include a
confirmed: booleanfield. WhenwaitForTransactionthrows (network error, RPC hiccup), the handler still returnssuccess: truewith the tx hash so the client gets the optimistic UX, butconfirmed: falseso the response is honest about the on-chain state. The client can ignore the field today (preserves current UX) or surface it in a future pass.Starknet wallet balances now carry
balancesInWei: Record<string, bigint>end-to-end:fetchStarknetBalancesUnified(app/utils.ts) was already computing the per-token bigint internally; it now stores and returns it, mirroring the EVM path.fetchStarknetBalancewrapper exposes the new field.WalletBalancesinterface (app/context/BalanceContext.tsx) declaresbalancesInWei?as an optional field, so existing consumers are unaffected.EarnWalletFormreadsallBalances.starknetWallet?.balancesInWei?.[token]directly instead of the lossyparseAmountToBaseUnits(walletBalanceUnit.toString())round-trip. The Max button and amount validation now use exact base units, eliminating precision loss at the 6th USDC decimal.The
WalletBalanceschange is backward-compatible (optional field with aBigInt("0")fallback in the consumer); other balance consumers (Transfer, swap, balance display) are unaffected.CodeRabbit follow-up on PR #499: the previous commit added
balancesInWei?to the WalletBalances interface and plumbed it through the Starknet path, but EVM balances still discarded it inbuildWalletBalancesFromRaw. That made the JSDoc claim ("Populated by Starknet and EVM fetchers") misleading and left a latent precision gap for any future caller doing exact-integer math on EVM amounts.Concretely:
buildWalletBalancesFromRawnow accepts an optionalbalancesInWeiparameter and returns it on the WalletBalances object. The CNGN conversion logic is unchanged; base units bypass the float corridor entirely (correct, since CNGN <-> NGN is a display-only rate).fetchWalletBalanceresult now passresult.balancesInWeithrough.[cngnRate]useEffect (re-applying the rate after it resolves) preservebalancesInWeifrom the existing WalletBalances object.fetchStarknetBalancesUnified(utils.ts) populatesbalanceWeion each ChainBalanceEntry, matching the EVM entries shape so per-token exact-integer math is available downstream.All changes are backward-compatible:
balancesInWeiis optional on WalletBalances, and existing consumers that read onlybalances(display number) are unaffected. The roadmap to bridge EVM USDC into Vesu via the Earn product can now readallBalances.<chain>?.balancesInWei?.[token]on any network without further infrastructure changes.Addresses CodeRabbit's follow-up on PR #499 (after @onahprosper asked for a recheck). The previous approach (return
success: true, confirmed: false) relied on a client-side check that does not exist in useEarnHandler, so withdrawals where on-chain confirmation failed were still being recorded as completed activity in the UI.Switching to CodeRabbit's original suggestion of an HTTP 502 response: the failure is now honest at the protocol level, the existing client check (
!res.ok || !data?.success) treats it correctly without any client change, and the response body still carriestransactionHashso callers can point the user to the explorer.Drops the now-unused
confirmedflag from both deposit and withdraw response shapes. Same simplification applied to both routes.Addresses @onahprosper's review feedback on PR #499: replaces the hard-coded EARN_TOKENS dropdown with a runtime fetch of Paycrest's supported-token list (via the existing
getNetworkTokens("Starknet")helper, which already wrapsapi.paycrest.io/v2/tokenswith cache + fallback).New
useEarnAvailableTokens()hook in useEarnHandler.ts:EARN_TOKENS(the registry of tokens we have Vesu pool mappings for) so we never expose a token without a pool.EARN_TOKENSlist if Paycrest is unreachable or returns no Starknet tokens.EarnWalletForm.tsx now consumes the hook for both the dropdown items and the
onSelecthandler (the previousname === "USDT" ? ... : ...hardcode is replaced with a lookup against the dynamic list).Effect: when Paycrest adds a new Starknet token AND we add a matching Vesu pool mapping to
EARN_TOKEN_CONFIG, the new token appears in the UI with no further code change. TheEARN_TOKENSregistry remains the source of truth for which tokens our backend can build calls for; it is kept static (and exported) for type-level coverage and for the position-refresh / activity-filter loops that should iterate every supported token regardless of Paycrest's current advertisement.Update app/api/starknet/earn/deposit/route.ts
refactor(starknet): extract applySafetyMargin to shared utility; gate fee estimation on isDeployed
Addresses two CodeRabbit comments on PR #499:
Fee estimation runs on undeployed accounts without deployment context, and the result is discarded on the deploy path.
In
app/api/starknet/earn/deposit/route.ts, the fee-estimation block now only runs whenisDeployed && !isSponsored. On the first-deposit path (account not yet deployed),deployReadyAccountalready builds its owndeploy_and_invokepaymaster transaction with properdeploymentDataand performs its own deployment-aware fee estimation internally, so estimating here withoutdeploymentDataproduced an inaccurate fee that was never used. Withdraw is unaffected because it already rejects undeployed accounts upfront.withMargin15was misleadingly named and duplicated across 5 sites.The helper actually applies a 1.5x (50%) safety margin via
(bi * 3 + 1) / 2, not 15%. Extracted into a single sharedapplySafetyMargin(v)export inapp/lib/starknet.tswith a clarifying doc comment. All 5 call sites updated to import and use it:app/lib/starknet.ts:deployReadyAccountapp/api/starknet/transfer/route.tsapp/api/starknet/create-order/route.tsapp/api/starknet/earn/deposit/route.tsapp/api/starknet/earn/withdraw/route.tsThe local duplicate copies are removed; the formula is unchanged. This
keeps the cross-route fee-margin behaviour consistent and gives the
helper an honest name that matches the math.
fix(earn): update EarnConsentModal risk copy and enhance MobileDropdown and WalletDetails components
fix(earn): update risk copy in EarnConsentModal and enhance balance checks in EarnWalletForm and EarnHubView components
feat(kyc): enhance SmileID verification process and error handling (feat(kyc): enhance SmileID verification process and error handling #516)
feat(kyc): enhance SmileID verification process and error handling
The desktop settings dropdown dropped the useWalletAddress() hook (KYC #496) and inlined EVM-only logic, so on Starknet it showed the EVM smart-wallet address instead of the Starknet Ready account. Restore the hook (which already handles the Starknet branch) with the migration-based EVM fallback, matching WalletDetails and MobileDropdown.
fix(referral): gate modal on server-side referral status to prevent cross-device reappearance and delayed load (fix(referral): gate modal on server-side referral status to prevent cross-device reappearance and delayed load #518)
fix(referral): gate modal on server-side referral status to prevent cross-device reappearance and delayed load
address review comments
address review comments
fix(ui): show correct address and networks in wallet copy modal (fix(ui): show correct address and networks in wallet copy modal #519)
fix(ui): show correct address and networks in wallet copy modal
updated privacy policy and terms (updated privacy policy and terms #509)
updated privacy policy and terms
feat(kyc): add KYC policy page and update routing
refactor(kyc): update KYC verification process and improve UI components (refactor(kyc): update KYC verification process and improve UI components #520)
refactor(kyc): update KYC verification process and improve UI components
refactor(referral): enhance claim qualification logic and error handling (refactor(referral): enhance claim qualification logic and error handling #522)
refactor(referral): enhance claim qualification logic and error handling
checkPartyQualificationfunction to streamline KYC and volume checks for both claimants and referees.tryClaimOnefunction to utilize the new qualification checks, improving code readability and maintainability.checkPartyQualificationfunction to include timeout handling and improved error messages for failed rate lookups.tryClaimOnefunction to streamline qualification checks and provide clearer error responses when verification fails.dirtyFieldsto form state to track changes in the recipient address field.showRecipientAddressErrorvariable to conditionally display error messages based on the recipient address's validity and its dirty state.useEffecthook to trigger validation only when the recipient address is not empty, enhancing performance and user experience.Audit of the entire kYC | Earn | Referral flow (Audit of the entire kYC | Earn | Referral flow #524)
fix(kyc): reliable scroll restore and fresh tier status after verification
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
Referral modal:
Network selector (root cause of 'referral popup only after refresh'): gate the open trigger on Privy 'ready' and re-check once the embedded wallet address settles, so a fresh signup reliably opens the network modal — the referral modal chains off its close callback.
Referral Pending/Earned cards: coerce reward_amount to Number in the API totals (Postgres numeric deserializes as string via PostgREST, so the reduce was string-concatenating and corrupting total_pending), and harden the card formatting against non-numeric values.
Earn disclaimer: key the 'seen' flag per Privy user id instead of a single device-global key, so a second user on the same device (and brand-new signups) see the risk disclosure instead of inheriting the first user's acceptance.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
Update CODEOWNERS to include additional reviewers for pull requests (Update CODEOWNERS to include additional reviewers for pull requests #525)
Update CODEOWNERS to include additional reviewers for pull requests
fix: apply CodeRabbit auto-fixes
Fixed 2 file(s) based on 1 unresolved review comment.
Share links are generated as ?ref=NBXXXX (handleCopyLink) but nothing ever consumed the parameter — the only reader of ref was the BlockFest hook — so users arriving via a referral link still had to type the code manually.
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
https://claude.ai/code/session_012WaogE9qdJ86chMiHNGPei
Referrers should only require one qualifying transaction
Replace Noblocks logos in Navbar with SVG image and adjust dropdown arrow position. Add new SVG logo file for Noblocks World Cup logo.
feat(kyc): enhance KYC status and phone verification flow
feat(referral): implement referral program feature flag and UI integration (feat(referral): implement referral program feature flag and UI integration #531)
feat(referral): implement referral program feature flag and UI integration
feat(utils): add normalizeStarknetAddressOrNull function and update E… (feat(utils): add normalizeStarknetAddressOrNull function and update E… #538)
feat(utils): add normalizeStarknetAddressOrNull function and update ExportStarknetWalletModal and StarknetContext to use it
refactor(utils): remove normalizeStarknetAddressOrNull and update address normalization in components
feat(kyc): implement unlimited monthly limit for tier 3 KYC users
feat(utils): add function to format first word of recipient name in t… (feat(utils): add function to format first word of recipient name in t… #500)
feat(utils): add function to format first word of recipient name in title case
formatRecipientNameFirstWordForPillto extract and title-case the first word of a recipient's name.TransactionStatuscomponent to utilize the new formatting function for improved display of recipient names.resetExportStatefunction to encapsulate the logic for resetting modal state variables.useEffecthook to simplify the closing behavior of the modal.AnimatePresencecomponent to callresetExportStateon exit completion for better state management.Replace the "On-ramp"/"Off-ramp" toggle labels in the swap modal with "Buy"/"Sell", and drop the " ~ 1 {token}" suffix from the preview rate.
Clear cached Starknet wallet keys on logout (regression from KYC #496). Also widen Buy/Sell toggle padding from px-3 to px-4.
Description
References
Testing
Checklist
mainBy submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.
Summary by CodeRabbit