refactor(referral): enhance claim qualification logic and error handling#522
Conversation
- 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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughExtracts KYC and volume checks into a reusable helper with structured QualificationResult, adds conditional cNGN→USD conversion, updates tryClaimOne to allow referrer-or-referee callers and role-dependent checks, and tweaks client gating state for referral modal eligibility. ChangesReferral Claim Qualification Refactor
Sequence DiagramsequenceDiagram
participant Caller
participant tryClaimOne
participant checkPartyQualification
participant Supabase as Supabase(KYC)
participant Aggregator as ExternalAggregator
Caller->>tryClaimOne: POST claim (wallet)
tryClaimOne->>tryClaimOne: verify caller equals referrer or referred
alt Caller is Referrer
tryClaimOne->>checkPartyQualification: check referrer (KYC + volume)
checkPartyQualification->>Supabase: fetch tier
Supabase-->>checkPartyQualification: tier response
alt cNGN present
checkPartyQualification->>Aggregator: fetch NGN→USD rate (with timeout)
Aggregator-->>checkPartyQualification: rate or error
end
checkPartyQualification-->>tryClaimOne: qualification result
tryClaimOne->>checkPartyQualification: check referee (KYC + volume)
checkPartyQualification->>Supabase: fetch tier
Supabase-->>checkPartyQualification: tier response
checkPartyQualification-->>tryClaimOne: qualification result
else Caller is Referee
tryClaimOne->>checkPartyQualification: check referee (KYC + volume)
checkPartyQualification->>Supabase: fetch tier
Supabase-->>checkPartyQualification: tier response
alt cNGN present
checkPartyQualification->>Aggregator: fetch NGN→USD rate (with timeout)
Aggregator-->>checkPartyQualification: rate or error
end
checkPartyQualification-->>tryClaimOne: qualification result
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/api/referral/claim/route.ts (1)
97-106:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon’t turn FX lookup failures into false
VOLUME_NOT_METrejections.Line 104 swallows every cNGN rate lookup failure, and Lines 114-127 then treat that qualifying volume as
0. That makes eligible users fail qualification whenever the aggregator is down, misconfigured, or slow. Please return a transient verification error here instead, and add a timeout so the claim request is not blocked indefinitely by the rate service.Suggested fix
let ngnPerUsd: number | null = null; if (hasCngn) { try { const rateUrl = `${process.env.NEXT_PUBLIC_AGGREGATOR_URL}/rates/USDC/1/NGN`; - const rateRes = await fetch(rateUrl); + const rateRes = await fetch(rateUrl, { + signal: AbortSignal.timeout(5000), + }); + if (!rateRes.ok) { + throw new Error(`rate lookup failed with status ${rateRes.status}`); + } const rateJson = (await rateRes.json()) as { data?: string }; const parsed = Number(rateJson?.data); - if (parsed > 0) ngnPerUsd = parsed; + if (parsed > 0) { + ngnPerUsd = parsed; + } else { + throw new Error("invalid NGN rate payload"); + } } catch { - // If rate fetch fails, cNGN txs will be skipped (counted as 0). + return { + ok: false, + code: "VERIFICATION_ERROR", + message: "Unable to verify qualifying transaction volume. Please try again later.", + }; } }Also applies to: 114-128
🤖 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 97 - 106, The FX lookup currently swallows all errors in the hasCngn block (rateUrl/rateRes/rateJson/parsed setting ngnPerUsd) which causes downstream qualification to treat cNGN volume as 0 and trigger VOLUME_NOT_MET; change this to use a fetch with a bounded timeout (AbortController or equivalent) and if the rate fetch fails or times out, surface a transient verification error instead of silently setting ngnPerUsd to 0 — i.e., return or throw a transient error response from the claim route so the request can be retried, and only fall back to zero if you explicitly decide that is safe after a successful, non-timed-out fetch. Ensure you update the hasCngn/ngnPerUsd logic and any code paths that compare volumes against VOLUME_NOT_MET to rely on the explicit transient error path rather than a default zero.
🤖 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/referral/claim/route.ts`:
- Around line 162-178: Move the completed-claim lookup/short-circuit so it runs
before building/executing the qualificationChecks block: check whether the claim
is already completed (the existing "completed" lookup/short-circuit) before
creating qualificationChecks and before the loop that awaits each check (the for
(const check of qualificationChecks) { const qualification = await check; ...
}). Only run checkPartyQualification for referrer/referred wallets (controlled
by isReferrerClaim, referrerWallet, referredWallet, referralCreatedAt) when the
claim is new or pending; if the claim is completed return immediately and do not
perform KYC/volume revalidation.
---
Outside diff comments:
In `@app/api/referral/claim/route.ts`:
- Around line 97-106: The FX lookup currently swallows all errors in the hasCngn
block (rateUrl/rateRes/rateJson/parsed setting ngnPerUsd) which causes
downstream qualification to treat cNGN volume as 0 and trigger VOLUME_NOT_MET;
change this to use a fetch with a bounded timeout (AbortController or
equivalent) and if the rate fetch fails or times out, surface a transient
verification error instead of silently setting ngnPerUsd to 0 — i.e., return or
throw a transient error response from the claim route so the request can be
retried, and only fall back to zero if you explicitly decide that is safe after
a successful, non-timed-out fetch. Ensure you update the hasCngn/ngnPerUsd logic
and any code paths that compare volumes against VOLUME_NOT_MET to rely on the
explicit transient error path rather than a default zero.
🪄 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: c9dc40d2-e303-4029-9bf8-9bc5e27f3a2d
📒 Files selected for processing (1)
app/api/referral/claim/route.ts
|
@sundayonah please review coderabbit |
…n 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.
not neccessary anymore. |
* 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>
Description
Fixes referral payout rules so rewards align with product spec: a referrer should not be paid until the referred user qualifies, and the referrer must also meet their own requirements.
Problem:
tryClaimOnetreated referrer and referee as fully independent — each party only needed their own KYC (Tier 1) and qualifying transaction volume. A referrer could receive $1 USDC even when their referee had not verified phone or completed enough volume.Solution: Introduced
checkPartyQualification()to centralize KYC + volume checks per wallet. UpdatedtryClaimOneto apply different rules by role:NEXT_PUBLIC_REFERRAL_MIN_QUALIFYING_VOLUME_USD, default $20) since the referral code was appliedreferrals.created_at)Error messages distinguish claimant vs referee failures (e.g. "Your referee must verify their phone number..." vs "You must verify your phone number...").
Impacts:
app/api/referral/claim/route.ts(GET auto-claim + POST manual claim)KYC_REQUIRED/VOLUME_NOT_METreferral_claimsrowAlternatives considered:
References
N/A — internal referral program behavior fix.
Testing
Manual (staging or local with Supabase + rewards wallet configured):
total_earnedupdates in referral hub.referral_claimsper wallet).No unit tests added — claim logic is server-side with Supabase + on-chain dependencies; covered by manual QA above.
Checklist
mainBy submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.
Summary by CodeRabbit
New Features
Bug Fixes