Conversation
Adds KYC gate to the fiat checkout panel that verifies user identity before allowing order creation. Includes useKycStatus hooks and KycGate component. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request implements a crucial KYC (Know Your Customer) verification system, which is now a prerequisite for fiat checkouts. It introduces a dedicated UI component and a set of custom hooks to manage the identity verification process, ensuring regulatory compliance and enhanced security by preventing order creation until a user's identity is confirmed. The changes provide a seamless, guided flow for users to complete their verification. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a KYC verification flow using the Persona SDK. It adds a KycGate component that blocks the fiat checkout process until the user's identity is verified. The implementation is well-structured, with new hooks for managing KYC state (useKycStatus, useCreateKycInquiry, useVerifyKyc) and clean integration into the FiatCheckoutPanel. My review includes suggestions to improve code clarity by removing redundant useMemo calls in the new hooks, enhancing error handling in the KYC completion callback for a better user experience, and making a type assertion safer. Overall, this is a solid implementation of a critical feature.
| } catch { | ||
| // Will be picked up by polling via refetch | ||
| } |
There was a problem hiding this comment.
The catch block for the verifyKyc call is empty. Silently swallowing this error can lead to a confusing user experience. If the verification call fails, the user won't receive any immediate feedback. While the status will be refetched, it's better to inform the user about the error by setting an error state.
| } catch { | |
| // Will be picked up by polling via refetch | |
| } | |
| } catch (error) { | |
| setPersonaError(error instanceof Error ? error.message : "Failed to complete verification. We will retry automatically."); | |
| } |
| inquiryId: config.inquiryId, | ||
| sessionToken: config.sessionToken, | ||
| ...(config.templateId && { templateId: config.templateId }), | ||
| environment: (config.environment as "sandbox" | "production") || "sandbox", |
There was a problem hiding this comment.
The type assertion (config.environment as "sandbox" | "production") is not entirely safe. If the API were to return a different string for the environment, it could lead to unexpected behavior. It's safer to explicitly validate the value and default to "sandbox" if it's not "production".
| environment: (config.environment as "sandbox" | "production") || "sandbox", | |
| environment: config.environment === "production" ? "production" : "sandbox", |
| return useMemo( | ||
| () => ({ | ||
| kycStatus: data || null, | ||
| isLoadingKycStatus: isLoading, | ||
| kycStatusError: error, | ||
| refetchKycStatus: refetch, | ||
| }), | ||
| [data, isLoading, error, refetch], | ||
| ); |
There was a problem hiding this comment.
The useMemo hook here is redundant. The values returned from react-query's useQuery hook (data, isLoading, error, refetch) are already stable. You can simplify the code by returning the object directly. This improves readability and removes unnecessary memoization. This also applies to useCreateKycInquiry and useVerifyKyc hooks in this file.
return {
kycStatus: data || null,
isLoadingKycStatus: isLoading,
kycStatusError: error,
refetchKycStatus: refetch,
};| return useMemo( | ||
| () => ({ | ||
| createInquiry: mutateAsync, | ||
| isCreatingInquiry: isPending, | ||
| }), | ||
| [mutateAsync, isPending], | ||
| ); |
| return useMemo( | ||
| () => ({ | ||
| verifyKyc: mutateAsync, | ||
| isVerifying: isPending, | ||
| }), | ||
| [mutateAsync, isPending], | ||
| ); |
persona@^6.0.0 does not exist — latest is 5.7.0. Updated package.json and regenerated pnpm-lock.yaml to fix CI frozen-lockfile failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused KycStatusResponse import and useCallback import - Replace non-null assertion with type assertion for walletAddress - Fix missing openPersonaFlow dependency by reordering useCallback hooks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…te for card payments - Switch KYC identification from wallet address to B3 userId (JWT-based) - KycGate: add Sign In button when user is not authenticated - AnySpend: require login before creating stripe-web2 orders, open sign-in modal if not authenticated - useKycStatus: remove walletAddress param, send auth header for all KYC API calls - BottomNavigation: fix React DOM SVG camelCase attribute warnings - anyspend-demo-vite: configure VITE_ANYSPEND_BASE_URL to point to local backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace B3 JWT (Authorization: Bearer) with EIP-191 wallet signature in useKycStatus hooks — headers: X-Wallet-Address, X-Wallet-Signature, X-Wallet-Timestamp via wagmi's useAccount + useSignMessage - Cache signed headers for 4 min to avoid repeated wallet prompts (server allows 5-min window) - Add FIAT_AUTH panel (LoginStep inline) to AnySpend for B3 sign-in without losing flow state; detect auth via useEffect on isAuthenticated - Fix Persona Client: remove templateId when resuming with inquiryId (mutually exclusive fields) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strip border/bg/rounded wrapper from all KycGate states so content renders flat inside the parent checkout card instead of adding a second nested card layer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntity - Defer wallet signature prompt until user clicks Buy (not on panel mount) - Use kycApprovedRef (useRef) instead of useState to avoid stale closure when handleFiatOrder is called synchronously after KycGate resolves - Fix MaxListenersExceeded: cache wagmi config per partnerId in getCachedWagmiConfig to avoid recreating connectors on every render - Use verifyMessage (EIP-1271 + EIP-6492) via Base public client instead of recoverMessageAddress to support Coinbase Smart Wallet and thirdweb smart accounts (B3 Global Account) - Pass wallet auth headers with stripe-web2 order requests so backend KYC lookup uses the signing wallet address, not the B3 JWT address (fixes identity mismatch for users with multiple wallets) - Use friendlier wallet signature message for KYC auth Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the user is on the FIAT_KYC panel (Persona iframe), clicking outside the iframe was closing the entire AnySpend modal, forcing the user to restart the flow. Now the modal is non-closable while activePanel === FIAT_KYC and automatically becomes closable again when the user leaves that panel. Uses the existing setClosable action from useModalStore (same pattern as CryptoPaymentMethod's connect-wallet flow). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ESLint @typescript-eslint/no-non-null-assertion blocked the build. Replace wagmiConfigCache.get(key)! with a local variable pattern that avoids the assertion while keeping the same semantics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When closable=false, using onPointerDownOutside and onInteractOutside with e.preventDefault() caused Radix to intercept focus-related events and block focus from moving to the Persona iframe — making it non-interactable. Fix: remove the two outside-interaction handlers and rely solely on handleOpenChange to prevent the modal from closing. handleOpenChange already has the guard `if (!open && !isClosable) return` which achieves the same close-prevention without touching focus or pointer propagation. onEscapeKeyDown is kept to prevent Escape from closing during KYC. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The setClosable(false) approach caused Radix's FocusScope to reinitialize on re-render, stealing focus from the Persona iframe and making it non-interactable. New approach: - Remove setClosable from AnySpend.tsx entirely (Persona iframe works again) - Auto-resume Persona in KycGate when the gate activates and there is a pending inquiry (status==="pending" && inquiry exists). This handles the case where the user accidentally closed the modal mid-KYC: returning to the FIAT_KYC panel automatically reopens Persona at the same step. - autoResumedRef guards against firing more than once per gate mount. - personaCancelled guard prevents auto-resume after the user explicitly cancelled, so they still see the manual Resume button. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The wallet signing prompt was triggered inside React Query's queryFn
(an async, non-user-gesture context), which caused browsers and wallets
(especially Coinbase Smart Wallet) to block the popup — showing only a
loading spinner with no action possible.
Fix: call getKycHeaders() inside handleFiatOrder (which runs directly from
a button click) before navigating to FIAT_KYC. The 4-minute module-level
cache in useWalletAuthHeaders means useKycStatus's queryFn reuses the
cached signature immediately, skipping the signing prompt entirely.
The .catch(() => {}) is intentional: if signing fails (e.g., user cancels),
we still navigate to the panel where the error state is shown gracefully.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Persona opens as a fullscreen overlay, clicks on the Persona UI or accidentally outside it were dismissing the AnySpend checkout modal. After KYC completed the order state was lost and users had no way back. Fix: call setClosable(false) immediately before client.open() and setClosable(true) in every exit path (onComplete, onCancel, onError, and component unmount). This keeps the checkout modal open throughout the entire Persona session. Because B3DynamicModal no longer sets onPointerDownOutside or onInteractOutside when closable=false (relying on handleOpenChange instead), the Persona iframe itself remains fully interactive. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…king Locking the modal with setClosable(false) prevents the Persona iframe from receiving click events. Instead, let Radix close the modal when the user clicks inside the Persona iframe (outside-click), then reopen it in onComplete after KYC approval via useModalStore.getState().isOpen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
KycGatecomponent with Persona SDK integration for identity verificationuseKycStatus,useCreateKycInquiry, anduseVerifyKychooks for KYC state managementFiatCheckoutPanel— blocks order creation until identity is verifiedpersonadependency topackage.jsonTest plan
🤖 Generated with Claude Code