@funkit/connect
Version:
Funkit Connect SDK elevates DeFi apps via web2 sign-ins and one-click checkouts.
461 lines (460 loc) • 22.3 kB
TypeScript
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;
}