@brahmafi/cards-sdk
Version:
Official SDK for integrating Swype payment and credit services
1 lines • 682 kB
Source Map (JSON)
{"version":3,"sources":["../src/widgets/kyc/KycModal.tsx","../src/components/Modal.tsx","../src/components/SDKWrapper.tsx","../src/stores/cardStatusStore.ts","../src/stores/sdkConfigStore.ts","../src/api/api.ts","../src/api/httpClient.ts","../src/api/auth.ts","../src/api/utils.ts","../src/config/creditRegistry.ts","../src/components/icons/USDT0Icon.tsx","../src/components/icons/USDeIcon.tsx","../src/components/icons/USDhlIcon.tsx","../src/stores/creditStore.ts","../src/stores/transactionsStore.ts","../src/stores/rewardsStore.ts","../src/api/rewards/api.ts","../src/stores/xpHistoryStore.ts","../src/stores/referralEarningsStore.ts","../src/stores/hookTransactionsStore.ts","../src/hooks/useGetHookTransactions.ts","../src/core/dataFetcher.ts","../src/stores/useKycStore.ts","../src/components/icons/SuccessKycSvg.tsx","../src/widgets/dashboard/components/InfoIcon.tsx","../src/widgets/dashboard/components/ContentWrapper.tsx","../src/widgets/dashboard/components/WalletTag.tsx","../src/components/shared/Typography.tsx","../src/utils/textUtils.ts","../src/components/icons/CopyIcon.tsx","../src/components/icons/OpenInNewTabIcon.tsx","../src/widgets/dashboard/components/FreezeCardTransactions.tsx","../src/widgets/dashboard/components/CustomButton.tsx","../src/widgets/dashboard/components/CustomInput.tsx","../src/widgets/kyc/KycPendingViaRain.tsx","../src/components/SumSubWidget.tsx","../src/hooks/use-sumsub.ts","../src/utils/getUserIpAddress.ts","../src/widgets/kyc/KycModalWrapper.tsx","../src/components/icons/PendingKycSvg.tsx","../src/components/icons/RejectedSvg.tsx","../src/components/icons/ResubmissionKycIcon.tsx","../src/widgets/dashboard/ManageCardModal.tsx","../src/components/Drawer.tsx","../src/components/icons/BrahmaLogoWithNameIcon.tsx","../src/components/icons/USDCIcon.tsx","../src/components/icons/VisaIcon.tsx","../src/hooks/useCreditData.ts","../src/components/shared/CreditCard.tsx","../src/hooks/useCardData.ts","../src/hooks/useCardDetails.ts","../src/core/secureCardMethods.ts","../src/components/icons/ChevronRightIcon.tsx","../src/components/icons/PencilIcon.tsx","../src/widgets/dashboard/useIsMobile.ts","../src/core/createCard.ts","../src/core/delegateAmount.ts","../src/core/updateCardProvider.ts","../src/core/cardHookMethods.ts","../src/core/random.ts","../src/api/sdk.ts","../src/hooks/useGetTransactions.ts","../src/hooks/useRewardsData.ts","../src/hooks/useXPHistory.ts","../src/hooks/useReferralEarnings.ts"],"sourcesContent":["import { useMemo, useEffect, JSX } from \"react\";\nimport { Modal } from \"../../components/Modal\";\nimport { useCardStatusStore } from \"../../stores/cardStatusStore\";\nimport { useKycStore } from \"../../stores/useKycStore\";\nimport KycPendingViaRain from \"./KycPendingViaRain\";\nimport PreRainTerms from \"./PreRainTerms\";\nimport { SumSubWidget } from \"../../components/SumSubWidget\";\nimport KycRejectedViaRain from \"./KycRejectedViaRain\";\nimport KycModalWrapper from \"./KycModalWrapper\";\nimport SuccessKycSvg from \"../../components/icons/SuccessKycSvg\";\nimport PendingKycSvg from \"../../components/icons/PendingKycSvg\";\nimport RejectedSvg from \"../../components/icons/RejectedSvg\";\nimport ResubmissionKycIcon from \"../../components/icons/ResubmissionKycIcon\";\n\ninterface KycModalProps {\n eoaAddress: string;\n onClose?: () => void;\n onComplete?: () => void;\n}\nexport function KycModal({\n eoaAddress,\n onClose,\n onComplete,\n}: KycModalProps): JSX.Element | null {\n const { status } = useCardStatusStore();\n const { acceptedRainTerms } = useKycStore();\n\n // Call onComplete when status becomes approved\n useEffect(() => {\n if (status === \"approved\" && onComplete) {\n onComplete();\n }\n }, [status, onComplete]);\n\n const getModalView = useMemo(() => {\n switch (status) {\n case \"userNotExisting\": {\n return {\n title: \"User Verification\",\n component: <SumSubWidget eoaAddress={eoaAddress} />,\n };\n }\n\n case \"pending\": {\n if (acceptedRainTerms) {\n return {\n title: \"User Verification is in Process\",\n component: <KycPendingViaRain />,\n };\n } else {\n return {\n title: \"User Verification is in Process\",\n component: (\n <KycModalWrapper\n icon={<PendingKycSvg />}\n text=\"Your verification is being processed and may take up to 2 days to complete. Please check back later for issuance.\"\n buttonText=\"Dashboard\"\n onButtonClick={onClose}\n />\n ),\n };\n }\n }\n\n case \"rejected\": {\n return {\n title: \"User Verification Rejected\",\n component: (\n <KycModalWrapper\n icon={<RejectedSvg />}\n text=\"Your User Verification was rejected. Please contact support to resolve the issue.\"\n buttonText=\"Contact Support\"\n onButtonClick={onClose}\n />\n ),\n };\n }\n\n case \"resubmit\": {\n return {\n title: \"User Verification is in Process\",\n component: (\n <KycModalWrapper\n icon={<ResubmissionKycIcon />}\n text=\"Your verification with was rejected. Please Contact Support to resolve the issue.\"\n buttonText=\"Contact Support\"\n onButtonClick={onClose}\n />\n ),\n };\n }\n case \"submitted\": {\n return {\n title: \"User Verification Submitted\",\n component: (\n <KycModalWrapper\n icon={<SuccessKycSvg />}\n text=\"Your User Verification has been successfully submitted. You can close this window and return to the dashboard. Once the User Verification is approved, you'll be able to start using your card.\"\n buttonText=\"Dashboard\"\n onButtonClick={onClose}\n />\n ),\n };\n }\n\n case \"approved\": {\n // For approved status, we just return null as this SDK doesn't handle card activation\n return {\n title: \"\",\n component: <></>,\n };\n }\n\n default:\n return {\n title: \"Apply for Card\",\n component: <></>,\n };\n }\n }, [status, acceptedRainTerms]);\n\n const { title, component } = getModalView;\n\n // Don't render modal if status is approved or no component\n if (status === \"approved\" || !component) {\n return null;\n }\n\n return (\n <Modal title={title} onClose={onClose || (() => {})} padding=\"p-1\">\n {component}\n </Modal>\n );\n}\n","import { motion, AnimatePresence } from \"framer-motion\";\nimport React, { ReactNode } from \"react\";\nimport { SDKWrapper } from \"./SDKWrapper\";\n\ninterface ModalProps {\n title: string;\n children: ReactNode;\n onClose: () => void;\n onOutsideClick?: () => void;\n overlayOpacity?: string;\n topDistance?: string;\n open?: boolean;\n padding?: string; // New prop for custom padding\n}\n\nexport function Modal({\n title,\n children,\n onClose,\n onOutsideClick,\n overlayOpacity = \"bg-black bg-opacity-100\",\n topDistance = \"pt-[100px]\",\n open = true,\n padding, // Accept the padding prop\n}: ModalProps) {\n // Default padding classes if not provided\n const childrenPadding = padding ?? \"px-4 sm:px-6 py-4 sm:py-6\";\n return (\n <SDKWrapper>\n <AnimatePresence>\n {open && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.2 }}\n className={`fixed inset-0 z-50 flex items-start justify-center ${topDistance} ${overlayOpacity} px-4 sm:px-0`}\n onClick={onOutsideClick}\n aria-modal=\"true\"\n role=\"dialog\"\n >\n <motion.div\n initial={{ scale: 0.95, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.95, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"relative max-h-[90vh] w-full max-w-lg p-0 flex flex-col\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex items-center justify-between pb-[14px]\">\n <h2 className=\"text-base sm:text-lg font-semibold text-[#E6E8ED]\">\n {title}\n </h2>\n <button\n onClick={onClose}\n aria-label=\"Close modal\"\n className=\"text-gray-400 hover:text-white text-xl sm:text-2xl px-2\"\n >\n ×\n </button>\n </div>\n <div\n style={{ maxHeight: \"70vh\" }}\n className=\"border border-[#292930] rounded-[9px] bg-[#0D0D10] shadow-lg flex flex-col 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\"\n >\n <div className={`${childrenPadding} relative`}>{children}</div>\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n </SDKWrapper>\n );\n}\n","import React, { ReactNode } from \"react\";\n\ninterface SDKWrapperProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * SDKWrapper component that wraps all SDK components with a root class\n * This ensures all Tailwind styles are scoped and don't affect the host application\n */\nexport function SDKWrapper({ children, className = \"\" }: SDKWrapperProps) {\n return <div className={`swype-sdk-root ${className}`}>{children}</div>;\n}\n","import { create } from \"zustand\";\nimport { CardStatus, UserCard } from \"../api/types\";\nimport { useSDKConfigStore } from \"./sdkConfigStore\";\nimport { getCardStatus } from \"../api/api\";\n\ninterface CardStatusState {\n status: CardStatus | null;\n userCard: UserCard | null;\n isUserCardDelegated: boolean;\n isLoading: boolean;\n error: string | null;\n // Internal methods for SDK use only\n _setCardStatus: (status: CardStatus, userCard: UserCard | null) => void;\n _setUserStatus: (status: CardStatus) => void;\n _setLoading: (loading: boolean) => void;\n _setError: (error: string | null) => void;\n _reset: () => void;\n _setUserCard: (userCard: UserCard | null) => void;\n _refreshCardStatus: () => Promise<void>;\n}\n\n// Helper function to check if card is delegated to the current EOA\nconst isCardDelegatedToCurrentEOA = (userCard: UserCard | null): boolean => {\n if (!userCard) return false;\n\n // Get current EOA from global config store\n const { jwt } = useSDKConfigStore.getState();\n if (!jwt?.eoa) return false;\n\n // Card is delegated to this EOA if creditProviderAddress matches the current EOA\n return (\n (userCard.solverCreditProviderAddress || \"\").toLowerCase() ===\n jwt.eoa.toLowerCase()\n );\n};\n\nexport const useCardStatusStore = create<CardStatusState>((set) => ({\n status: null,\n userCard: null,\n isLoading: false,\n error: null,\n isUserCardDelegated: false,\n\n // Internal methods for SDK use only (prefixed with _)\n _setCardStatus: (status, userCard) => {\n const isCardDelegated = isCardDelegatedToCurrentEOA(userCard);\n set({\n status,\n userCard,\n isUserCardDelegated: isCardDelegated,\n });\n },\n\n _setLoading: (loading) => set({ isLoading: loading }),\n _setUserStatus: (status) => set({ status }),\n\n _setError: (error) => set({ error }),\n\n _reset: () =>\n set({\n status: null,\n userCard: null,\n isUserCardDelegated: false,\n isLoading: false,\n error: null,\n }),\n\n _setUserCard: (userCard) => {\n const isCardDelegated = isCardDelegatedToCurrentEOA(userCard);\n set({\n userCard,\n isUserCardDelegated: isCardDelegated,\n });\n },\n\n _refreshCardStatus: async () => {\n const { jwt } = useSDKConfigStore.getState();\n if (!jwt?.eoa) {\n console.error(\"[_refreshCardStatus]: No EOA address found\");\n set({ error: \"No EOA address found\" });\n return;\n }\n\n try {\n set({ error: null });\n const statusResponse = await getCardStatus();\n if (statusResponse && statusResponse.data) {\n set({\n status: statusResponse.data,\n error: null,\n });\n } else {\n set({\n error: \"Invalid response format\",\n isLoading: false,\n });\n }\n } catch (error: any) {\n console.error(\"[_refreshCardStatus]: Error fetching card status\", error);\n set({\n error: error?.message || \"Failed to fetch card status\",\n isLoading: false,\n });\n }\n },\n}));\n","import { create } from \"zustand\";\nimport { dataFetcher } from \"../core/dataFetcher\";\nimport { JWT } from \"../api/types\";\n\ninterface SDKConfigState {\n apiKey: string | null;\n baseUrl: string | null;\n apiPath: string | null;\n jwt: JWT | null;\n tenantId: string | null;\n isInitialized: boolean;\n\n setConfig: (config: {\n apiKey: string;\n baseUrl?: string;\n apiPath?: string;\n jwt?: JWT;\n tenantId?: string;\n }) => void;\n reset: () => void;\n clearJWT: () => void;\n setJWT: (jwt: JWT) => void;\n}\n\nexport const useSDKConfigStore = create<SDKConfigState>((set, get) => ({\n apiKey: null,\n baseUrl: null,\n apiPath: null,\n tenantId: null,\n isInitialized: false,\n jwt: null,\n\n setConfig: (config) => {\n // Reset previous state\n get().reset();\n\n // Set new config\n set({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl || \"https://dev.console.fi\",\n apiPath: config.apiPath || \"/v1/vendor\",\n tenantId: config.tenantId || null,\n jwt: config.jwt || null,\n isInitialized: true,\n });\n\n // Note: Data fetching will be triggered after authentication is complete\n },\n reset: () => {\n set({\n apiKey: null,\n baseUrl: null,\n apiPath: null,\n tenantId: null,\n isInitialized: false,\n jwt: null,\n });\n\n // Reset data stores\n dataFetcher.reset();\n },\n setJWT: (jwt: JWT) => {\n set({ jwt });\n },\n clearJWT: () => set({ jwt: null }),\n}));\n","import axios from \"axios\";\nimport {\n CardStatusResponse,\n NewCardStatusResponse,\n KYCStatus,\n RainStatus,\n SumSubTokenResponse,\n CreditDelegationRequest,\n CreditDelegationResponse,\n LinkCardRequest,\n UserCardsResponse,\n CreditInformationResponse,\n CreateCardResponse,\n TransactionsResponse,\n OnchainTransactionDetailsResponse,\n UpdateCardRequest,\n CardLimitFrequency,\n EulerTotalAssetResponse,\n UserParams,\n UserConfig,\n HookType,\n HookConfig,\n HookTransaction,\n} from \"./types\";\nimport { getApiClient } from \"./httpClient\";\nimport {\n formatAvailableLimit,\n generateUUID,\n getCardSolverConfig,\n mapEulerMetadataToSuppliedAssets,\n mapNewStatusToLegacyCardStatus,\n} from \"./utils\";\nimport {\n getProviderConfig,\n CreditProviderId,\n isCreditProviderOfType,\n HYPER_EVM_CHAIN_ID,\n BASE_CHAIN_ID,\n} from \"../config/creditRegistry\";\nimport {\n getMockCardStatus,\n getMockUserCards,\n getMockAaveCreditInformation,\n getMockEulerCreditInformation,\n getMockSumSubToken,\n getMockCreditDelegationTxData,\n getMockTransactions,\n transactionsMockData,\n getMockNewCardStatus,\n} from \"./mockData\";\nimport { useSDKConfigStore } from \"../stores/sdkConfigStore\";\n\n/**\n * Extracts a user-friendly error message from an Axios error object.\n *\n * @param error - The error object to extract the message from.\n * @param fallback - The message to return if no specific error is found. Defaults to `\"Unexpected error\"`.\n * @returns The extracted error message or the fallback string.\n */\nconst extractErrorMessage = (error: any, fallback = \"Unexpected error\") =>\n error?.response?.data?.data?.error ||\n error?.response?.data?.message ||\n error?.message ||\n fallback;\n\n/**\n * Fetches the current card status for a given EOA address.\n *\n * @returns The card status as a `CardStatusResponse`.\n * @throws Will throw an error if the request fails (except 404).\n */\nexport const getCardStatus = async (): Promise<CardStatusResponse> => {\n // const mockResponse = getMockNewCardStatus(\"rain_rejection_error\");\n // const legacyStatus = mapNewStatusToLegacyCardStatus(\n // mockResponse.data.kycStatus,\n // mockResponse.data.rainStatus,\n // mockResponse.data.errorDetails\n // );\n\n // return { data: legacyStatus };\n // Real API call (commented out - uncomment to use real API)\n try {\n const client = getApiClient();\n const response = await client.get<NewCardStatusResponse>(\n `/cards/users/status`\n );\n\n // Map new response format to legacy format for backward compatibility\n const { kycStatus, rainStatus, errorDetails } = response.data.data;\n const legacyStatus = mapNewStatusToLegacyCardStatus(\n kycStatus,\n rainStatus,\n errorDetails\n );\n\n return { data: legacyStatus };\n } catch (error) {\n if (axios.isAxiosError(error) && error.response?.status === 404) {\n return { data: \"userNotExisting\" };\n }\n throw error;\n }\n};\n\n/**\n * Retrieves all cards linked to a specific user.\n *\n * @returns A `UserCardsResponse` object with details of linked cards, or `{ data: null }` on failure.\n */\nexport const getUserCards = async (): Promise<UserCardsResponse> => {\n // Return mock data\n // return getMockUserCards();\n\n // Real API call (commented out - uncomment to use real API)\n try {\n const client = getApiClient();\n const response = await client.get<UserCardsResponse>(`/cards`);\n return response.data;\n } catch (err) {\n console.error(\"[getUserCards]\", err);\n return { data: null };\n }\n};\n\n/**\n * Fetches credit information for a given credit address and solver type.\n *\n * @param solverType - The type of solver (\"aave-v3\", \"euler\", \"hypurrFi\", or custom).\n * @returns A `CreditInformationResponse` containing available limit, delegation amount, and metadata.\n */\nexport const getCreditInformation = async (\n solverType: CreditProviderId,\n chainID: number,\n userConfig?: UserConfig[\"config\"]\n): Promise<CreditInformationResponse> => {\n // Return mock data based on credit type\n // if (solverType === \"aave-v3\" || solverType === \"hypurrFi\") {\n // return getMockAaveCreditInformation();\n // } else {\n // return getMockEulerCreditInformation();\n // }\n\n // Real API call (commented out - uncomment to use real API)\n try {\n const client = getApiClient();\n\n // Registry is single source of truth - no hardcoded conditions\n const config =\n userConfig ?? getProviderConfig(solverType as CreditProviderId) ?? {};\n\n const response = await client.post<CreditInformationResponse>(\n `/cards/users/credit`,\n {\n solverType,\n config,\n chainID,\n }\n );\n\n // Transform availableLimit if it exists\n if (\n response.data?.data &&\n typeof response.data.data.availableLimit === \"number\"\n ) {\n response.data.data.availableLimit = formatAvailableLimit(\n response.data.data.availableLimit,\n solverType,\n userConfig?.purchaseBaseTokenAddress || undefined\n );\n response.data.data.amountDelegated = formatAvailableLimit(\n response.data.data.amountDelegated,\n solverType,\n userConfig?.purchaseBaseTokenAddress || undefined\n );\n }\n\n // If Euler, map vaults to suppliedAssets for compatibility (without mutating original)\n if (solverType === \"euler\" && response.data?.data?.metadata) {\n response.data.data.metadata = mapEulerMetadataToSuppliedAssets(\n response.data.data.metadata\n );\n }\n\n return response.data;\n } catch (error) {\n console.error(\"[getCreditInformation]\", error);\n return { data: null };\n }\n};\n\n/**\n * Retrieves a SumSub KYC access token for the given user.\n *\n * @param userId - The user ID in the system.\n * @param ipAddress - Optional IP address of the user.\n * @returns The SumSub token data, or `undefined` if the request fails.\n */\nexport const getSumSubAccessToken = async (\n userId: string,\n ipAddress?: string\n): Promise<SumSubTokenResponse[\"data\"] | undefined> => {\n // Return mock data\n // return getMockSumSubToken();\n\n // Real API call (commented out - uncomment to use real API)\n try {\n const client = getApiClient();\n const response = await client.post<SumSubTokenResponse>(\n \"/cards/sumsub/token\",\n { ownerAddress: userId, ipAddress }\n );\n return response.data.data;\n } catch (error) {\n console.error(\"[error at getSumSubAccessToken]\", error);\n return undefined;\n }\n};\n\n/**\n * Generates transaction data for delegating credit.\n *\n * @param requestData - The delegation request payload.\n * @param solverParams - Optional solver configuration parameters.\n * @returns An object containing the credit delegation transaction data.\n */\nexport const getCreditDelegationTxData = async (\n requestData: CreditDelegationRequest,\n solverParams?: {\n aTokenAddress: string;\n purchaseBaseTokenAddress: string;\n purchaseAssetAddress: string;\n purchaseAllocationPercent: number;\n purchasePoolAddress: string;\n }\n): Promise<{ data: CreditDelegationResponse }> => {\n // Return mock data\n // return getMockCreditDelegationTxData();\n\n // Real API call (commented out - uncomment to use real API\n const payload = {\n creditType: requestData.creditType,\n creditLimit: requestData.creditLimit,\n chainID: requestData.chainID,\n userParams: {\n ...requestData.userParams,\n ...(solverParams ?? {}),\n },\n };\n const client = getApiClient();\n const response = await client.post<{ data: CreditDelegationResponse }>(\n `/cards/users/credit/delegate`,\n payload\n );\n return response.data;\n};\n\n/**\n * Links an existing card to a specific credit account.\n *\n * @param requestData - The link request payload.\n * @returns The API response object.\n */\nexport const linkCardToCreditAccount = async (\n requestData: LinkCardRequest\n): Promise<any> => {\n const client = getApiClient();\n const response = await client.patch(\"/cards/\", requestData);\n return response.data;\n};\n\n/**\n * Updates a user's card by linking it to a credit provider using signed authorization.\n *\n * @param requestData - The update request containing credit provider details.\n * @returns An object indicating success or failure, with an optional error message.\n */\nexport const updateCard = async (\n requestData: UpdateCardRequest\n): Promise<{ success: boolean; message?: string }> => {\n try {\n const client = getApiClient();\n\n // Use provided config if available, otherwise fall back to default config\n const cardSolverConfig = isCreditProviderOfType(\n requestData.creditType,\n \"spend\"\n )\n ? { ...requestData.config }\n : getCardSolverConfig(requestData.creditType);\n\n const response = await client.patch(`/cards/creditprovider`, {\n creditProviderAddress: requestData.creditProviderAddress,\n chainID: requestData.chainId,\n solverType: requestData.creditType,\n cardID: requestData.cardID,\n cardSolverConfig,\n });\n\n if (response.status === 200) {\n return { success: true };\n }\n\n return {\n success: false,\n message: response.data?.message || \"Failed to update card\",\n };\n } catch (error: any) {\n console.error(\"[updateCard]\", error);\n return {\n success: false,\n message:\n error?.response?.data?.data?.error ||\n error?.message ||\n \"Failed to update card\",\n };\n }\n};\n\n/**\n * Creates a new virtual or physical card for a user.\n *\n * @param userEoa - The Ethereum address (EOA) of the user.\n * @param chainID - The blockchain network's chain ID.\n * @param solverType - The type of credit provider (solver).\n * @returns The API response containing card creation details.\n */\nexport const createCard = async (\n userEoa: string,\n chainID: number,\n solverType: CreditProviderId,\n cardSolverConfig: Record<string, string>\n): Promise<CreateCardResponse> => {\n const client = getApiClient();\n\n const response = await client.post<CreateCardResponse>(`/cards/create`, {\n creditProviderAddress: userEoa,\n chainID: chainID,\n solverType: solverType,\n cardSolverConfig,\n });\n\n return response.data;\n};\n\n/**\n * Initializes the configuration for a specific solver type for a user.\n *\n * @param chainID - The blockchain network's chain ID.\n * @param solverType - The type of solver to initialize.\n * @param config - A record of configuration key-value pairs.\n * @returns An object containing success status and message.\n */\nexport const initializeUserConfig = async (\n chainID: number,\n solverType: CreditProviderId,\n config: Record<string, string>\n): Promise<{\n success: boolean;\n message?: string;\n}> => {\n const client = getApiClient();\n\n try {\n const response = await client.post<CreateCardResponse>(`/cards/users`, {\n solverType,\n config,\n chainID,\n });\n\n if (response.status === 200) {\n return { success: true };\n }\n\n return {\n success: false,\n message: \"Configuration initialized successfully.\",\n };\n } catch (err: any) {\n return {\n success: false,\n message:\n err?.response?.data?.data?.error ||\n err?.message ||\n \"Failed to initialize configuration.\",\n };\n }\n};\n\n/**\n * Retrieves the configuration for a specific credit type.\n *\n * @returns A promise that resolves with the configuration object or null if an error occurs.\n */\nexport const getUserConfig = async (): Promise<UserConfig | null> => {\n try {\n const client = getApiClient();\n const response = await client.get<{ data: UserConfig }>(\n `/cards/users/preferences`\n );\n return response.data.data;\n } catch (error) {\n console.error(\"Failed to fetch user config:\", error);\n return null;\n }\n};\n\n/**\n * Freezes or unfreezes a user's card using a signed request.\n *\n * @param cardID - The UUID of the card.\n * @param freeze - Whether to freeze (`true`) or unfreeze (`false`) the card.\n * @returns An object indicating success or failure with an optional message.\n */\nexport const freezeCard = async (\n cardID: string,\n freeze: boolean\n): Promise<{ success: boolean; message?: string }> => {\n const client = getApiClient();\n\n try {\n const response = await client.patch(`/cards/status`, {\n cardID,\n freeze,\n });\n // if 200 then return success\n if (response.status === 200) {\n return { success: true };\n }\n // if not 200, return failure with message\n return {\n success: false,\n message: response.data?.message || \"Failed to freeze card\",\n };\n } catch (error: any) {\n console.error(\"[freezeCard]\", error);\n return {\n success: false,\n message:\n error?.response?.data?.data?.error ||\n error?.message ||\n \"Failed to freeze card\",\n };\n }\n};\n\n/**\n * Updates the PIN of a card by sending encrypted PIN data.\n *\n * @param iv - Base64-encoded initialization vector (IV) used for encryption.\n * @param data - Base64-encoded encrypted PIN.\n * @returns An object indicating success or failure with an optional message.\n */\nexport const updatePin = async (\n iv: string,\n data: string,\n sessionID: string,\n cardID: string\n): Promise<{ success: boolean; message?: string }> => {\n const client = getApiClient();\n\n try {\n const response = await client.patch<{ success: boolean; message?: string }>(\n `/cards/pin`,\n { pin: { encryptedPin: { data, iv } }, cardID },\n {\n headers: {\n sessionID,\n },\n }\n );\n\n if (response.status === 200) {\n return { success: true };\n }\n\n return {\n success: false,\n message: response.data?.message || \"Failed to update PIN\",\n };\n } catch (error: any) {\n console.error(\"[updatePin]\", error);\n return {\n success: false,\n message:\n error?.response?.data?.data?.error ||\n error?.message ||\n \"Failed to update PIN\",\n };\n }\n};\n\n/**\n * Retrieves encrypted card details including PAN and CVC.\n *\n * @param cardID - The UUID of the card.\n * @returns An object containing encrypted PAN, CVC, and card expiration date.\n * @throws If the request fails or returns invalid data.\n */\nexport const getCardSecrets = async (\n cardID: string,\n sessionID: string\n): Promise<{\n encryptedPan: { iv: string; data: string };\n encryptedCvc: { iv: string; data: string };\n ExpirationMonth: string;\n ExpirationYear: string;\n}> => {\n const client = getApiClient();\n\n try {\n const response = await client.get(`/cards/secrets/${cardID}`, {\n headers: {\n sessionID,\n },\n });\n\n // If successful\n if (response.status === 200 && response.data?.data) {\n return response.data.data;\n }\n\n // Fallback if 200 but no data\n throw new Error(\"Unexpected response format while fetching card secrets.\");\n } catch (error) {\n console.error(\"[getCardSecrets]\", error);\n throw new Error(extractErrorMessage(error, \"Failed to fetch card secrets\"));\n }\n};\n\n/**\n * Fetches the spending limit of a specific card.\n *\n * @param cardID - The UUID of the card.\n * @returns An object containing the amount (in cents) and frequency of the limit.\n * @throws If the request fails or returns invalid data.\n */\nexport const getCardLimit = async (\n cardID: string\n): Promise<{\n amount: number; // in cents\n frequency: CardLimitFrequency;\n}> => {\n const client = getApiClient();\n\n try {\n const response = await client.get(`/cards/limit/${cardID}`);\n\n // If successful\n if (response.status === 200 && response.data?.data) {\n return response.data.data;\n }\n\n // Fallback if 200 but no data\n throw new Error(\"Unexpected response format while fetching card limits.\");\n } catch (error) {\n console.error(\"[getCardLimit]\", error);\n throw new Error(extractErrorMessage(error, \"Failed to fetch card limits\"));\n }\n};\n\n/**\n * Updates the spending limit of a card.\n *\n * @param limit - The new spending limit in cents.\n * @returns An object indicating success or failure with an optional message.\n */\nexport const updateLimit = async (\n limit: number,\n cardID: string\n): Promise<{ success: boolean; message?: string }> => {\n const client = getApiClient();\n\n try {\n const response = await client.patch<{ success: boolean; message?: string }>(\n `/cards/limits`,\n { limit, frequency: \"per24HourPeriod\", cardID }\n );\n\n if (response.status === 200) {\n return { success: true };\n }\n\n return {\n success: false,\n message: response.data?.message || \"Failed to update limit\",\n };\n } catch (error) {\n console.error(\"[updateLimit]\", error);\n return {\n success: false,\n message: extractErrorMessage(error, \"Failed to update limit\"),\n };\n }\n};\n\n/**\n * Fetches the public encryption key for card-related encryption operations.\n *\n * @returns An object containing the PEM-formatted public key.\n */\nexport const getCardPublicKey = async (): Promise<{ data: string }> => {\n const client = getApiClient();\n const response = await client.get<{ data: string }>(\"/cards/pubkey\");\n return response.data;\n};\n\n/**\n * Retrieves transactions for a user's card.\n *\n * @param cardID - The UUID of the card.\n * @param limit - Optional maximum number of transactions to fetch.\n * @param cursor - Optional pagination cursor.\n * @returns A `TransactionsResponse` containing the transaction list and pagination data.\n */\nexport const getTransactions = async (\n cardID: string,\n limit?: number,\n cursor?: string\n): Promise<TransactionsResponse> => {\n // Return mock data\n\n // await waitFor(2000);\n // return transactionsMockData;\n // Real API call\n try {\n const client = getApiClient();\n const response = await client.get<import(\"./types\").TransactionsResponse>(\n `cards/transactions/${cardID}`,\n {\n params: {\n limit,\n cursor,\n },\n }\n );\n return response.data;\n } catch (error) {\n console.error(\"[getTransactions]\", error);\n // Return a default structure on error to prevent crashes\n return {\n data: {\n transactions: [],\n pagination: {\n limit: limit || 50,\n has_next: false,\n },\n },\n };\n }\n};\n\n/**\n * Fetches on-chain transaction details for a given card transaction.\n *\n * @param transactionID - The transaction identifier.\n * @returns An `OnchainTransactionDetailsResponse` object.\n */\nexport const getOnchainTransactionDetails = async (\n transactionID: string\n): Promise<OnchainTransactionDetailsResponse> => {\n // Return mock data\n // return getMockOnchainTransactionDetails();\n\n // Real API call (commented out - uncomment to use real API)\n try {\n const client = getApiClient();\n const response = await client.get<OnchainTransactionDetailsResponse>(\n `cards/transactions/${transactionID}/onchain`\n );\n return response.data;\n } catch (error) {\n console.error(\"[getOnchainTransactionDetails]\", error);\n return { data: [] };\n }\n};\n\n/**\n * Checks whether the current user is located in the United States.\n *\n * @returns An object containing a boolean `data` field.\n */\nexport const getUserIsUs = async (): Promise<{ data: boolean }> => {\n try {\n const client = getApiClient();\n const response = await client.get<{ data: boolean }>(`cards/user/is_us`);\n return response.data;\n } catch (error) {\n console.error(\"[getUserIsUs]\", error);\n return { data: false };\n }\n};\n\n/**\n * Retrieves a preview of Euler credit and collateral amounts for specific vaults.\n *\n * @param vaults - List of vault addresses to preview.\n * @param creditType - The type of credit (defaults to `\"euler\"`).\n * @returns An object containing credit and collateral amounts as strings.\n */\nexport const getEulerCardPreview = async (\n vaults: string[]\n): Promise<{ data: { credit: string; collateral: string } }> => {\n try {\n const client = getApiClient();\n const creditType: CreditProviderId = \"euler\";\n const eulerConfig = getProviderConfig(creditType);\n const response = await client.post<{\n data: { credit: string; collateral: string };\n }>(`/cards/users/credit/${creditType}/preview`, {\n vaults,\n ...eulerConfig,\n chainID: BASE_CHAIN_ID, // Euler is only on Base\n });\n return response.data;\n } catch (error) {\n console.error(\"[getEulerCardPreview]\", error);\n return { data: { credit: \"0\", collateral: \"0\" } };\n }\n};\n\n/**\n * Fetches the Euler health factor for the user.\n *\n * @returns An object containing the health factor as a string.\n */\nexport const getEulerHealthFactor = async (): Promise<{\n data: { healthFactor: string };\n}> => {\n try {\n const client = getApiClient();\n const creditType: CreditProviderId = \"euler\";\n const eulerConfig = getProviderConfig(creditType);\n\n if (!eulerConfig?.borrowVaultAddress) {\n return { data: { healthFactor: \"0\" } };\n }\n\n const response = await client.get<{\n data: { healthFactor: string };\n }>(\n `/cards/users/${creditType}/hf/${eulerConfig.borrowVaultAddress}/${BASE_CHAIN_ID}`\n );\n\n return response.data;\n } catch (error) {\n console.error(\"[getEulerHealthFactor]\", error);\n return { data: { healthFactor: \"0\" } };\n }\n};\n\n/**\n * Fetches the pending transaction balance for a specific card.\n *\n * @param cardID - The UUID of the card.\n * @returns An object containing the status and USD balance of pending transactions.\n */\nexport const getPendingTransactionBalance = async (\n cardID: string\n): Promise<{\n data: {\n status: string;\n balances: { USD: number };\n };\n}> => {\n try {\n const client = getApiClient();\n const response = await client.get<{\n data: {\n status: string;\n balances: { USD: number };\n };\n }>(`/cards/transactions/balances/pending/${cardID}`);\n return response.data;\n } catch (error) {\n console.error(\"[getPendingTransactionBalances]\", error);\n return { data: { status: \"error\", balances: { USD: 0 } } };\n }\n};\n\n/**\n * Retrieves the total Euler assets for a specific blockchain network.\n *\n * @returns An `EulerTotalAssetResponse` containing total assets in USD.\n */\nexport const getEulerTotalAsset =\n async (): Promise<EulerTotalAssetResponse> => {\n try {\n const client = getApiClient();\n const response = await client.get<EulerTotalAssetResponse>(\n `/cards/users/euler/assets/${BASE_CHAIN_ID}`\n );\n return response.data;\n } catch (error) {\n console.error(\"[getEulerTotalAsset]\", error);\n return { data: { totalAssetsUSD: 0 } };\n }\n };\n\n/**\n * Retrieves the user's hook transaction history.\n *\n * @param limit - The maximum number of transactions to retrieve.\n * @param offset - The number of transactions to skip.\n * @returns A promise that resolves to an array of hook transactions.\n */\nexport const getHookTransactions = async (\n limit?: number,\n offset?: number\n): Promise<HookTransaction[]> => {\n try {\n const client = getApiClient();\n const response = await client.get<{ data: HookTransaction[] }>(\n \"/cards/transactions/hook\",\n {\n params: { limit, offset },\n }\n );\n return response.data.data;\n } catch (error) {\n console.error(\"[getHookTransactions]\", error);\n // Safe fallback\n return [];\n }\n};\n\n/**\n * Updates a card hook with the provided configuration.\n *\n * @param cardID - The UUID of the card.\n * @param chainID - The chain ID associated with the hook.\n * @param hookType - The type of hook to update (e.g., \"yield_buy\").\n * @param config - The configuration object for the hook.\n * @returns A promise that resolves to an object indicating success or failure,\n * along with an optional message.\n */\nexport const updateCardHook = async (\n cardID: string,\n chainID: number,\n hookType: HookType,\n config: HookConfig<HookType>\n): Promise<{ success: boolean; message?: string }> => {\n const client = getApiClient();\n\n try {\n const response = await client.patch<{ success: boolean; message?: string }>(\n `/cards/hook`,\n {\n cardID,\n config,\n hookType,\n chainID,\n }\n );\n\n if (response.status === 200) {\n return { success: true };\n }\n\n return {\n success: false,\n message: response.data?.message || \"Failed to update card hook\",\n };\n } catch (error: any) {\n console.error(\"[updateCardHook] Error:\", error);\n return {\n success: false,\n message:\n error?.response?.data?.data?.error ||\n error?.message ||\n \"Failed to update card hook\",\n };\n }\n};\n\n/**\n * Deletes a card hook with the provided configuration.\n *\n * @param cardID - The UUID of the card.\n * @returns A promise that resolves to an object indicating success or failure,\n * along with an optional message.\n */\nexport const deleteCardHook = async (\n cardID: string\n): Promise<{\n success: boolean;\n message?: string;\n}> => {\n const client = getApiClient();\n\n try {\n // pass params to delete\n const response = await client.delete(`/cards/hook/${cardID}`);\n\n if (response.status === 200) {\n return { success: true };\n }\n\n return {\n success: false,\n message: response.data?.message || \"Failed to delete card hook\",\n };\n } catch (error: any) {\n console.error(\"[deleteCardHook] Error:\", error);\n return {\n success: false,\n message:\n error?.response?.data?.data?.error ||\n error?.response?.data?.message ||\n error?.message ||\n \"Failed to delete card hook\",\n };\n }\n};\n","import axios, {\n AxiosError,\n AxiosInstance,\n InternalAxiosRequestConfig,\n} from \"axios\";\nimport { useSDKConfigStore } from \"../stores/sdkConfigStore\";\nimport { ensureValidAuth, refreshAuth } from \"./auth\";\nimport { JWT } from \"./types\";\n\n/**\n * Shared promise to prevent concurrent refresh calls.\n * This ensures that if multiple requests encounter\n * an expired token at the same time, only one refresh\n * request is sent to the server.\n */\nlet refreshPromise: Promise<JWT> | null = null;\n\n/**\n * Refresh the access token if not already refreshing.\n *\n * This function retrieves the necessary configuration and refresh token from the\n * SDK configuration store, then calls `refreshAuth`. It uses a shared promise\n * to ensure that only one token refresh operation is active at any given time,\n * preventing multiple concurrent refresh requests.\n *\n * @returns A Promise that resolves to a JWT object once the refresh is complete.\n * @throws {Error} If the SDK is not initialized, the refresh token is missing,\n * or the token refresh operation fails.\n */\nasync function refreshTokenIfNeeded(): Promise<JWT> {\n if (!refreshPromise) {\n // Get the current state directly inside the function scope to ensure it's up-to-date\n const { jwt, tenantId, apiKey, baseUrl, apiPath } =\n useSDKConfigStore.getState();\n\n if (!jwt?.refreshToken) {\n throw new Error(\"No refresh token available in SDK configuration store.\");\n }\n if (!jwt?.eoa || !tenantId || !apiKey || !baseUrl || !apiPath) {\n throw new Error(\"Missing required SDK configuration for token refresh.\");\n }\n\n refreshPromise = refreshAuth(jwt.refreshToken, {\n eoaAddress: jwt.eoa,\n tenantId,\n apiKey,\n baseUrl,\n apiPath,\n }).finally(() => {\n // Clear the promise once it settles, regardless of success or failure\n refreshPromise = null;\n });\n }\n return refreshPromise;\n}\n/**\n * Apply authentication + partner headers to request config.\n */\nfunction applyAuthHeaders(config: InternalAxiosRequestConfig) {\n const { jwt, apiKey, tenantId } = useSDKConfigStore.getState();\n\n if (!apiKey) throw new Error(\"Missing API key.\");\n if (!jwt?.accessToken) throw new Error(\"Missing access token.\");\n\n config.headers.set(\"Authorization\", `Bearer ${jwt.accessToken}`);\n config.headers.set(\"X-Api-Key\", apiKey);\n config.headers.set(\"Content-Type\", \"application/json\");\n config.headers.set(\"ngrok-skip-browser-warning\", \"true\");\n\n if (tenantId) {\n config.headers.set(\"X-Partner-ID\", tenantId);\n }\n\n return config;\n}\n\n/**\n * Get a preconfigured Axios API client for SwypeSDK.\n *\n * ### Features:\n * 1. **Automatic Token Validation**\n * Runs `ensureValidAuth()` before each request to verify token freshness.\n *\n * 2. **Automatic Token Refresh**\n * If token is expired or missing, it calls `refreshAuth()` and updates headers automatically.\n *\n * 3. **Concurrency-safe Refresh**\n * Uses a `refreshPromise` guard so only one refresh request is sent at a time.\n *\n * 4. **Fallback Retry on Auth Error**\n * If a request fails with 401/403, it attempts one refresh + retry before failing.\n *\n * 5. **Centralized Header Management**\n * All header setting happens in `applyAuthHeaders()` — no duplication.\n *\n * @throws If SDK is not initialized or no valid token exists after refresh.\n *\n * @example\n * ```ts\n * const client = getApiClient();\n * const res = await client.get(\"/users/me\");\n * console.log(res.data);\n * ```\n */\nexport function getApiClient(): AxiosInstance {\n const { isInitialized, apiKey, jwt, baseUrl, apiPath, clearJWT } =\n useSDKConfigStore.getState();\n\n // Fail early if SDK is not ready\n if (!isInitialized || !apiKey) {\n throw new Error(\"SwypeSDK not initialized. Call initializeSwypeSDK first.\");\n }\n if (!jwt?.accessToken) {\n throw new Error(\"SwypeSDK auth not ready. Missing access token.\");\n }\n\n const client = axios.create({\n baseURL: `${baseUrl}${apiPath}`,\n });\n\n /**\n * Request Interceptor\n * -------------------\n * Runs before each request.\n * 1. Checks if token is valid (`ensureValidAuth()`).\n * 2. If not valid, refreshes (`refreshTokenIfNeeded()`).\n * 3. Applies updated headers from store.\n */\n client.interceptors.request.use(\n async (config) => {\n try {\n await ensureValidAuth();\n } catch {\n // Token expired → attempt refresh\n try {\n await refreshTokenIfNeeded();\n } catch (err) {\n clearJWT();\n throw err;\n }\n }\n return applyAuthHeaders(config);\n },\n (error) => Promise.reject(error)\n );\n\n /**\n * Response Interceptor\n * --------------------\n * Handles API errors globally.\n * 1. If a 401/403 occurs, it attempts one token refresh + retry.\n * 2. If refresh fails, clears auth state to force re-login.\n */\n client.interceptors.response.use(\n (response) => response,\n async (error: AxiosError) => {\n const { response, config } = error;\n\n if (\n response &&\n config &&\n (response.status === 401 || response.status === 403)\n ) {\n try {\n await refreshTokenIfNeeded();\n return client(applyAuthHeaders(config));\n } catch (refreshError) {\n clearJWT();\n return Promise.reject(refreshError);\n }\n }\n\n return Promise.reject(error);\n }\n );\n\n return client;\n}\n","import axios from \"axios\";\n\nimport { useSDKConfigStore } from \"../stores/sdkConfigStore\";\nimport {\n JWT,\n PasskeyDiscoverableLoginBeginResponse,\n PasskeyDiscoverableLoginFinishOptions,\n PasskeyLoginBeginResponse,\n PasskeyLoginFinishOptions,\n PasskeyRegistrationBeginResponse,\n PasskeyRegistrationFinishOptions,\n WebAuthnCredential,\n} from \"./types\";\nimport { getApiClient } from \"./httpClient\";\n\n/* ------------------ Constants ------------------ */\n/** EIP-712 domain version for authentication. */\nconst AUTH_VERSION = \"1\";\n\n/* ------------------ Helpers ------------------ */\n\n/**\n * Creates an EIP-712 typed data object for login authentication.\n *\n * @param timestamp - The Unix timestamp in seconds when the login request is created.\n * @param tenantID - The tenant ID string.\n * @returns EIP-712 structured typed data for signing.\n */\nfunction createLoginTypedData(timestamp: number, tenantID: string) {\n return {\n types: {\n EIP712Domain: [\n { name: \"name\", type: \"string\" },\n { name: \"version\", type: \"string\" },\n ],\n Login: [\n { name: \"timestamp\", type: \"uint256\" },\n { name: \"tenantID\", type: \"string\" },\n ],\n },\n primaryType: \"Login\",\n domain: {\n name: \"BrahmaCards\",\n version: AUTH_VERSION,\n },\n message: {\n timestamp,\n tenantID,\n },\n };\n}\n\n/**\n * Creates a preconfigured Axios instance for SwypeSDK authentication requests.\n *\n * @param baseUrl - The API base URL.\n * @param apiPath - The API path prefix.\n * @param apiKey - The API key for authentication.\n * @param tenantId - The tenant ID for authentication.\n * @returns A configured Axios instance.\n */\nfunction createAuthClient(\n baseUrl: string,\n apiPath: string,\n apiKey: string,\n tenantId: string\n) {\n return axios.create({\n baseURL: `${baseUrl}${apiPath}/cards/auth`,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Api-Key\": apiKey,\n \"ngrok-skip-browser-warning\": \"true\",\n \"X-Partner-ID\": tenantId,\n },\n });\n}\n\n/**\n * Ensures the SDK is initialized and that specific required fields exist in the store.\n *\n * @param requiredFields - List of store keys that must be present.\n * @throws If the SDK is not initialized or any required field is missing.\n * @returns The current SDK config store state.\n */\nfunction ensureSDKInitialized<\n T extends keyof ReturnType<typeof useSDKConfigStore.getState>\n>(requiredFields: T[] = []) {\n const state = useSDKConfigStore.getState();\n\n if (!state.isInitialized) {\n throw new Error(\"SwypeSDK not initialized. Call initializeSwypeSDK first.\");\n }\n\n for (const field of requiredFields) {\n if (!state[field]) {\n throw new Error(`Missing required SDK config field: ${field}`);\n }\n }\n\n return state;\n}\n\n/**\n * Sends an authentication-related request to the Swype API and updates the SDK store with the new tokens.\n *\n * @param path - The relative API endpoint path (e.g., `/sig/login`, `/token/refresh`).\n * @param payload - The JSON payload to send in the request.\n * @throws If authentication fails or the response is missing the required fields.\n * @returns The new `AuthState` returned by the API.\n */\nasync fun