UNPKG

swype-sdk

Version:

Official SDK for integrating Swype payment and credit services

1,447 lines (1,416 loc) 374 kB
// src/widgets/kyc/KycModal.tsx import { useMemo, useEffect as useEffect3 } from "react"; // src/components/Modal.tsx import { motion, AnimatePresence } from "framer-motion"; // src/components/SDKWrapper.tsx import { jsx } from "react/jsx-runtime"; function SDKWrapper({ children, className = "" }) { return /* @__PURE__ */ jsx("div", { className: `swype-sdk-root ${className}`, children }); } // src/components/Modal.tsx import { jsx as jsx2, jsxs } from "react/jsx-runtime"; function Modal({ title, children, onClose, onOutsideClick, overlayOpacity = "bg-black bg-opacity-100", topDistance = "pt-[100px]", open = true }) { return /* @__PURE__ */ jsx2(SDKWrapper, { children: /* @__PURE__ */ jsx2(AnimatePresence, { children: open && /* @__PURE__ */ jsx2( motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, className: `fixed inset-0 z-50 flex items-start justify-center ${topDistance} ${overlayOpacity} px-4 sm:px-0`, onClick: onOutsideClick || onClose, "aria-modal": "true", role: "dialog", children: /* @__PURE__ */ jsxs( motion.div, { initial: { scale: 0.95, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0.95, opacity: 0 }, transition: { duration: 0.2 }, className: "relative max-h-[90vh] w-full max-w-lg p-0 flex flex-col", onClick: (e) => e.stopPropagation(), children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pb-[14px]", children: [ /* @__PURE__ */ jsx2("h2", { className: "text-base sm:text-lg font-semibold text-[#E6E8ED]", children: title }), /* @__PURE__ */ jsx2( "button", { onClick: onClose, "aria-label": "Close modal", className: "text-gray-400 hover:text-white text-xl sm:text-2xl px-2", children: "\xD7" } ) ] }), /* @__PURE__ */ jsx2("div", { className: "border border-[#292930] rounded-[9px] bg-[#0D0D10] shadow-lg flex flex-col max-h-[70vh] overflow-y-auto [&::-webkit-scrollbar]:w-1.5 sm:[&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-[#292930] [&::-webkit-scrollbar-track]:border-0 [&::-webkit-scrollbar-thumb]:bg-[#808080] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border-0", children: /* @__PURE__ */ jsx2("div", { className: "px-4 sm:px-6 py-4 sm:py-6 relative", children }) }) ] } ) } ) }) }); } // src/stores/cardStatusStore.ts import { create as create4 } from "zustand"; // src/stores/sdkConfigStore.ts import { create as create3 } from "zustand"; // src/api/api.ts import axios2 from "axios"; // src/api/httpClient.ts import axios from "axios"; // src/api/auth.ts var authState = { signature: null, deadline: null }; function createCardOperationTypedData(eoa, cardId, timestamp, action, chainId) { return { types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" } ], CardOperation: [ { name: "eoa", type: "address" }, { name: "cardId", type: "string" }, { name: "action", type: "string" }, { name: "timestamp", type: "uint256" } ] }, primaryType: "CardOperation", domain: { name: "BrahmaCards", version: "1", chainId }, message: { eoa, cardId, action, timestamp } }; } var createLoginTypedData = (eoaAddress, timestamp) => ({ types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" } ], LoginRequest: [ { name: "eoa", type: "address" }, { name: "message", type: "string" }, { name: "timestamp", type: "uint256" }, { name: "action", type: "string" } ] }, primaryType: "LoginRequest", domain: { name: "BrahmaCards", version: "1" }, message: { eoa: eoaAddress, message: "I'm a BrahmaCards user", timestamp, action: "login" } }); var generateCardOperationSign = async (eoa, chainId, cardId, action, timestamp, signTypedData) => { const typedData = createCardOperationTypedData( eoa, cardId, timestamp, action, chainId ); const signature = await signTypedData(typedData); console.log({ typedData, signature, type: typeof signature }); return { signature }; }; var generateAuthSignature = async (eoaAddress, signTypedData) => { const deadline = Math.floor(Date.now() / 1e3); const typedData = createLoginTypedData(eoaAddress, deadline); const signature = await signTypedData(typedData); console.log({ typedData, deadline, signature, type: typeof signature }); return { signature, deadline }; }; var generateAuthSign = async (eoaAddress, signTypedData) => { return generateAuthSignature(eoaAddress, signTypedData); }; async function initializeAuth(eoaAddress, authSignature) { try { authState = { signature: authSignature.signature, deadline: authSignature.deadline }; console.log("SwypeSDK auth initialized"); } catch (error) { console.error("Failed to initialize SwypeSDK auth:", error); throw error; } } var ensureValidAuth = async () => { const { eoaAddress, isInitialized } = useSDKConfigStore.getState(); if (!isInitialized || !eoaAddress) { throw new Error("SwypeSDK not initialized. Call initializeSwypeSDK first."); } const currentTime = Math.floor(Date.now() / 1e3); if (!authState.signature || !authState.deadline) { throw new Error( "Authentication expired. Please re-initialize the SDK with a new signature." ); } }; var getAuthState = () => { return { ...authState }; }; var clearAuth = () => { authState = { signature: null, deadline: null }; }; // src/api/httpClient.ts function getApiClient() { const { eoaAddress, apiKey, baseUrl, apiPath, isInitialized } = useSDKConfigStore.getState(); if (!isInitialized || !apiKey) { throw new Error("SwypeSDK not initialized. Call initializeSwypeSDK first."); } const authState2 = getAuthState(); if (!authState2.signature || !authState2.deadline) { throw new Error( "SwypeSDK auth not ready. Authentication signature missing." ); } const fullBaseURL = `${baseUrl}${apiPath}`; const authJson = { timestamp: authState2.deadline, sig: authState2.signature, userAddr: eoaAddress }; const encodedAuth = btoa(JSON.stringify(authJson)); console.log({ encodedAuth, authJson }); const client = axios.create({ baseURL: fullBaseURL, headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${encodedAuth}` } }); client.interceptors.request.use( async (config) => { await ensureValidAuth(); const freshAuthState = getAuthState(); if (freshAuthState.signature && freshAuthState.deadline) { config.headers.set("Authorization", `Bearer ${encodedAuth}`); config.headers.set("x-api-key", apiKey); } return config; }, (error) => { return Promise.reject(error); } ); client.interceptors.response.use( (response) => response, (error) => { if (error.response) { console.error("API Error Response:", { status: error.response.status, data: error.response.data }); if (error.response.status === 401 || error.response.status === 403) { clearAuth(); } } return Promise.reject(error); } ); return client; } // src/api/utils.ts import forge from "node-forge"; import { v4 } from "uuid"; var formatUnits = (value, decimals) => { const divisor = BigInt(10 ** decimals); const quotient = value / divisor; const remainder = value % divisor; const formattedRemainder = remainder.toString().padStart(decimals, "0"); return `${quotient.toString()}.${formattedRemainder}`; }; var formatAvailableLimit = (availableLimit, creditType) => { const decimals = creditType === "aave-v3" ? 6 : 18; const formatted = Number(formatUnits(BigInt(availableLimit), decimals)); return Number(formatted.toFixed(4)); }; function mapEulerMetadataToSuppliedAssets(metadata) { if (!Array.isArray(metadata?.vaults)) return metadata; const suppliedAssets = metadata.vaults.map((vault) => { const formattedAsset = Number(formatUnits(BigInt(vault.assets || 0), 18)); console.log({ formattedAsset }); return { UsageAsCollateralEnabledOnUser: true, // Assume true for Euler amount: formattedAsset, amountUSD: 0, // No USD value provided, set to 0 or calculate if possible collateralAmountBase: String(formattedAsset), collateralAmountBig: formattedAsset, supplyRate: 0, // Not provided by Euler, set to 0 or fetch if available underlyingAsset: vault.vault || "" }; }); const suppliedValue = suppliedAssets.reduce( (sum, asset) => sum + asset.amountUSD, 0 ); return { ...metadata, suppliedAssets, suppliedValue }; } var waitFor = async (ms = 1500) => await new Promise((resolve) => setTimeout(resolve, ms)); function encryptPinWithSessionKey(pin, sessionKey) { const hexLength = pin.length.toString(16); const formattedPin = `2${hexLength}${pin}${"F".repeat(14 - pin.length)}`; const ivBytes = forge.random.getBytesSync(16); const encodedIV = forge.util.encode64(ivBytes); const keyBytes = forge.util.hexToBytes(sessionKey); const cipher = forge.cipher.createCipher("AES-GCM", keyBytes); cipher.start({ iv: ivBytes }); cipher.update(forge.util.createBuffer(formattedPin)); cipher.finish(); const encrypted = cipher.output.getBytes() + cipher.mode.tag.getBytes(); const encodedEncrypted = forge.util.encode64(encrypted); return { iv: encodedIV, data: encodedEncrypted }; } var generateUUID = (options) => { return v4(options); }; // src/api/api.ts var getCardStatus = async (eoaAddress) => { try { const client = getApiClient(); const response = await client.get(`/cards/user/status`); return response.data; } catch (error) { if (axios2.isAxiosError(error) && error.response?.status === 404) { return { data: "userNotExisting" }; } throw error; } }; var getUserCards = async (eoaAddress) => { try { const client = getApiClient(); const response = await client.get(`/cards/user`); return response.data; } catch (err) { console.error("[getUserCards]", err); return { data: null }; } }; var getCreditInformation = async (creditAddress, creditType) => { try { const client = getApiClient(); const response = await client.get( `/cards/credit/${creditType}` ); if (response.data?.data && typeof response.data.data.availableLimit === "number") { response.data.data.availableLimit = formatAvailableLimit( response.data.data.availableLimit, creditType ); response.data.data.amountDelegated = formatAvailableLimit( response.data.data.amountDelegated, creditType ); } if (creditType === "euler" && response.data?.data?.metadata) { response.data.data.metadata = mapEulerMetadataToSuppliedAssets( response.data.data.metadata ); } return response.data; } catch (error) { console.error("[getCreditInformation]", error); return { data: null }; } }; var getSumSubAccessToken = async (userId, ipAddress) => { try { const client = getApiClient(); const response = await client.post( "/cards/sumsub/token", { userId, ipAddress } ); return response.data.data; } catch (error) { console.error("[error at getSumSubAccessToken]", error); return void 0; } }; var getCreditDelegationTxData = async (requestData) => { const eoa = requestData.from; const payload = { creditType: requestData.creditType, creditLimit: requestData.creditLimit, userParams: requestData?.userParams }; const client = getApiClient(); const response = await client.post( `/cards/credit/delegate`, payload ); return response.data; }; var updateCard = async (requestData) => { const { apiKey, baseUrl, apiPath } = useSDKConfigStore.getState(); try { const client = axios2.create({ baseURL: `${baseUrl}${apiPath}`, headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${requestData.auth}`, sessionID: generateUUID() } }); const response = await client.patch(`/cards/update/creditprovider`, { creditProviderAddress: requestData.creditProviderAddress, chainId: requestData.chainId, creditType: requestData.creditType }); if (response.status === 200) { return { success: true }; } return { success: false, message: response.data?.message || "Failed to update card" }; } catch (error) { console.error("[updateCard]", error); return { success: false, message: error?.response?.data?.data?.error || error?.message || "Failed to update card" }; } }; var createCard = async (userEoa, creditType) => { const client = getApiClient(); const response = await client.post(`/cards/create`, { creditProviderAddress: userEoa, chainID: 8453, creditType }); return response.data; }; var freezeCard = async (eoa, cardID, chainID, signature, timestamp, freeze, action) => { const { apiKey, baseUrl, apiPath } = useSDKConfigStore.getState(); const authJson = { cardID, action, version: "1", timestamp, chainID, sig: signature, userAddr: eoa }; const encodedAuth = btoa(JSON.stringify(authJson)); const client = axios2.create({ baseURL: `${baseUrl}${apiPath}`, headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${encodedAuth}`, sessionID: generateUUID() } }); try { const response = await client.patch(`/cards/update/status`, { cardID, freeze }); if (response.status === 200) { return { success: true }; } return { success: false, message: response.data?.message || "Failed to freeze card" }; } catch (error) { console.error("[freezeCard]", error); return { success: false, message: error?.response?.data?.data?.error || error?.message || "Failed to freeze card" }; } }; var updatePin = async (eoa, cardID, chainID, sessionID, signature, timestamp, iv, data, action) => { const { apiKey, baseUrl, apiPath } = useSDKConfigStore.getState(); const authJson = { cardID, action, version: "1", timestamp, chainID, sig: signature, userAddr: eoa }; const encodedAuth = btoa(JSON.stringify(authJson)); const client = axios2.create({ baseURL: `${baseUrl}${apiPath}`, headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${encodedAuth}`, sessionID } }); try { const response = await client.patch( `/cards/update/pin`, { pin: { encryptedPin: { data, iv } } } ); if (response.status === 200) { return { success: true }; } return { success: false, message: response.data?.message || "Failed to update PIN" }; } catch (error) { console.error("[updatePin]", error); return { success: false, message: error?.response?.data?.data?.error || error?.message || "Failed to update PIN" }; } }; var getCardSecrets = async (eoa, cardID, chainID, sessionID, signature, timestamp, action) => { const { apiKey, baseUrl, apiPath } = useSDKConfigStore.getState(); const authJson = { cardID, action, version: "1", timestamp, chainID, sig: signature, userAddr: eoa }; const encodedAuth = btoa(JSON.stringify(authJson)); const client = axios2.create({ baseURL: `${baseUrl}${apiPath}`, headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${encodedAuth}`, sessionID } }); try { const response = await client.get(`/cards/secrets`); if (response.status === 200 && response.data?.data) { return response.data.data; } throw new Error("Unexpected response format while fetching card secrets."); } catch (error) { console.error("[getCardSecrets] Error fetching card secrets:", error); const errorMessage = error?.response?.data?.data?.error || error?.response?.data?.message || error?.message || "Failed to fetch card secrets"; throw new Error(errorMessage); } }; var getCardPublicKey = async () => { const client = getApiClient(); const response = await client.get("/cards/pubkey"); return response.data; }; var getTransactions = async (userAddress, cardID, limit, cursor) => { await waitFor(2e3); console.log("api called"); try { const client = getApiClient(); const response = await client.get( `cards/transactions/${cardID}`, { params: { limit, cursor } } ); return response.data; } catch (error) { console.error("[getTransactions]", error); return { data: { transactions: [], pagination: { limit: limit || 50, has_next: false } } }; } }; var getOnchainTransactionDetails = async (transactionID) => { try { const client = getApiClient(); const response = await client.get( `cards/transactions/${transactionID}/onchain` ); return response.data; } catch (error) { console.error("[getOnchainTransactionDetails]", error); return { data: [] }; } }; var getUserIsUs = async () => { try { const client = getApiClient(); const response = await client.get(`cards/user/is_us`); return response.data; } catch (error) { console.error("[getUserIsUs]", error); return { data: false }; } }; // src/stores/creditStore.ts import { create } from "zustand"; var useCreditStore = create((set) => ({ creditInfo: { card: null, aave: null, euler: null }, isLoading: false, error: null, // Internal methods for SDK use only (prefixed with _) _setCreditInfo: (type, info) => set((state) => ({ creditInfo: { ...state.creditInfo, [type]: info } })), _setLoading: (loading) => set({ isLoading: loading }), _setError: (error) => set({ error }), _reset: () => set({ creditInfo: { card: null, aave: null, euler: null }, isLoading: false, error: null }) })); // src/stores/transactionsStore.ts import { create as create2 } from "zustand"; var useTransactionsStore = create2((set) => ({ transactions: [], pagination: null, isLoading: false, initialFetchDone: false, error: null, // Internal methods for SDK use only (prefixed with _) _setTransactions: (transactions) => set({ transactions }), _appendTransactions: (newTransactions) => set((state) => ({ transactions: [...state.transactions, ...newTransactions] })), _setPagination: (pagination) => set({ pagination }), _setLoading: (loading) => set({ isLoading: loading }), _setInitialFetchDone: (done) => set({ initialFetchDone: done }), _setError: (error) => set({ error }), _reset: () => set({ transactions: [], pagination: null, isLoading: false, initialFetchDone: false, error: null }) })); // src/core/dataFetcher.ts var DataFetcher = class _DataFetcher { static instance; hasFetched = false; static getInstance() { if (!_DataFetcher.instance) { _DataFetcher.instance = new _DataFetcher(); } return _DataFetcher.instance; } async fetchAllData() { const { eoaAddress, isInitialized } = useSDKConfigStore.getState(); if (!isInitialized || !eoaAddress || this.hasFetched) { return; } console.log("\u{1F680} SDK: Starting data fetch for:", eoaAddress); const cardStore = useCardStatusStore.getState(); const creditStore = useCreditStore.getState(); cardStore._setLoading(true); creditStore._setLoading(true); try { const [ cardStatusResponse, userCardsResponse, aaveCreditResponse, eulerCreditResponse ] = await Promise.all([ getCardStatus(eoaAddress), getUserCards(eoaAddress), getCreditInformation(eoaAddress, "aave-v3"), getCreditInformation(eoaAddress, "euler") ]); const userCard = userCardsResponse.data?.[0] || null; cardStore._setCardStatus(cardStatusResponse.data, userCard); creditStore._setCreditInfo("aave", aaveCreditResponse); creditStore._setCreditInfo("euler", eulerCreditResponse); this.hasFetched = true; console.log("\u2705 SDK: Successfully fetched all data for:", eoaAddress); } catch (error) { console.error("\u274C SDK: Error fetching data:", error); const errorMessage = error instanceof Error ? error.message : "Failed to fetch data"; cardStore._setError(errorMessage); creditStore._setError(errorMessage); } finally { cardStore._setLoading(false); creditStore._setLoading(false); } } // Public method to trigger data fetching after authentication async fetchDataAfterAuth() { this.hasFetched = false; await this.fetchAllData(); } reset() { this.hasFetched = false; useCardStatusStore.getState()._reset(); useCreditStore.getState()._reset(); useTransactionsStore.getState()._reset(); } }; var dataFetcher = DataFetcher.getInstance(); // src/stores/sdkConfigStore.ts var useSDKConfigStore = create3((set, get) => ({ eoaAddress: null, apiKey: null, baseUrl: null, apiPath: null, isInitialized: false, setConfig: (config) => { get().reset(); set({ eoaAddress: config.eoaAddress, apiKey: config.apiKey, baseUrl: config.baseUrl || "https://dev.console.fi", apiPath: config.apiPath || "/v1/vendor", isInitialized: true }); }, reset: () => { set({ eoaAddress: null, apiKey: null, baseUrl: null, apiPath: null, isInitialized: false }); dataFetcher.reset(); } })); // src/stores/cardStatusStore.ts var isCardDelegatedToCurrentEOA = (userCard) => { if (!userCard) return false; const { eoaAddress } = useSDKConfigStore.getState(); if (!eoaAddress) return false; return userCard.creditProviderAddress.toLowerCase() === eoaAddress.toLowerCase(); }; var useCardStatusStore = create4((set) => ({ status: null, userCard: null, isLoading: false, error: null, isUserCardDelegated: false, // Internal methods for SDK use only (prefixed with _) _setCardStatus: (status, userCard) => { const isCardDelegated = isCardDelegatedToCurrentEOA(userCard); set({ status, userCard, isUserCardDelegated: isCardDelegated }); }, _setLoading: (loading) => set({ isLoading: loading }), _setUserStatus: (status) => set({ status }), _setError: (error) => set({ error }), _reset: () => set({ status: null, userCard: null, isUserCardDelegated: false, isLoading: false, error: null }) })); // src/stores/useKycStore.ts import { create as create5 } from "zustand"; var useKycStore = create5((set) => ({ acceptedTerms: false, acceptedRainTerms: false, isKycModalOpen: false, isSumsubProfileVerified: false, setAcceptedTerms: (accepted) => set({ acceptedTerms: accepted }), setAcceptedRainTerms: (accepted) => set({ acceptedRainTerms: accepted }), setKycModalOpen: (open) => set({ isKycModalOpen: open }), setIsSumsubProfileVerified: (verified) => set({ isSumsubProfileVerified: verified }) })); // src/widgets/kyc/SumSubConsentView.tsx import { useState } from "react"; // src/components/shared/Checkbox.tsx import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; function Checkbox({ checked, onChange, label, className }) { return /* @__PURE__ */ jsxs2( "label", { className: `flex items-center gap-2 p-[20px_16px] border-[1.125px] border-[#18181C] bg-[#18181C] align-self-stretch ${className || ""}`, style: { borderRadius: 12 }, children: [ /* @__PURE__ */ jsx3("span", { children: /* @__PURE__ */ jsx3( "input", { type: "checkbox", checked, onChange: (e) => onChange(e.target.checked), className: "peer appearance-none w-[15px] h-[15px] rounded-[3px] border border-[#CBCED1] bg-[#18181C] checked:bg-[#18181C] checked:border-[#CBCED1] flex items-center justify-center transition-colors duration-150 focus:outline-none relative cursor-pointer" } ) }), /* @__PURE__ */ jsx3("span", { className: "pointer-events-none absolute w-[15px] h-[15px] flex items-center justify-center cursor-pointer", children: checked && /* @__PURE__ */ jsxs2( "svg", { width: "15", height: "15", viewBox: "0 0 27 27", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [ /* @__PURE__ */ jsx3( "rect", { x: "1.5", y: "1.5", width: "24", height: "24", rx: "6", fill: "#18181C", stroke: "#CBCED1", strokeWidth: "3" } ), /* @__PURE__ */ jsx3( "path", { d: "M7 14L12 19L20 9", stroke: "#CBCED1", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round" } ) ] } ) }), /* @__PURE__ */ jsx3("span", { className: "text-[#CBCED1] font-['Neue Montreal'] text-[15.75px] font-medium leading-[22.5px] ml-2 select-none", children: label }) ] } ); } // src/widgets/kyc/SumSubConsentView.tsx import { InfoCircledIcon } from "@radix-ui/react-icons"; import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; function SumSubConsentView() { const [checked1, setChecked1] = useState(false); const { setAcceptedTerms } = useKycStore(); return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-6 w-full pt-4", children: [ /* @__PURE__ */ jsx4("p", { className: "text-[#CBCED1] font-['Neue Montreal'] text-[18px] font-medium leading-[22.5px]", children: "Provide consent to proceed" }), /* @__PURE__ */ jsx4("div", { className: "flex flex-col gap-1 w-full", children: /* @__PURE__ */ jsx4( Checkbox, { checked: checked1, onChange: setChecked1, label: "I accept E-Sign consent", className: "rounded-t-md" } ) }), /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [ /* @__PURE__ */ jsx4("span", { children: /* @__PURE__ */ jsx4( InfoCircledIcon, { color: "#CBCED1", style: { width: "20px", height: "20px" } } ) }), /* @__PURE__ */ jsx4("p", { className: "text-[#CBCED1] font-['Neue Montreal'] text-[15.75px] font-medium leading-[22.5px]", children: "BrahmaFi card is not affiliated with swype.fun , AAVE, Euler or other Defi and issued subject to separate terms provided by Issuer." }) ] }), /* @__PURE__ */ jsx4( "button", { className: `flex h-[44px] justify-center items-center gap-1 self-stretch rounded-lg bg-white backdrop-blur-[6px] text-black font-medium text-[16px] transition ease-in-out ${checked1 ? "cursor-pointer opacity-100" : "cursor-not-allowed opacity-50"}`, disabled: !checked1, onClick: () => setAcceptedTerms(true), children: "Continue to Sumsub Verification" } ), /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [ /* @__PURE__ */ jsx4("span", { children: /* @__PURE__ */ jsx4( InfoCircledIcon, { color: "#CBCED1", style: { width: "20px", height: "20px" } } ) }), /* @__PURE__ */ jsx4("p", { className: "text-[#CBCED1] font-['Neue Montreal'] text-[15.75px] font-medium leading-[22.5px]", children: "Brahma never stores your data we use Sumsub that completes the verification process to ensure safety" }) ] }) ] }); } // src/widgets/kyc/KycPendingViaRain.tsx import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime"; function KycPendingViaRain() { return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-[10px]", style: { gap: "10px" }, children: [ /* @__PURE__ */ jsxs4( "div", { className: "rounded-[4px] border border-[#292930] bg-black flex flex-col items-center justify-center gap-[15px] h-[372px]", style: { height: "372px", gap: "15px" }, children: [ /* @__PURE__ */ jsx5(PendingSvg, {}), /* @__PURE__ */ jsxs4("p", { className: "text-[#E6E8ED] text-center font-['Neue Montreal'] text-[23.391px] font-medium leading-[120%]", children: [ "Processing your", /* @__PURE__ */ jsx5("br", {}), "KYC Verificationn" ] }) ] } ), /* @__PURE__ */ jsx5("p", { className: "text-[#E6E8ED] text-center font-['Neue Montreal'] text-[16px] font-medium leading-[120%]", children: "Your KYC verification is pending and may take up to 2 days. Please check back later for updates." }) ] }); } var PendingSvg = () => { return /* @__PURE__ */ jsxs4( "svg", { width: "88", height: "89", viewBox: "0 0 88 89", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [ /* @__PURE__ */ jsx5( "path", { d: "M5.49609 3.19922H43.7531L54.4651 12.993V72.98H5.49609V3.19922Z", fill: "#FFAD0D" } ), /* @__PURE__ */ jsx5( "path", { d: "M41.2347 8.17186H7.9063V67.5467H52.3441V19.3047H41.2347V8.17186ZM7.9063 0.75H44.9378L59.7504 15.5937V67.5467C59.7504 69.5151 58.9701 71.4029 57.5812 72.7948C56.1922 74.1867 54.3084 74.9686 52.3441 74.9686H7.9063C5.94203 74.9686 4.05821 74.1867 2.66926 72.7948C1.2803 71.4029 0.5 69.5151 0.5 67.5467V8.17186C0.5 6.20346 1.2803 4.31568 2.66926 2.92381C4.05821 1.53194 5.94203 0.75 7.9063 0.75ZM15.3126 34.1484H44.9378V41.5702H15.3126V34.1484ZM15.3126 48.9921H44.9378V56.414H15.3126V48.9921Z", fill: "#0D0D10" } ), /* @__PURE__ */ jsx5( "path", { d: "M54.4465 61.1122C44.3921 61.1122 24.3926 66.6428 24.3926 77.5397V85.7535H84.5005V77.5397C84.5005 66.6428 64.5009 61.1122 54.4465 61.1122ZM61.0584 54.6894C65.2113 52.3348 68.1074 47.8994 68.1074 42.7521C68.1074 35.1954 61.9873 29.0625 54.4465 29.0625C46.9057 29.0625 40.7856 35.1954 40.7856 42.7521C40.7856 47.8994 43.6817 52.3348 47.8347 54.6894C49.8018 55.7846 52.0422 56.4417 54.4465 56.4417C56.8508 56.4417 59.0912 55.7846 61.0584 54.6894Z", fill: "white", stroke: "#0D0D10", strokeWidth: "5.25554" } ) ] } ); }; // src/widgets/kyc/PreRainTerms.tsx import { useState as useState2, useEffect } from "react"; import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime"; function PreRainTerms() { const [checked1, setChecked1] = useState2(false); const [checked2, setChecked2] = useState2(false); const [checked3, setChecked3] = useState2(false); const { setAcceptedRainTerms } = useKycStore(); const [isUsResident, setIsUsResident] = useState2(null); const { eoaAddress } = useSDKConfigStore(); useEffect(() => { if (!eoaAddress) return; const checkResidency = async () => { try { const res = await getUserIsUs(); setIsUsResident(res.data); } catch { setIsUsResident(false); } }; checkResidency(); }, [eoaAddress]); const allChecked = checked1 && checked2 && checked3; return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-6 w-full pt-4", children: [ /* @__PURE__ */ jsx6("div", { className: "text-[#CBCED1] font-['Neue Montreal'] text-[18px] font-medium leading-[22.5px]", children: "By continuing you accept our terms" }), /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1 w-full", children: [ /* @__PURE__ */ jsx6( Checkbox, { checked: checked1, onChange: setChecked1, label: /* @__PURE__ */ jsxs5(Fragment, { children: [ "I accept the", " ", /* @__PURE__ */ jsx6("a", { href: "#", className: "underline", children: "BrahmaFi Card Terms Privacy Policy" }) ] }), className: "rounded-t-md" } ), /* @__PURE__ */ jsx6( Checkbox, { checked: checked2, onChange: setChecked2, label: "I certify that the information I have provided is accurate and that I will abide by all the rules and requirements related to my BrahmaFi Card" } ), /* @__PURE__ */ jsx6( Checkbox, { checked: checked3, onChange: setChecked3, label: isUsResident === null ? /* @__PURE__ */ jsx6(Fragment, { children: "Checking residency..." }) : isUsResident ? /* @__PURE__ */ jsx6(Fragment, { children: "I am a U.S. resident" }) : /* @__PURE__ */ jsxs5(Fragment, { children: [ "I confirm I'm not a U.S. resident, have reviewed the", " ", /* @__PURE__ */ jsx6("a", { href: "#", className: "underline", children: "supported countries list" }), ", comply with local laws, and understand the BrahmaFi card is not an unauthorised solicitation." ] }), className: "rounded-b-md" } ) ] }), /* @__PURE__ */ jsx6( "button", { className: `flex h-[44px] justify-center items-center gap-1 self-stretch rounded-lg bg-white backdrop-blur-[6px] text-black font-medium ${!allChecked ? "opacity-50 cursor-not-allowed" : ""}`, disabled: !allChecked, onClick: () => setAcceptedRainTerms(true), children: "Continue" } ) ] }); } // src/components/SumSubWidget.tsx import SumsubWebSdk from "@sumsub/websdk-react"; import { motion as motion2 } from "framer-motion"; // src/hooks/use-sumsub.ts import { useState as useState3, useEffect as useEffect2, useCallback } from "react"; // src/utils/getUserIpAddress.ts var getUserIpAddress = async () => { try { const response = await fetch("https://api.ipify.org?format=json"); const data = await response.json(); return data.ip; } catch (err) { console.error("[error at getUserIpAddress]", err); return ""; } }; // src/hooks/use-sumsub.ts var useSumSub = ({ eoaAddress, onApplicantIdChange, onVerificationComplete }) => { const [accessToken, setAccessToken] = useState3(null); const [isLoading, setIsLoading] = useState3(true); const [error, setError] = useState3(null); const [tokenRefreshCount, setTokenRefreshCount] = useState3(0); const [applicantId, setApplicantId] = useState3(null); const getAccessToken = useCallback(async () => { console.log("Attempting to get SumSub access token..."); setIsLoading(true); setError(null); try { if (!eoaAddress) throw new Error("EOA address not provided"); const ipAddress = await getUserIpAddress(); const tokenData = await getSumSubAccessToken(eoaAddress, ipAddress); if (!tokenData?.token) { throw new Error("No token received from backend"); } setAccessToken(tokenData.token); setTokenRefreshCount(0); console.log("SumSub access token obtained successfully."); } catch (err) { console.error("Error getting access token:", err); setError( err instanceof Error ? err.message : "Failed to get access token. Please try again." ); } finally { setIsLoading(false); } }, [eoaAddress]); useEffect2(() => { if (eoaAddress) { getAccessToken(); } }, [getAccessToken, eoaAddress]); const handleMessage = useCallback( (message) => { console.log("Sumsub Message Received:", message.type, message.payload); if (message.payload?.applicantId && typeof message.payload.applicantId === "string") { const newApplicantId = message.payload.applicantId; if (newApplicantId !== applicantId) { setApplicantId(newApplicantId); onApplicantIdChange?.(newApplicantId); console.log("Applicant ID updated:", newApplicantId); } } if (message.type === "idCheck.applicantReviewed" || message.payload?.reviewStatus === "completed") { console.log("Verification process completed."); onVerificationComplete?.(); } }, [applicantId, onApplicantIdChange, onVerificationComplete] ); const handleError = useCallback((sdkError) => { console.error("Sumsub WebSDK error:", sdkError); setError(`Sumsub SDK Error: ${sdkError.message}. Please try again.`); }, []); const handleTokenExpiration = useCallback(async () => { console.warn("Sumsub token expired, attempting refresh..."); if (tokenRefreshCount >= 2) { console.error("Max token refresh attempts reached."); setError("Failed to refresh session. Please try again later."); setAccessToken(null); return; } setTokenRefreshCount((prev) => prev + 1); setTimeout(async () => { await getAccessToken(); }, 2e3); }, [tokenRefreshCount, getAccessToken]); const retry = useCallback(() => { setTokenRefreshCount(0); getAccessToken(); }, [getAccessToken]); return { accessToken, isLoading, error, applicantId, handleMessage, handleError, handleTokenExpiration, retry }; }; // src/components/SumSubWidget.tsx import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime"; var LoadingBar = ({ className = "" }) => { return /* @__PURE__ */ jsx7( "div", { className: `h-4 overflow-hidden bg-white relative w-full ${className}`, children: /* @__PURE__ */ jsx7( motion2.div, { className: "bg-red-500 h-full absolute top-0 left-0", initial: { width: "0%" }, animate: { width: "100%" }, transition: { duration: 2, ease: "linear", repeat: Infinity, repeatType: "loop" } } ) } ); }; var RetroButton = ({ children, variant = "default", className = "", ...props }) => { const variantClasses = { default: "bg-[#E6BE8A] text-[#3B2F2F] border-[#8B4513] hover:bg-[#E6BE8A]", primary: "bg-[#8B4513] text-[#FFD700] border-[#D4AF37] hover:bg-[#8B4513]" }; return /* @__PURE__ */ jsx7( "button", { className: ` font-['Times_New_Roman',_Times,_serif] px-4 py-1.5 text-base ${variantClasses[variant]} ${className} border-2 active:translate-y-[1px] flex items-center justify-center gap-2 transition-none disabled:opacity-50 disabled:cursor-not-allowed `, ...props, children: /* @__PURE__ */ jsx7("span", { children }) } ); }; function SumSubWidget({ eoaAddress, onApplicantIdChange, onVerificationComplete }) { const { accessToken, isLoading, error, handleMessage, handleError, handleTokenExpiration, retry } = useSumSub({ eoaAddress, onApplicantIdChange, onVerificationComplete }); const { _setUserStatus } = useCardStatusStore(); const { setIsSumsubProfileVerified, isSumsubProfileVerified } = useKycStore(); if (isLoading) { return /* @__PURE__ */ jsx7(SDKWrapper, { children: /* @__PURE__ */ jsxs6("div", { className: "text-center p-6 min-h-[30vh] flex flex-col justify-center", children: [ /* @__PURE__ */ jsx7("p", { className: "font-['MS_Sans_Serif',_Arial,_sans-serif] mb-4 text-white", children: "Loading SumSub verification module..." }), /* @__PURE__ */ jsx7(LoadingBar, { className: "w-[50rem] mx-auto" }) ] }) }); } if (error || !accessToken) { return /* @__PURE__ */ jsx7(SDKWrapper, { children: /* @__PURE__ */ jsxs6("div", { className: "text-center p-6 min-h-[10vh] flex flex-col justify-center", children: [ /* @__PURE__ */ jsx7("p", { className: "font-['MS_Sans_Serif',_Arial,_sans-serif] mb-4 text-red-600", children: error || "Failed to load verification module. Please try again." }), /* @__PURE__ */ jsx7(RetroButton, { variant: "primary", onClick: retry, children: "Try Again" }) ] }) }); } const sdkKey = `sumsub-sdk-${accessToken}`; return /* @__PURE__ */ jsx7(SDKWrapper, { children: /* @__PURE__ */ jsxs6("div", { className: "relative rounded-md w-full min-h-[30vh]", children: [ /* @__PURE__ */ jsx7("div", { className: "relative w-full", style: { minHeight: "400px" }, children: /* @__PURE__ */ jsx7( SumsubWebSdk, { accessToken, expirationHandler: handleTokenExpiration, config: { lang: "en", customizationName: "swype", theme: "dark" }, options: { addViewportTag: false, adaptIframeHeight: true }, onMessage: (message, payload) => { console.log("Sumsub onMessage:", message.type, payload); if (payload && payload.reviewStatus === "completed") { _setUserStatus("initiated"); setIsSumsubProfileVerified(true); } handleMessage(message); }, onError: handleError }, sdkKey ) }), /* @__PURE__ */ jsx7("div", { className: "w-full mt-4", children: /* @__PURE__ */ jsx7( "button", { disabled: !isSumsubProfileVerified, className: `flex h-[44px] w-full justify-center items-center gap-1 self-stretch rounded-lg bg-white backdrop-blur-[6px] text-black font-medium disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-gray-100`, onClick: () => _setUserStatus("pending"), children: "Continue" } ) }) ] }) }); } // src/widgets/kyc/KycModal.tsx import { Fragment as Fragment2, jsx as jsx8 } from "react/jsx-runtime"; function KycModal({ eoaAddress, onClose, onComplete }) { const { status } = useCardStatusStore(); const { acceptedTerms, acceptedRainTerms } = useKycStore(); useEffect3(() => { if (status === "approved" && onComplete) { onComplete(); } }, [status, onComplete]); const getModalView = useMemo(() => { switch (status) { case "userNotExisting": case "feePending": case "initiated": { if (acceptedTerms) { return { title: "Complete KYC", component: /* @__PURE__ */ jsx8(SumSubWidget, { eoaAddress }) }; } else { return { title: "Verifying with Sumsub", component: /* @__PURE__ */ jsx8(SumSubConsentView, {}) }; } } case "review": { return { title: "KYC Verification is in Process", component: /* @__PURE__ */ jsx8(KycPendingViaRain, {}) }; } case "pending": { if (acceptedRainTerms) { return { title: "Accept Terms to Proceed", component: /* @__PURE__ */ jsx8(KycPendingViaRain, {}) }; } else { return { title: "KYC Verification is in Process", component: /* @__PURE__ */ jsx8(PreRainTerms, {}) }; } } case "approved": { return { title: "", component: /* @__PURE__ */ jsx8(Fragment2, {}) }; } default: return { title: "Apply for Card", component: /* @__PURE__ */ jsx8(Fragment2, {}) }; } }, [status, acceptedTerms, acceptedRainTerms]); const handleClose = onClose || (() => { }); const { title, component } = getModalView; if (status === "approved" || !component) { return null; } return /* @__PURE__ */ jsx8(Modal, { title, onOutsideClick: handleClose, onClose: handleClose, children: component }); } // src/components/shared/Typography.tsx import { jsx as jsx9 } from "react/jsx-runtime"; function Typography({ children, className = "", onClick }) { return /* @__PURE__ */ jsx9("span", { className, onClick, children }); } // src/components/icons/BrahmaLogoWithNameIcon.tsx import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime"; function BrahmaLogoWithNameIcon({ width = 24, height = 24, className = "", color = "#020202" }) { return /* @__PURE__ */ jsxs7( "svg", { width, height, viewBox: "0 0 165 26", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [ /* @__PURE__ */ jsx10( "path", { d: "M3.17017 15.3146C0.526313 19.419 -0.400434 23.0071 1.12 24.5276C3.42603 26.8338 10.4882 23.5102 16.8939 17.1043C17.5275 16.4707 18.1309 15.8307 18.7017 15.1895C18.9591 15.6095 19.1836 16.0163 19.3726 16.4048C19.366 16.412 19.3595 16.4192 19.3529 16.4264C20.1113 18.0058 20.2753 19.2747 19.6688 19.8837L19.6736 19.8885C18.3637 21.1985 16.7764 22.062 15.1008 22.479C19.2043 25.1222 22.7915 26.0486 24.3118 24.5283C24.3295 24.5105 24.3469 24.4925 24.364 24.4742L24.4163 24.5265C26.1567 22.6732 24.4777 17.9446 20.5897 12.9132C24.4732 7.86762 26.1245 3.13742 24.3169 1.32973C22.7967 -0.190567 19.2094 0.735793 15.1059 3.379C16.7815 3.79602 18.3689 4.65953 19.6789 5.96955L19.6763 5.97221C20.4335 6.72947 19.9946 8.5117 18.7 10.6428C18.1411 10.0169 17.5512 9.39221 16.9327 8.77364C12.7767 4.61761 8.34487 1.75676 5.11017 0.884273C3.34268 0.403192 1.93094 0.514261 1.11486 1.33038C-0.405557 2.85086 0.521161 6.43895 3.16497 10.5433C3.58194 8.8674 4.44545 7.27986 5.75549 5.96977L5.75703 5.97131C5.7745 5.95391 5.79252 5.93714 5.81107 5.921L5.82939 5.93932C6.53614 5.31855 8.02553 5.61887 9.83682 6.6082C11.4192 7.47738 13.2449 8.87043 15.0074 10.6329C15.7709 11.3965 16.4651 12.1719 17.0774 12.9343C16.4673 13.6931 15.7762 14.4647 15.0164 15.2245C11.1748 19.0663 7.03252 21.1526 5.76448 19.8845L5.76073 19.8883C4.45065 18.5782 3.58713 16.9906 3.17017 15.3146Z", fill: color } ), /* @__PURE__ */ jsx10( "path", { d: "M53.6055 13.6965C53.3596 13.4604 53.065 13.2765 52.7218 13.1385C52.3787 13.0035 52.0022 12.9054 51.5861 12.8441C52.2815 12.6816 52.8312 12.3872 53.2259 11.9518C53.6237 11.5194 53.8242 10.8601 53.8242 9.97089C53.8242 8.78726 53.4142 7.91333 52.6034 7.34912C51.7926 6.78797 50.5901 6.50586 49.0019 6.50586H37.666V19.3234H49.5516C50.2834 19.3234 50.9484 19.2559 51.5406 19.121C52.1358 18.9861 52.6429 18.7714 53.065 18.4832C53.4871 18.1919 53.809 17.827 54.0367 17.3885C54.2614 16.95 54.3768 16.4287 54.3768 15.8216C54.3768 15.3401 54.31 14.9231 54.1764 14.5735C54.0428 14.2209 53.8515 13.9265 53.6085 13.6965H53.6055ZM40.5083 8.87924H49.0019C49.6244 8.87924 50.1133 8.98964 50.4686 9.21042C50.8239 9.43426 51.0001 9.81143 51.0001 10.3388C51.0001 10.8663 50.827 11.2526 50.4777 11.4489C50.1285 11.6482 49.6518 11.7463 49.0566 11.7463H40.5083V8.87618V8.87924ZM51.0395 16.6188C50.6934 16.8427 50.1984 16.95 49.5516 16.95H40.5083V13.9695H49.5516C50.25 13.9695 50.7541 14.0982 51.0729 14.3558C51.3887 14.6134 51.5497 14.9844 51.5497 15.4659C51.5497 16.0117 51.3796 16.3919 51.0395 16.6157V16.6188Z", fill: color } ), /* @__PURE__ */ jsx10( "path", { d: "M74.2133 13.4083C74.6324 12.7214 74.848 11.918 74.848 10.992V10.9736C74.848 10.3143 74.7447 9.71943 74.5443 9.17668C74.3439 8.63086 74.0251 8.16171 73.5817 7.76921C73.1414 7.37365 72.5705 7.06088 71.8751 6.84316C71.1767 6.61932 70.3416 6.50586 69.3638 6.50586H58.5957V9.08162V12.8962V19.3264H61.4411V15.3647H69.382L71.948 19.3264H75.1577L72.1484 14.9538C73.1049 14.6103 73.7912 14.0921 74.2164 13.4083H74.2133ZM71.9844 10.992C71.9844 11.6482 71.7718 12.1266 71.3497 12.4332C70.9276 12.7429 70.2626 12.8962 69.3577 12.8962H58.5957V9.08162H69.3577C70.2626 9.08162 70.9246 9.23801 71.3497 9.54158C71.7688 9.85129 71.9844 10.3296 71.9844 10.9705V10.9889V10.992Z", fill: color } ), /* @__PURE__ */ jsx10( "path", { d: "M131.998 13.8747L125.044 6.52148H122.293V19.339H125.172V10.5967L131.3 17.1159H132.694L138.819 10.5967V19.339H141.719V6.52148H138.946L131.998 13.8747Z", fill: color } ), /* @__PURE__ */ jsx10( "path", { d: "M115.018 11.6332H103.755V6.52148H100.84V19.339H103.755V14.2641H115.018V19.339H117.955V6.52148H115.018V11.6332Z", fill: color } ), /* @__PURE__ */ jsx10( "path", { d: "M89.1331 6.52148H86.2331L78.0918 19.339H81.2469L87.6846 9.07578L94.1224 19.339H97.2745L89.1331 6.52148Z", fill: color } ), /* @__PURE__ */ jsx10( "path", { d: "M156.859 6.52148H153.959L145.817 19.339H148.972L155.41 9.07578L161.848 19.339H165L156.859 6.52148Z", fill: color } ), /* @__PURE__ */ jsx10( "rect", { x: "82.4775", y: "14.0586", width: "10.3247", height: "2.45311", fill: color } ), /* @__PURE__ */ jsx10( "rect", { x: "150.196", y: "14.0586",