UNPKG

@funkit/connect

Version:

Funkit Connect SDK elevates DeFi apps via web2 sign-ins and one-click checkouts.

461 lines (460 loc) 22.3 kB
import type { FunAddress } from '@funkit/api-base'; import type { ApiCheckoutClientMetadata, ApiFunkitCheckoutActionParams, ApiFunkitCheckoutConfig } from '@funkit/utils'; import type { ReactNode } from 'react'; import type { Address } from 'viem'; import type { PaymentMethodInfo } from '../../domains/paymentMethods'; import type { CheckoutBlockedReason } from '../../modals/CheckoutModal/CheckoutBlockedReason'; import type { WithdrawalClient, WithdrawalTransaction } from '../../wallets/Wallet'; import type { FunkitCheckoutQuoteResult } from '../FunkitHistoryContext'; export type FunkitCheckoutActionParams = ApiFunkitCheckoutActionParams; import type { DynamicTargetAssetCandidate, TokenInfo } from '@funkit/connect-core'; export type { DynamicTargetAssetCandidate, TokenInfo, } from '@funkit/connect-core'; /** * Argument passed to a dynamic {@link FunkitCheckoutConfig.modalTitle} resolver. * Extends {@link TokenInfo} with the active dynamic routing id so titles can be * keyed by route/action — not just token address — which matters when two * routes share a target token (e.g. Lighter USDC perps vs spot both deposit * mainnet USDC). */ export interface ModalTitleContext extends TokenInfo { dynamicRoutingId?: string; } /** * A token to offer adding to the user's wallet (EIP-747 `wallet_watchAsset`) on * the post-checkout success screen. * * Set this when the token the user ends up holding differs from the checkout's * destination token — e.g. a vault receipt token (Aave's aUSDT) minted by a * post-swap supply action, where the checkout's `toTokenAddress` is the * intermediate underlying (USDT), not the token the wallet should track. * * Purely a post-checkout UX affordance: nothing in quoting, routing, or amount * display reads this field, so setting it cannot perturb a checkout's quote. */ export interface AddToWalletToken extends TokenInfo { /** Symbol shown in the wallet prompt (e.g. "aUSDT"). */ tokenSymbol: string; /** Token decimals. If omitted, the button reads `decimals()` on-chain. */ decimals?: number; /** Optional token icon, passed to the wallet as `image`. */ iconSrc?: string; } export declare enum FunCheckoutStartingStep { SOURCE_CHANGE = "source_change", CONFIRMATION = "confirmation", SELECT_ASSET = "select_asset", /** * Open directly on the Polymarket perps ⇄ predictions transfer step. * Requires {@link FunkitCheckoutConfig.polymarketPerpsTransfer} handles * (set by `createPolymarketDepositConfig`) — without them the step renders * but cannot transfer. */ PERPS_TRANSFER = "perps_transfer" } /** Token a Polymarket account can be topped up with. */ export type PolymarketTopupToken = 'pUSD'; /** * A Polymarket account in the transfer step. Both accounts share the same shape * so each direction is symmetrical and integrator-owned. */ export interface PolymarketTransferAccount { /** Account address (the transfer target when funds move *into* it). */ address: Address; /** Minimum amount (USD) to top this account up with `token` (0 = none). */ getMinTopupAmount: (token: PolymarketTopupToken) => number; /** * Transfers funds *from* this account to `toAddress`. Implemented by the * integrator (keeps their perps API auth in-house); the SDK invokes the * source account's transfer when the user confirms. `amountUsd` is the exact * string the user entered (validated numeric, ≤6 decimals) — never a parsed * number — so no float precision is lost before the integrator scales it. */ transfer: (params: { toAddress: Address; amountUsd: string; }) => Promise<void>; /** * Fetches this account's live balance (USD). The transfer step polls this so * the displayed balances reconcile with the real on-chain / perps-API state * after a transfer (optimistic updates alone go stale). Integrator-owned. */ getBalanceUsd: () => Promise<number>; } /** The two accounts the perps transfer step moves funds between. */ export interface PolymarketPerpsTransferHandles { predictions: PolymarketTransferAccount; perps: PolymarketTransferAccount; } export interface FunkitCheckoutConfig extends Omit<ApiFunkitCheckoutConfig, 'generateActionsParams' | 'customRecipient' | 'modalTitle' | 'iconSrc' | 'modalTitleMeta'> { /** List of contract action params **/ generateActionsParams?: (targetAssetAmount: number, config?: { targetAsset: Address; targetChain: string; }) => Promise<FunkitCheckoutActionParams[]>; /** Custom recipient address of the checkout. If specified, a different checkout flow will be executed. It is not recommended to set this unless the Fun.xyz team advises it. **/ customRecipient?: FunAddress; /** **************************************** * Checkout ModalUI-related configurations * *******************************************/ /** Title to show in the checkout modal. Defaults to "Checkout". * The resolver receives the target token plus the active `dynamicRoutingId`, * so titles can disambiguate routes that share a target token. **/ modalTitle?: string | ((asset: ModalTitleContext) => string); /** Additional information to show in the title of the checkout modal. Blank by default **/ modalTitleMeta?: string; /** * Integrator-provided, display-only yield metadata for the destination * (e.g. an Aave/Felix/Benqi supply). The SDK never computes these — the * integrator resolves them from their own data source and passes a snapshot * at checkout-init time. When present, the confirmation screen renders it as * Supply APY / Collateralization rows (`DestinationYieldRows`). Omitted * segments are simply not rendered. */ destinationYieldInfo?: { /** Supply APY as a display string without the % sign (e.g. "2.79"). */ supplyApy?: string; /** Whether the supply is/will be enabled as collateral for the user. */ collateralizationEnabled?: boolean; }; /** * Integrator-provided health-factor resolver for a lending supply (e.g. Aave). * The SDK never computes health factor — the integrator owns the math (run * against live position data with the protocol's own library, e.g. * `@aave/math-utils`) so it matches the protocol app exactly. The SDK calls * this at the confirmation step with the live supply amount (the target * asset, human units) and renders the result as a Health factor (before → * after) row. Return `null` when the position data isn't ready, or for an * infinite/no-debt health factor return `"-1"` (rendered as ∞). */ resolveHealthFactor?: (targetAssetHumanAmount: string) => { before: string | null; after: string | null; } | null; /** Icon to show in the checkout modal (50px x 50px). If not specified, no icon will be shown. **/ iconSrc?: null | string; /** Title of the item being checked out. e.g. Staked Ether, XYZ Option, Solar Bond ABC **/ checkoutItemTitle: string; /** amount of source token. it is not baseUnit **/ withdrawalSourceTokenBalance?: () => string | number; /** @deprecated isDefiMode has been removed. This field is ignored. */ isDefiMode?: boolean; /** The step at which the checkout process should start */ startingStep?: FunCheckoutStartingStep; /** Symbol of the source token. Required if startingStep is set to FunCheckoutStartingStep.CONFIRMATION. */ sourceTokenSymbol?: string; /** ID of the source chain. Required if startingStep is set to FunCheckoutStartingStep.CONFIRMATION. */ sourceChain?: string; /** Address of the source token on the source chain. Required if startingStep is set to FunCheckoutStartingStep.CONFIRMATION. */ sourceTokenAddress?: Address; /** Address list of the tokens that cannot be used as source. e.g. tokens for staking vault */ disabledSourceTokens?: TokenInfo[]; /** ***************************** * Miscellaneous configurations * ********************************/ /** a list of candidate assets to be chosen dynamically */ dynamicTargetAssetCandidates?: DynamicTargetAssetCandidate[]; /** * Minimal USD needed to deposit. Receives the active `dynamicRoutingId` (like * {@link modalTitle}) so the minimum can vary by route when two routes share a * target token (e.g. Polymarket predictions vs perps). */ getMinDepositUSD?: (asset: ModalTitleContext) => number; /** For Checkout with Custom Action, QRCode needs special action type * to specify the onchain action to execute after deposit */ qrcodeActionType?: string; /** * Polymarket-specific handles for the perps ⇄ predictions transfer step. Set * by `createPolymarketDepositConfig` and read by the perps transfer step. * Lets Polymarket keep their perps API auth in-house (they implement the * withdrawal) instead of handing the SDK an API key. */ polymarketPerpsTransfer?: PolymarketPerpsTransferHandles; /** * For dynamic routing, the id of the dynamic route to use */ dynamicRoutingId?: string; /** * Force a specific bridge provider for the checkout quote. * Set via dynamic routing rules. */ bridgeOverride?: 'across' | 'relay'; /** * Token to offer adding to the user's wallet on the post-checkout success * screen via EIP-747 `wallet_watchAsset`. Set when the held token differs * from the checkout's destination token (e.g. an Aave aUSDT receipt). See * {@link AddToWalletToken}. */ addToWalletToken?: AddToWalletToken; } export interface WithdrawalConfigBase { /** Title to show in the checkout modal. Defaults to "Checkout" **/ modalTitle: string; /** Additional information to show in the title of the checkout modal. Blank by default **/ modalTitleMeta?: string; /** Icon to show in the checkout modal (50px x 50px). If not specified, no icon will be shown. **/ iconSrc?: null | string; sourceTokenSymbol: string; /** source chain id to withdraw **/ sourceChainId: string; /** source token id to withdraw **/ sourceTokenAddress: Address; /** Disable connected option for email login user */ disableConnectedWallet?: boolean; /** Default token to select in the receive token dropdown. Falls back to sourceTokenSymbol if not set. */ defaultReceiveToken?: string; /** minimal USD needed to withdraw */ getMinWithdrawalUSD?: (asset: TokenInfo) => number; /** * Returns the minimum withdrawal amount in token units for the given symbol. * Used when the source token can vary (e.g. Lighter Secure withdrawal). */ getMinWithdrawalAmount?: (symbol: string) => number; /** * Maximum USD value allowed for a single withdrawal. Amounts above this block * the CTA (which switches to "Max amount exceeded"). Omit for no upper bound. * Mirrors {@link getMinWithdrawalUSD}. */ getMaxWithdrawalUSD?: (asset: TokenInfo) => number; /** * Returns the current withdrawable balance for the source token. * Called on every render — use a ref-backed stable callback so the value * stays live without triggering unnecessary re-renders. */ withdrawalSourceTokenBalance?: () => string | number; /** * Optional async hook fired when the user commits to the withdrawal — after * the withdraw CTA, but before any nonce fetch, wallet signature, or tx * submission. Awaited, so async work completes before signing begins. * * Throw to abort: the error message is surfaced to the user verbatim and no * nonce is consumed. Runs on every withdrawal method, before * {@link WalletWithdrawalConfig.preWithdrawalAction}. */ onBeforeSign?: () => Promise<void> | void; } export interface WalletWithdrawalConfig extends WithdrawalConfigBase { /** Wallet instance used to execute the quote */ wallet: WithdrawalClient; /** * Optional async hook that runs **before** the SDK executes the wallet-based * withdrawal quote. Return any transactions that must execute as part of the * withdrawal flow *prior* to the SDK's withdrawal transaction (e.g. an * `approve` and a custom-collateral `unwrap` that produces the source token). * * The SDK atomically prepends the returned array to the withdrawal txn(s) * via the integrator's `wallet.sendTransactions` primitive, so the * approve/unwrap and the withdrawal land in a single user signature. * * Return `[]` if no extra transactions are required — the hook is then * purely a side-effect / preflight step. * * Throw to abort the withdrawal — the thrown error message is surfaced to * the user in the standard error UI. * * Mutually exclusive with {@link CustomWithdrawalConfig['withdrawCallback']} * by virtue of living on a different config variant. */ preWithdrawalAction?: (param: WithdrawalParam) => Promise<WithdrawalTransaction[]>; /** * Token that Swapped accepts for fiat withdrawal. When set and differs from * {@link WithdrawalConfigBase.sourceTokenAddress}, the Swapped controller * fetches a Relay quote to swap the source token into this token before * sending to Swapped's deposit address. */ swappedTargetTokenAddress?: Address; /** * Chain ID for the Swapped target token. Defaults to * {@link WithdrawalConfigBase.sourceChainId} when omitted. */ swappedTargetChainId?: string; /** * Symbol of the Swapped target token (e.g. 'USDC'). Recorded against the * on-chain movement, which is denominated in the target — not the source — * token. Defaults to {@link WithdrawalConfigBase.sourceTokenSymbol}. */ swappedTargetTokenSymbol?: string; } export interface CustomWithdrawalConfig extends WithdrawalConfigBase { /** * @returns Optionally return a transaction hash string. When provided, the * success screen can display and link to the transaction. */ withdrawCallback: (param: WithdrawalParam) => Promise<undefined | string>; } /** Lighter L2 route type: 0 = perps (USDC collateral), 1 = spot. */ export type LighterRouteType = 0 | 1; /** * Lighter-specific data for quoteless Secure withdrawal. `assetIndex` selects * the L2 asset (avoids overloading targetChainId); `routeType` disambiguates * USDC, whose perps and spot balances share asset index 3. */ export interface LighterWithdrawalCustomerData { customerName: 'lighter'; assetIndex: number; routeType: LighterRouteType; } /** * Customer-specific data threaded through withdrawal callbacks, discriminated * by `customerName` so future customers extend the union instead of adding * top-level params. */ export type WithdrawalCustomerData = LighterWithdrawalCustomerData; export interface WithdrawalParam { quoteId: string; funQuote: unknown; targetAssetAddress: Address; targetChainId: number; destinationAddress: Address; /** Customer-specific data for quoteless flows (e.g. Lighter Secure). */ customerData?: WithdrawalCustomerData; /** User-entered amount in token units for quoteless flows (avoids overloading quoteId). */ userInputAmount?: string; /** * USD value of the withdrawal as computed by the modal (token amount × live * spot price). Provided so quoteless flows (e.g. Lighter Secure) don't need * a separate price round-trip to compute `estTotalUsd` for tracking. */ withdrawalUSD?: string; } /** * A single entry in a {@link MultiMethodWithdrawalConfig}. * * Each option represents a distinct withdrawal method (e.g. "Fast" vs "Secure") * that the user can pick from a selection screen before entering amounts. */ export interface WithdrawalMethodOption { /** Stable id used for tracking/analytics. */ id: string; /** Primary label (e.g. "Fast"). */ keyText: string; /** Optional subtitle (e.g. "USDC Perps only · Multiple chains · Instant"). */ subtitleText?: string; /** Optional leading icon. */ icon?: ReactNode; /** Optional trailing badge/icon rendered to the right of the label. */ valueIcon?: ReactNode; /** * The actual withdrawal config that drives the flow once the user selects * this option. Must be a single concrete (non-multi) config. */ config: WalletWithdrawalConfig | CustomWithdrawalConfig; } /** * Withdrawal config that presents the user with a selection screen before * entering the standard withdrawal flow. Once a method is picked, the modal * delegates to that method's inner config. */ export interface MultiMethodWithdrawalConfig { /** Title shown in the modal header. */ modalTitle: string; /** Optional meta under the title. */ modalTitleMeta?: string; /** Optional section label above the options list (e.g. "Crypto"). */ sectionTitle?: string; /** The methods the user can choose between. Order is preserved in the UI. */ methods: WithdrawalMethodOption[]; disableConnectedWallet?: boolean; } export type FunkitWithdrawalConfig = WalletWithdrawalConfig | CustomWithdrawalConfig | MultiMethodWithdrawalConfig; export interface FunkitCheckoutValidationResult { isValid: boolean; message: string; } export interface FunkitCheckoutOnCloseResult { isNewDeposit: boolean; /** True if the modal was soft-hidden (state preserved) instead of hard-closed */ isSoftHidden?: boolean; } /** * Conflict resolution options when beginCheckout is called while a soft-hidden checkout exists */ export interface CheckoutConflictResolution { /** The ID of the hidden checkout */ hiddenCheckoutId: string; /** Resume the hidden checkout (reshow the modal without starting new checkout) */ resume: () => void; /** Replace the hidden checkout with a new one (discard hidden state, proceed with new checkout) */ replace: () => void; } export type FunkitCheckoutResult = { type: 'error' | 'success'; message: string; metadata: object | Error; }; export interface UseFunkitCheckoutProps { /** @optional the checkout config object **/ config?: FunkitCheckoutConfig; /** @optional fires when the checkout modal is opened **/ onOpen?: () => void; /** @optional fires when the checkout modal is closed **/ onClose?: (result: FunkitCheckoutOnCloseResult) => void; /** @optional fires when checkout config is done validating **/ onValidation?: (result: FunkitCheckoutValidationResult) => void; /** @optional fires when a checkout quote is received **/ onEstimation?: (result: FunkitCheckoutQuoteResult) => void; /** @optional fires when a checkout is confirmed with the funkit server. Returns the newly created checkoutId. **/ onConfirmation?: (checkoutId: string) => void; /** @optional fires if the checkout fails at any point **/ onError?: (result: FunkitCheckoutResult) => void; /** @optional fires after the checkout steps are completed **/ onSuccess?: (result: FunkitCheckoutResult) => void; /** * @optional * fires if the checkout requires an active wallet connection. If not specified, defaults to funkit wallet connection. * Call the provided `onLoginFinished()` callback once login is completed to resume the checkout flow (and unhide the soft-hidden modal). * It is strongly recommended to call `onLoginFinished()` after login is completed. If not called, the checkout modal may remain hidden. */ onLoginRequired?: ({ onLoginFinished, }: { onLoginFinished?: () => void; }) => void; /** @optional fires when a withdrawal is confirmed with the funkit server. Returns the newly created withdrawalId. **/ onWithdrawalConfirmation?: (withdrawalId: string) => void; /** @optional fires when checkout is blocked (e.g. geo-restriction, security hold, or missing wallet connection). **/ onCheckoutBlocked?: (params: { blockedReason: CheckoutBlockedReason; }) => void; /** @optional fires if the withdrawal fails at any point **/ onWithdrawalError?: (result: FunkitCheckoutResult) => void; } /** Ensures that config is defined */ export interface UseFunkitCheckoutPropsWithFullConfig extends UseFunkitCheckoutProps { config: FunkitCheckoutConfig; } /** * Checkout Item for frontend use */ export interface FunkitActiveCheckoutItem extends Omit<ApiCheckoutClientMetadata, 'selectedPaymentMethodInfo' | 'initSettings'> { /** The deposit address provided by server after the checkout is confirmed. If `null`, it is not confirmed yet. **/ depositAddress: null | Address; /** Final settings the checkout was init-ed with **/ initSettings: UseFunkitCheckoutPropsWithFullConfig; /** User's selected payment method information */ selectedPaymentMethodInfo: PaymentMethodInfo | null; /** User's choice of source asset to fund the checkout operation **/ selectedSourceAssetInfo: { address: Address | null; symbol: string | null; chainId: string; iconSrc: string | null; /** Source token decimals, when known — lets later screens reconstruct * base-unit amounts. */ decimals?: number; }; /** Time of completion of the active checkout item. For frontend use only. **/ completedTimestampMs: number | null; sourceAssetAmount: string | null; /** In EXACT_IN mode, the fiat value the user entered on InputAmount. * Stored alongside targetAssetAmount so the confirmation screen can display * the same dollar value without recomputing from the token amount. */ inputFiatAmount: number | null; /** Canonical token amount in base units (e.g. 328257705848429095n). * Avoids precision loss from bigint→number round-trips for EXACT_IN quotes. */ inputTokenAmountBaseUnits: bigint | null; } export interface FunkitActiveWithdrawalItem { /** Unique identifier for frontend use only. */ id: string; /** Final settings the withdrawal was init-ed with **/ initSettings: Partial<UseFunkitCheckoutPropsWithFullConfig>; withdrawalSourceTokenBalance: () => string | number; }