Skip to main content

API reference

Every export of @fluent.xyz/connect: 65 names, 15 values and 50 types. A name or member marked experimental may change or be removed in a minor release; experimental.md explains each one. Every other name is kept after 0.6.0.

Each export shows its TSDoc, its declaration (a function's body left out), the literals of a union of strings, and a table of its members, nested members by their dotted path; a union of object types shows its members in its declaration instead.

Contents​

  • Widget and hooks: FluentWidget, FluentWidgetProps, FluentWidgetRenderContext, FluentWidgetConnectButtonRenderContext, useFluentWidget, FluentWidgetStatus, FluentWidgetSession, FluentWidgetAccount, FluentAccountType, FluentAccountCapabilities, FluentWidgetExecutionStatus, AccountAvatar, AccountAvatarProps
  • Config: FluentWidgetConfig, FluentWidgetNetwork, FluentWidgetAuthMethod, FluentWidgetAssets
  • Batch operations: FluentBatchApi, FluentBatchOperationInput, FluentBatchCallInput, FluentBatchOperation, FluentBatchOperationExecuteOptions, FluentEncodedBatchCall, FluentExecuteResult, FluentGasPayment, FluentWidgetGasPayment, FluentGasTokenSymbol
  • Signing: FluentSignApi, FluentSigningMode
  • Auth and errors: FluentAuthError, FluentAuthErrorCode, FluentWidgetConfigError, FluentWidgetConfigErrorCode, FluentReviewRejectedError, FluentReviewRejectedErrorCode, FluentSponsorshipFallbackError, FluentSponsorshipReason, FluentGasPaymentError, FluentGasPaymentErrorCode
  • Link X: useLinkX, FluentLinkXStatus, FluentLinkXResult, FluentXAccount
  • Iframe: useFluentIframeBridge, UseFluentIframeBridgeOptions, FluentIframeElement
  • Chains and tokens: fluentMainnet, fluentTestnet, getFluentChainByChainId, getFluentChainForNetwork, FluentChainDefinition, FluentTokenDefinition
  • Experimental: FluentPrivyAdapter, FluentExternalWalletState, FluentWalletChoice, FluentWidgetExecution, FluentWidgetExecutionSource, FluentUserOperationGas, FluentUserOperationGasError, FluentUserOperationGasErrorCode, FluentFixedAccountGas, FluentFixedPaymasterGas, FluentExecuteTiming, FluentExecuteTimingInstants, FluentGasPaymentEthRates

Widget and hooks​

FluentWidget​

value

The Fluent Connect widget: the connect button, the sign-in dialog, the account drawer and the Widget API, provided to the App's own content rendered through renderHome or renderPage.

Throws FluentWidgetConfigError from a render when config cannot be used, its code missing_option, invalid_option or removed_option with the option named in its message, or, for the experimental execution, invalid_fee_source, invalid_receipt_source or invalid_chain_check_before_sign. The config is resolved again on every render that passes a new config object, so such a render can throw too; keep config stable (a module constant or a memo) to resolve it once.

Example

import { FluentWidget, type FluentWidgetConfig } from "@fluent.xyz/connect";

// The App's id, its login client and the Network: see the configuration guide.
declare const config: FluentWidgetConfig;

export function App() {
return <FluentWidget config={config} mode="page" renderPage={() => <main>Your App</main>} />;
}
export function FluentWidget(props: FluentWidgetProps) { … }

FluentWidgetProps​

type

The props of <FluentWidget>.

export type FluentWidgetProps = {
wallet?: FluentExternalWalletState | null;
config: FluentWidgetConfig;
privyAdapter?: FluentPrivyAdapter;
mode?: "home" | "page";
connectButton?: "fixed" | "inline" | false;
renderConnectButton?: (context: FluentWidgetConnectButtonRenderContext) => ReactNode;
renderHome?: (context: FluentWidgetRenderContext) => ReactNode;
renderPage?: (context: FluentWidgetRenderContext) => ReactNode;
tokens?: readonly FluentTokenDefinition[];
debugLogging?: boolean;
onSessionChange?: (session: FluentWidgetSession | null) => void;
};
MemberTypeOptionalExperimentalDescription
walletFluentExternalWalletState | nullyesyesAn External wallet the host connects itself, a FluentExternalWalletState whose inline choices are FluentWalletChoices; omit it to let the widget connect one. Experimental. May change or be removed in a minor release: a host that brings its own wallet connection is not a 0.6.0 commitment.
configFluentWidgetConfignono
privyAdapterFluentPrivyAdapteryesyesOptional native Privy SDK bridge, a FluentPrivyAdapter, for a host that runs Privy's native SDK; requires Direct login. Experimental. May change or be removed in a minor release: the one provider seam of the widget, kept for hosts that run a native login SDK.
mode"home" | "page"yesno
connectButton"fixed" | "inline" | falseyesnoShow the default connect/account control. - "fixed" (default) — top-right floating button - "inline" — same button, no fixed positioning (host places via CSS / wrapper) - false — hide; use renderConnectButton, openConnect, or openAccount
renderConnectButton(context: FluentWidgetConnectButtonRenderContext) => ReactNodeyesnoReplace the default connect button UI / placement entirely.
renderHome(context: FluentWidgetRenderContext) => ReactNodeyesno
renderPage(context: FluentWidgetRenderContext) => ReactNodeyesno
tokensreadonly FluentTokenDefinition[]yesnoIntegrator tokens the widget lists beside its Default tokens; see FluentTokenDefinition.
debugLoggingbooleanyesnoPrint the widget's internal connect / smart-account / signing diagnostics to the console, and show the debug panel in mode="home". Off by default; turn on only while debugging an integration.
onSessionChange(session: FluentWidgetSession | null) => voidyesnoCalled each time the widget writes its stored session: with the new FluentWidgetSession when a Fluent ID signs in by Direct login, and with null on a disconnect through the widget. It is not called for a session restored from storage on mount, nor when an External wallet connects; read the render context's session and widget.account for those.

FluentWidgetRenderContext​

type

What the widget hands an App's own content: the Widget session, the Widget API, the External wallet, and the calls that open the widget's screens, disconnect, mint an Auth token and link X. renderHome and renderPage receive it, and useFluentWidget() returns it.

export type FluentWidgetRenderContext = {
session: FluentWidgetSession | null;
userAvatarUrl?: string;
userXDisplayName?: string;
userGoogleName?: string;
wallet: FluentExternalWalletState | null;
widget: FluentBatchApi;
openConnect: () => void;
openAccount: () => void;
disconnect: () => Promise<void>;
hasConnectedAccount: boolean;
status: FluentWidgetStatus;
refreshBalances: () => void;
getAuthToken: () => Promise<string>;
linkX: () => Promise<FluentLinkXResult>;
};
MemberTypeOptionalExperimentalDescription
sessionFluentWidgetSession | nullnonoWho is signed in, or null: a FluentWidgetSession. Once onSessionChange has fired on this page, equal to (not the same object as) the last value it received; a session restored from storage is here before any callback. Connecting an External wallet neither writes nor clears it; read the connected account from widget.account.
userAvatarUrlstringyesnoThe Account avatar's linked X picture; absent for an External wallet or forceDefault.
userXDisplayNamestringyesnoThe display name of the person's linked X account, the name X shows above the handle, not the handle. Absent when no X account is linked or X reports no name for it, and while an External wallet is connected without a Fluent ID. avatar.forceDefault does not hide it.
userGoogleNamestringyesnoThe name on the Google account linked to the person's Direct login. Absent when none is linked or Google reports no name for it, and while an External wallet is connected without a Fluent ID. avatar.forceDefault does not hide it.
walletFluentExternalWalletState | nullnoyesThe External wallet as the widget sees it, a FluentExternalWalletState whose inline choices are FluentWalletChoices, or null. Experimental. May change or be removed in a minor release: a host that brings its own wallet connection is not a 0.6.0 commitment.
widgetFluentBatchApinonoThe Widget API: the account, Batch operations, signatures and the current Gas payment.
openConnect() => voidnonoOpens the sign-in dialog, the same one the connect button opens. A click event passed to it is ignored, so it can be handed straight to onClick.
openAccount() => voidnonoOpens the account drawer, as the connect button does once a Widget account is connected.
disconnect() => Promise<void>nonoTear down the current session without going through the widget's account menu — the same teardown that menu's "Disconnect" runs. The session and the stored identity token are cleared before this resolves, so a host can await it before resetting its own state. It also asks the service to revoke the Service session, and any Service session that a getAuthToken() still out at the disconnect opens later, and resolves once those requests have been answered or have failed, so it can take as long as that getAuthToken() does. A failed revocation is not retried: the promise resolving does not prove the service ended the Service session.
hasConnectedAccountbooleannonoWhether a Widget account, a Fluent ID or an External wallet, is connected now. false while a stored session is still being restored: branch on status to tell that apart.
statusFluentWidgetStatusnonoConnection state as a single value that is meaningful on every render — including the first, before a stored session has been reconciled. Branch on this rather than on hasConnectedAccount alone: "restoring" and "disconnected" both have hasConnectedAccount === false, and treating them alike shows a Connect button to users who are already signed in. The account's address is widget.account.address.
refreshBalances() => voidnonoRefetch the widget's on-chain balances. Transactions run through widget.createBatchOp refresh automatically, and the wallet menu reads them itself when it opens and every 10 seconds while it stays open; call this to show a tx sent via an External wallet client (outside the widget's execution path) before the next scheduled read.
getAuthToken() => Promise<string>nonoA short-lived (5 min) Auth token, a Fluent-signed JWT for the connected person. Verify it on your backend against <iss>/.well-known/jwks.json (ES256), checking iss, aud (= your appId) and exp; sub is stable per person per App. Reused until authTokenRenewalOffsetSeconds before exp, then renewed; a renewal the service accepts asks the wallet for nothing. When the wallet is asked to sign: guides/backend-auth.md. External wallets: EOAs and deployed smart-contract wallets sign in; a not-yet-deployed smart account cannot (no ERC-6492). Throws FluentAuthError, its code one of: - not_connected: no Widget account is connected, or an External wallet has no client when the call has to sign the Sign-in challenge; - login_credential_missing: a Fluent ID's login left no credential in the page; - origin_mismatch: the Sign-in challenge names an origin other than the page's; - request_failed: the request did not reach the service, its answer carried no code or no token, or the External wallet refused to sign the Sign-in challenge; - service_refused: the service refused with a code this SDK does not name; - a code the service answered with: unknown_app, app_not_auth_enabled, origin_not_allowed, origin_missing, bad_request, challenge_unknown, challenge_used, challenge_expired, app_mismatch, invalid_signature, signature_prefix_rejected, address_already_linked, invalid_login_credential, no_signer, rate_limited or internal. A refused refresh credential is not among them: the call forgets it and signs in again within the same call, which can prompt the External wallet. Keep a default branch.
linkX() => Promise<FluentLinkXResult>nonoLink an X account to the connected person, headless: no widget screen, no settings page, one call. Idempotent and re-enterable, because linking X needs an OAuth round trip that leaves the page. A person who already has X resolves { status: "linked", x } without leaving the page. A person who has none resolves { status: "redirecting" } once the navigation to X has started: the page is unloading, so nothing on it may wait for the link to finish. When the browser comes back, useLinkX() calls linkX() again on mount, and it resolves linked; nothing public tells a page that calls linkX() itself that it is a return from X. A link somebody else started in this tab, or a stale record of one, is discarded rather than resumed: the call does no work and rejects with link_failed; call again to start one for the signed-in person. An External wallet can be asked to sign before either result: guides/link-x.md says when. Throws FluentAuthError, whose codes today are user_rejected, linked_to_another_user, not_authenticated, bad_request and link_failed; keep a default branch for any code a later release adds.

FluentWidgetConnectButtonRenderContext​

type

What renderConnectButton receives to draw its own connect button.

export type FluentWidgetConnectButtonRenderContext = {
connected: boolean;
pending: boolean;
addressLabel?: string;
userAvatarUrl?: string;
userXDisplayName?: string;
userGoogleName?: string;
onClick: () => void;
openConnect: () => void;
openAccount: () => void;
DefaultButton: () => ReactNode;
};
MemberTypeOptionalExperimentalDescription
connectedbooleannono
pendingbooleannonoTrue while a Fluent ID's smart account is being prepared after sign-in.
addressLabelstringyesno
userAvatarUrlstringyesnoThe Account avatar's linked X picture; absent for an External wallet or forceDefault.
userXDisplayNamestringyesnoThe person's X display name, the render context's userXDisplayName.
userGoogleNamestringyesnoThe person's Google name, the render context's userGoogleName.
onClick() => voidnono
openConnect() => voidnono
openAccount() => voidnono
DefaultButton() => ReactNodenono

useFluentWidget​

value

Access the Fluent widget render context from any component rendered inside <FluentWidget> (e.g. within renderHome / renderPage). Returns the same object the render props receive — session, widget, openConnect, etc. — so host apps no longer need to prop-drill it. The Widget API is useFluentWidget().widget.

Throws if used outside <FluentWidget>.

Example

import { useFluentWidget } from "@fluent.xyz/connect";

const calls = [{ to: "0x000000000000000000000000000000000000dEaD", value: 1n, data: "0x" }] as const;

export function Send() {
const { widget, status } = useFluentWidget();
if (status !== "connected") return null;
return <button onClick={() => widget.createBatchOp({ calls }).execute()}>Send</button>;
}
export function useFluentWidget(): FluentWidgetRenderContext { … }

FluentWidgetStatus​

type

Top-level connection state of the widget, safe to read on any render.

The distinction that matters is "restoring" vs "disconnected": a session restored from a previous visit is not known synchronously, so a host that treats "not connected yet" as "no account" will render a Connect button at users who do have a live session. Wait out "restoring" instead.

  • "restoring" — a previous session may exist; nothing is decided yet.
  • "connecting" — a sign-in the user started is in flight.
  • "connected" — an account is available. Whether it can send transactions right now is a separate question — see widget.account.executionStatus.
  • "disconnected" — no account, and none is being restored or negotiated.
export type FluentWidgetStatus = "restoring" | "connecting" | "connected" | "disconnected";

Literals: connected, connecting, disconnected, restoring.

FluentWidgetSession​

type

What the widget tells an App about who is signed in: the Widget account's address and type, and when the widget built that snapshot. Not a credential: an App proves who the person is with an Auth token from getAuthToken().

Only a Fluent ID's Direct login writes a session. Connecting an External wallet neither writes nor clears it; read the connected account from widget.account.

export type FluentWidgetSession = {
account: {
address: Address;
type: FluentAccountType;
};
issuedAt: number;
};
MemberTypeOptionalExperimentalDescription
account{ address: Address; type: FluentAccountType; }nono
account.addressAddressnonoThe Fluent ID's smart account address.
account.typeFluentAccountTypenono"smart": today a session is only ever a Fluent ID's. FluentAccountType may gain values, so a switch over it keeps a default branch.
issuedAtnumbernonoThe local time the widget built the snapshot, in Unix seconds from the browser's clock; not a time the service attests. A session restored on a later page load keeps that time.

FluentWidgetAccount​

type

The Widget account as the Widget API reports it: its address and type, whether it is connected and can send now, and what it can do.

export type FluentWidgetAccount = {
address?: Address;
connected: boolean;
executionReady: boolean;
executionStatus: FluentWidgetExecutionStatus;
executionError?: string;
type?: FluentAccountType;
capabilities: FluentAccountCapabilities;
};
MemberTypeOptionalExperimentalDescription
addressAddressyesnoThe Widget account's address: the Fluent ID's, or the External wallet's.
connectedbooleannonoWhether a Widget account, a Fluent ID or an External wallet, is connected.
executionReadybooleannonoWhether the Widget account can send now; executionStatus says why it cannot.
executionStatusFluentWidgetExecutionStatusnonoWhere sending stands, a FluentWidgetExecutionStatus.
executionErrorstringyesnoThe message of the last failure to prepare the Fluent ID's account, if any: the reason an executionStatus of "error" gives.
typeFluentAccountTypeyesnoActive account kind, or undefined when nothing is connected.
capabilitiesFluentAccountCapabilitiesnonoWhat the active account can do — lets hosts adapt UI without branching on type.

FluentAccountType​

type

How the connected account executes transactions.

export type FluentAccountType = "smart" | "eoa";

Literals: eoa, smart.

FluentAccountCapabilities​

type

What the Widget account can do, so a host adapts its UI without branching on its type.

export type FluentAccountCapabilities = {
atomicBatch: boolean;
erc20Gas: boolean;
};
MemberTypeOptionalExperimentalDescription
atomicBatchbooleannonoMultiple calls land in a single atomic transaction (smart account only).
erc20GasbooleannonoGas can be paid in an ERC-20 (BLEND / USDnr) via the paymaster instead of native ETH — smart account only. This is NOT free/sponsored gas: the user still pays, just in a token.

FluentWidgetExecutionStatus​

type

Whether the Widget account can send now: ready, disconnected, unavailable (connected but not ready yet) or error (see FluentWidgetAccount.executionError).

export type FluentWidgetExecutionStatus = "disconnected" | "ready" | "unavailable" | "error";

Literals: disconnected, error, ready, unavailable.

AccountAvatar​

value

The Account avatar: the person's linked X picture, else the host's default logo, else the Fluent mark.

Example

import { AccountAvatar, useFluentWidget } from "@fluent.xyz/connect";

export function Avatar() {
const { userAvatarUrl } = useFluentWidget();
return <AccountAvatar userAvatarUrl={userAvatarUrl} />;
}
export function AccountAvatar({ userAvatarUrl, defaultLogoUrl, className }: AccountAvatarProps) { … }

AccountAvatarProps​

type

The props of AccountAvatar.

export type AccountAvatarProps = {
userAvatarUrl?: string | null;
defaultLogoUrl?: string | null;
className?: string;
};
MemberTypeOptionalExperimentalDescription
userAvatarUrlstring | nullyesnoThe person's linked X picture, the render context's userAvatarUrl. Wins over defaultLogoUrl.
defaultLogoUrlstring | nullyesnoHost-supplied stand-in for the Fluent mark — a URL or a data URI.
classNamestringyesno

Config​

FluentWidgetConfig​

type

The configuration of one <FluentWidget>, fixed for the App's deployment.

export type FluentWidgetConfig = {
appId: string;
privyClientId: string;
clientId?: never;
partnerId?: never;
network: FluentWidgetNetwork;
appName?: string;
customOAuthRedirectUrl?: string;
enabledAuthMethods?: FluentWidgetAuthMethod[];
swapper?: {
enabled?: boolean;
integratorId?: string;
dstChainId?: string;
dstTokenAddress?: string;
};
gasPayment?: {
defaultToken?: FluentGasTokenSymbol;
sponsorship?: "auto" | "never";
ethValueByToken?: FluentGasPaymentEthRates;
};
execution?: FluentWidgetExecution;
quickSign?: {
whitelistAddresses?: readonly string[];
};
reputationEnabled?: boolean;
source?: string;
campaign?: string;
disableAnalytics?: boolean;
reconnectOnMount?: boolean;
sponsorshipUrl?: string;
authTokenRenewalOffsetSeconds?: number;
assets?: Partial<FluentWidgetAssets>;
avatar?: {
defaultLogoUrl?: string;
forceDefault?: boolean;
};
};
MemberTypeOptionalExperimentalDescription
appIdstringnonoThe App's id (app_<32 hex>) — the same string the Fluent Dashboard shows and the token's aud carries.
privyClientIdstringnoyesThe Privy app client issued by Fluent for this App. Login configuration, not identity: it carries the allowed origins for Privy login, while appId is what sponsorship, auth and analytics speak. Required while login runs on Privy. Experimental. May change or be removed in a minor release: it exists while login runs on Privy, and goes when Fluent's own login replaces it.
clientIdneveryesnoRemoved in 0.2.0 — the type exists only so the error names the fix.
partnerIdneveryesnoRenamed to appId in 0.3.0 — the type exists only so the error names the fix.
networkFluentWidgetNetworknonoThe Network the widget runs on. It picks the chain, the funds, the login app, the account project and the service, so the App names it: the widget reads no environment variable for it and has no default.
appNamestringyesno
customOAuthRedirectUrlstringyesyesAn HTTPS URL passed to the provider behind Direct login as its OAuth redirect URL. When to use it: experimental.md. Experimental. May change or be removed in a minor release: it configures the redirect of the login provider the widget runs on today.
enabledAuthMethodsFluentWidgetAuthMethod[]yesnoSign-in methods the sign-in dialog offers beside X, in the order given. Defaults to ["google", "email", "passkey"] — all of them; name a shorter set to drop the rest, or [] to leave X as the only Fluent method. Sign-in with X is always offered and is not a value here. A method still has to be enabled for the App by Fluent — listing it here only decides whether its button is drawn.
swapper{ enabled?: boolean; integratorId?: string; dstChainId?: string; dstTokenAddress?: string; }yesyesThe swap entry in the account drawer. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.
swapper.enabledbooleanyesno
swapper.integratorIdstringyesno
swapper.dstChainIdstringyesno
swapper.dstTokenAddressstringyesno
gasPayment{ defaultToken?: FluentGasTokenSymbol; sponsorship?: "auto" | "never"; ethValueByToken?: FluentGasPaymentEthRates; }yesno
gasPayment.defaultTokenFluentGasTokenSymbolyesnoInitial/fallback token. Defaults to ETH; a valid saved user choice wins.
gasPayment.sponsorship"auto" | "never"yesnoNative-gas sponsorship policy. Defaults to auto; never skips the paymaster.
gasPayment.ethValueByTokenFluentGasPaymentEthRatesyesyesWhat one unit of each Gas token is worth in ETH, a FluentGasPaymentEthRates keyed by FluentGasTokenSymbol, for the cost the widget shows beside each Gas token. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.
executionFluentWidgetExecutionyesyesHow a Fluent ID's send is executed: a FluentWidgetExecution, each of whose sources is a FluentWidgetExecutionSource. Every field defaults to the fast path, which falls back to the bundler on its own: set a field to switch that one answer to the bundler for good. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.
quickSign{ whitelistAddresses?: readonly string[]; }yesno
quickSign.whitelistAddressesreadonly string[]yesnoCall targets Quick sign may execute without the Review. With a list set, a Batch operation runs in Silent mode only when every call's to is on it; a single call to any other address shows the Review, even with Quick sign on. An empty list is honoured and stops Quick sign from silencing anything; omit the option to keep it covering every address. Compared case-insensitively. signMessage / signTypedData have no call target and are not scoped by this.
reputationEnabledbooleanyesnofalse drops the Reputation tab that follows Tokens and Activity in the account drawer. The families request is never made. Default true.
sourcestringyesno
campaignstringyesno
disableAnalyticsbooleanyesnoTurns off all analytics: PostHog is never initialised, nothing is sent or stored.
reconnectOnMountbooleanyesnoRestore injected wallets and WalletConnect by default; true restores all, false restores none.
sponsorshipUrlstringyesyesPoint the widget at a sponsorship service other than the network default. Local development only: the deployed URL is the one every real integration should use. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.
authTokenRenewalOffsetSecondsnumberyesnogetAuthToken() reuses a token until this many seconds before its exp, then fetches a fresh one (the renewal-offset pattern: MSAL's tokenRenewalOffsetSeconds, Go oauth2's expiryDelta). Default 30. Set 0 to drop the margin entirely: a token is then reused until exp itself, which is not the same as always fetching — it is the case the margin exists for, a token handed out a moment before it expires and rejected by the backend it is sent to.
assetsPartial<FluentWidgetAssets>yesnoImages for the widget; see FluentWidgetAssets.
avatar{ defaultLogoUrl?: string; forceDefault?: boolean; }yesnoThe account avatar shown on the connect button and in the account drawer.
avatar.defaultLogoUrlstringyesnoImage used when there is no X avatar to show — a URL or a data URI. Defaults to the Fluent mark.
avatar.forceDefaultbooleanyesnoAlways show defaultLogoUrl, even for users who signed in with X. Default false (the linked X picture wins when the person has one).

FluentWidgetNetwork​

type

Which of Fluent's two deployments the widget runs on.

export type FluentWidgetNetwork = "testnet" | "mainnet";

Literals: mainnet, testnet.

FluentWidgetAuthMethod​

type

A sign-in method the sign-in dialog can offer beside X. X is not on this list: it is always offered and cannot be switched off.

export type FluentWidgetAuthMethod = "google" | "email" | "passkey";

Literals: email, google, passkey.

FluentWidgetAssets​

type

Images for the widget, each a URL or a data URI. Today the widget reads only fluentLogo, which it passes to the provider behind Direct login as its logo; see assets in configuration.md.

export type FluentWidgetAssets = {
fluentLogo: string;
walletConnectIcon: string;
metamaskIcon: string;
coinbaseIcon: string;
};
MemberTypeOptionalExperimentalDescription
fluentLogostringnonoThe Fluent mark.
walletConnectIconstringnonoThe WalletConnect icon.
metamaskIconstringnonoThe MetaMask icon.
coinbaseIconstringnonoThe Coinbase Wallet icon.

Batch operations​

FluentBatchApi​

type

The Widget API: what the widget gives an App's code to act for the Widget account. Reached as widget in the render context or from useFluentWidget().

export type FluentBatchApi = FluentSignApi & {
account: FluentWidgetAccount;
signingMode: FluentSigningMode;
gasPayment: FluentWidgetGasPayment;
createBatchOp: (input: FluentBatchOperationInput) => FluentBatchOperation;
};
MemberTypeOptionalExperimentalDescription
signMessage(params: { message: SignableMessage }) => Promise<Hex>nonoEIP-191 signature. ERC-1271 (ERC-6492 before deployment) for a smart account, ECDSA for an External wallet. Throws FluentReviewRejectedError with code signature_review_rejected when the person dismisses the Review: always shown for an External wallet, and for a Fluent ID unless Quick sign is on. An External wallet's refusal in its own prompt rejects with the wallet's error. Throws FluentAuthError with code not_connected when no Widget account is connected or the External wallet has no client, and root_signer_required when neither of the Fluent ID's signing paths signs with its root Signer, a guard no configuration reaches today. (example below)
signTypedData< const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData, >( typedData: TypedDataDefinition<typedData, primaryType>, ) => Promise<Hex>nonoEIP-712 signature. ERC-1271 (ERC-6492 before deployment) for a smart account, ECDSA for an External wallet. Throws FluentReviewRejectedError with code signature_review_rejected when the person dismisses the Review, as for signMessage. Throws FluentAuthError with code not_connected or root_signer_required, as for signMessage. (example below)
accountFluentWidgetAccountnonoThe Widget account: its address, type, readiness and capabilities.
signingModeFluentSigningModenonoHow the widget signs by default: "silent" while the person's Quick sign is on.
gasPaymentFluentWidgetGasPaymentnonoThe Gas payment chosen in the widget, which execute() uses when given no gasPayment.
createBatchOp(input: FluentBatchOperationInput) => FluentBatchOperationnonoBuilds a Batch operation, encoding its calls now; execute() sends it. Throws Error when calls is empty, or when a call has neither data nor abi with method: a call that still names its function in 0.5's functionName is one.

signMessage example

import { useFluentWidget } from "@fluent.xyz/connect";

export function useSignGreeting() {
const { widget } = useFluentWidget();
return () => widget.signMessage({ message: "Sign in to Marketplace" });
}

signTypedData example

import { useFluentWidget } from "@fluent.xyz/connect";

const verifyingContract = "0x0000000000000000000000000000000000000001";
const order = { maker: "0x0000000000000000000000000000000000000002", price: 100n } as const;

export function useSignOrder() {
const { widget } = useFluentWidget();
return () =>
widget.signTypedData({
domain: { name: "Marketplace", version: "1", chainId: 20994, verifyingContract },
types: {
Order: [
{ name: "maker", type: "address" },
{ name: "price", type: "uint256" },
],
},
primaryType: "Order",
message: order,
});
}

FluentBatchOperationInput​

type

What widget.createBatchOp takes: the calls of one Batch operation, and its Review title.

export type FluentBatchOperationInput = {
id?: string;
reviewTitle?: string;
calls: readonly FluentBatchCallInput[];
};
MemberTypeOptionalExperimentalDescription
idstringyesno
reviewTitlestringyesnoHeading of this Batch operation's Review (Fluent ID, Prompt mode). Defaults to the widget's own copy, "Confirm transaction".
callsreadonly FluentBatchCallInput[]nono

FluentBatchCallInput​

type

One contract call of a Batch operation: either raw data, or an abi with the method to encode and its args.

export type FluentBatchCallInput = {
id?: string;
label?: string;
to: Address;
value?: bigint;
data?: Hex;
abi?: Abi;
method?: string;
args?: readonly unknown[];
};
MemberTypeOptionalExperimentalDescription
idstringyesno
labelstringyesno
toAddressnono
valuebigintyesnoNative ETH sent with the call, in wei. Defaults to 0n.
dataHexyesno
abiAbiyesno
methodstringyesnoThe ABI function to encode, with abi and args, when data is not given.
argsreadonly unknown[]yesno

FluentBatchOperation​

type

A Batch operation widget.createBatchOp built: its calls, encoded, and execute, which sends them for the Widget account.

export type FluentBatchOperation = {
id?: string;
reviewTitle?: string;
calls: readonly FluentBatchCallInput[];
encodedCalls: FluentEncodedBatchCall[];
canExecute: boolean;
execute: (options?: FluentBatchOperationExecuteOptions) => Promise<FluentExecuteResult>;
};
MemberTypeOptionalExperimentalDescription
idstringyesnoFluentBatchOperationInput.id, copied.
reviewTitlestringyesnoFluentBatchOperationInput.reviewTitle, copied.
callsreadonly FluentBatchCallInput[]nonoThe calls as the host passed them.
encodedCallsFluentEncodedBatchCall[]nonoThe same calls encoded, in order: what execute() sends.
canExecutebooleannonoWhether the widget could send the operation when it was built: the Widget account ready, or able to prepare itself when execute() is called. Not updated afterwards; read widget.account.executionReady for the live value.
execute(options?: FluentBatchOperationExecuteOptions) => Promise<FluentExecuteResult>nonoExecutes the operation for the Widget account and resolves once it is included. Throws FluentReviewRejectedError with code transaction_review_rejected when the person dismisses the Review, which a Fluent ID shows in Prompt mode; an External wallet's refusal in its own prompt rejects with the wallet's error. Throws FluentSponsorshipFallbackError when a Fluent ID asked for Sponsorship, a sponsored submission failed, and the submission in which the account pays its own gas failed too; its code says why Sponsorship failed: denied, unauthorized, unreachable or no_token. A failure while waiting for an operation's inclusion is not wrapped: it rejects with its own error, and an operation whose wait failed was submitted. What else is not wrapped: guides/sponsorship.md. Throws FluentUserOperationGasError (experimental) with code invalid_policy for a skipEstimation policy that cannot be used (other invalid userOperationGas values throw a plain Error), and paymaster_gas_mismatch when, with estimation skipped, the paymaster's authorization of a Gas token payment does not match the fixed gas limits. Under Sponsorship, a failed check of the paymaster's gas fails the sponsored submission instead, and the account pays its own gas. Throws FluentGasPaymentError with code paymaster_unavailable when a Fluent ID's operation is paid in a Gas token and the address of the paymaster to approve it to cannot be read; nothing has been signed or sent.

FluentBatchOperationExecuteOptions​

type

The options of FluentBatchOperation.execute, every one optional.

export type FluentBatchOperationExecuteOptions = {
confirmation?: FluentSigningMode;
gasPayment?: FluentGasPayment;
userOperationGas?: FluentUserOperationGas;
};
MemberTypeOptionalExperimentalDescription
confirmationFluentSigningModeyesnoThe FluentSigningMode of this operation. Defaults to the widget's signingMode, which follows the person's Quick sign. A "silent" operation with a call outside quickSign.whitelistAddresses is signed as "prompt". Any value but "silent", 0.5's "always" and "session" included, is "prompt". Ignored for an External wallet.
gasPaymentFluentGasPaymentyesnoHow the gas of this operation is paid. Defaults to the Gas payment chosen in the widget.
userOperationGasFluentUserOperationGasyesyesFluent ID gas limits, estimation opt-out or headroom: a FluentUserOperationGas, whose fixed limits are a FluentFixedAccountGas and a FluentFixedPaymasterGas. Ignored for External wallet sends. Experimental. May change or be removed in a minor release while the gas policy is measured; omitting it, which lets the widget estimate, is the recommended setting.

FluentEncodedBatchCall​

type

A Batch operation's call as it is sent: data encoded, value defaulted to 0n.

export type FluentEncodedBatchCall = {
id?: string;
label?: string;
to: Address;
value: bigint;
data: Hex;
};
MemberTypeOptionalExperimentalDescription
idstringyesno
labelstringyesno
toAddressnono
valuebigintnono
dataHexnono

FluentExecuteResult​

type

Result of executing a batch operation.

export type FluentExecuteResult = {
hash: Hash;
hashes: Hash[];
receipt?: TransactionReceipt;
userOpHash?: Hash;
atomic: boolean;
sponsored: boolean | null;
paymaster?: Address;
timing?: FluentExecuteTiming;
};
MemberTypeOptionalExperimentalDescription
hashHashnonoPrimary transaction hash — the final/target call (e.g. deposit/withdraw).
hashesHash[]nonoAll transaction hashes in order. One for a smart-account UserOp; one per call for an EOA.
receiptTransactionReceiptyesnoIncluded receipt for hash (the final EOA call); not a finality guarantee.
userOpHashHashyesnoERC-4337 operation hash when executing through a smart account.
atomicbooleannonoTrue when all calls landed atomically (smart account), false for sequential EOA txs.
sponsoredboolean | nullnonoWhether the App's Sponsorship paid the gas for this operation, read back off the settled operation (which contract the EntryPoint charged) first: a refused Sponsorship falls back to the account paying its own gas, so a flag set from asking for Sponsorship would report the fallback as a success. false for a Gas token payment and always for an External wallet, which has no paymaster. When a Fluent ID's receipt names no paymaster, the way the operation was sent decides: false when it made no sponsored submission (the fallback, sponsorship: "never", Sponsorship unavailable or not configured), and null after a sponsored submission, where nothing says who paid.
paymasterAddressyesyesThe paymaster a Fluent ID's receipt names: the Sponsorship paymaster or a Gas token's paymaster, when one paid. Undefined when the receipt carried no paymaster — sponsored is then false, or null after a sponsored submission — and always for an External wallet, which has no paymaster. Experimental. May change or be removed in a minor release while the numbers are measured.
timingFluentExecuteTimingyesyesWhere this send's milliseconds went, as a FluentExecuteTiming whose raw instants are a FluentExecuteTimingInstants. Fluent ID only: an External wallet performs its prompt, its signing and its broadcast behind one sendTransaction await, so EIP-1193 exposes no truthful instant to put a boundary on. Experimental. May change or be removed in a minor release while the numbers are measured.

FluentGasPayment​

type

How one operation's gas is paid: a Gas token by symbol, or native ETH, and whether Sponsorship is asked for.

Without includeApproval, the widget adds the approval of the Gas token to its ERC-20 paymaster at the start of the operation when the account's allowance is below the threshold: 1 USDnr when the allowance is below 0.1 USDnr, 10 BLEND when it is below 1 BLEND. An allowance that cannot be read counts as none. includeApproval: true with an approveAmount approves that amount instead, whatever the allowance. Either way the Review lists the operation's own calls, and nothing is added for "ETH" or for an External wallet.

export type FluentGasPayment = {
/**
* Gas token symbol. The widget resolves the ERC-20 address for the active
* network internally — callers never pass (or risk mistyping) an address.
* `"ETH"` means native gas (no ERC-20 paymaster).
*/
symbol: FluentGasTokenSymbol;
/**
* Whether to ask the App's sponsorship paymaster to pay for this operation.
*
* `"auto"` tries Sponsorship, and if the paymaster refuses, resends with the account paying
* its own gas. `"never"` skips the paymaster outright, so the account pays from the first
* attempt and the result reports `sponsored: false`. What an omitted value means:
* [guides/sponsorship.md](guides/sponsorship.md#not-asking-sponsorship-never-and-gas-tokens).
*
* Ignored when `symbol` names an ERC-20: that token's own paymaster pays,
* and sponsorship is not in the picture.
*/
sponsorship?: "auto" | "never";
} & (
| {
/** Approves `approveAmount` to the paymaster whatever the allowance, for a Gas token. */
includeApproval: true;
/** In the Gas token's smallest unit. */
approveAmount: bigint;
}
| {
includeApproval?: false;
approveAmount?: never;
}
);

FluentWidgetGasPayment​

type

The Gas payment chosen in the widget: the Gas token's symbol, its address and decimals.

export type FluentWidgetGasPayment = {
symbol: FluentGasTokenSymbol;
token?: Address;
decimals: number;
};
MemberTypeOptionalExperimentalDescription
symbolFluentGasTokenSymbolnono
tokenAddressyesno
decimalsnumbernono

FluentGasTokenSymbol​

type

A symbol that can pay for gas. Deliberately string: which tokens are payable is data on the token definitions now, not a fixed set of literals.

export type FluentGasTokenSymbol = string;

Signing​

FluentSignApi​

type

The signatures of the Widget API, for the Widget account. A Fluent ID shows the Review in Prompt mode and none in Silent mode; an External wallet always shows the Review, then its own prompt.

export type FluentSignApi = {
signMessage: (params: { message: SignableMessage }) => Promise<Hex>;
signTypedData: <
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
>(
typedData: TypedDataDefinition<typedData, primaryType>,
) => Promise<Hex>;
};
MemberTypeOptionalExperimentalDescription
signMessage(params: { message: SignableMessage }) => Promise<Hex>nonoEIP-191 signature. ERC-1271 (ERC-6492 before deployment) for a smart account, ECDSA for an External wallet. Throws FluentReviewRejectedError with code signature_review_rejected when the person dismisses the Review: always shown for an External wallet, and for a Fluent ID unless Quick sign is on. An External wallet's refusal in its own prompt rejects with the wallet's error. Throws FluentAuthError with code not_connected when no Widget account is connected or the External wallet has no client, and root_signer_required when neither of the Fluent ID's signing paths signs with its root Signer, a guard no configuration reaches today. (example below)
signTypedData< const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData, >( typedData: TypedDataDefinition<typedData, primaryType>, ) => Promise<Hex>nonoEIP-712 signature. ERC-1271 (ERC-6492 before deployment) for a smart account, ECDSA for an External wallet. Throws FluentReviewRejectedError with code signature_review_rejected when the person dismisses the Review, as for signMessage. Throws FluentAuthError with code not_connected or root_signer_required, as for signMessage. (example below)

signMessage example

import { useFluentWidget } from "@fluent.xyz/connect";

export function useSignGreeting() {
const { widget } = useFluentWidget();
return () => widget.signMessage({ message: "Sign in to Marketplace" });
}

signTypedData example

import { useFluentWidget } from "@fluent.xyz/connect";

const verifyingContract = "0x0000000000000000000000000000000000000001";
const order = { maker: "0x0000000000000000000000000000000000000002", price: 100n } as const;

export function useSignOrder() {
const { widget } = useFluentWidget();
return () =>
widget.signTypedData({
domain: { name: "Marketplace", version: "1", chainId: 20994, verifyingContract },
types: {
Order: [
{ name: "maker", type: "address" },
{ name: "price", type: "uint256" },
],
},
primaryType: "Order",
message: order,
});
}

FluentSigningMode​

type

How a Fluent ID signs: "prompt" shows the widget's Review first (Prompt mode), "silent" signs with none (Silent mode), except that where the App set quickSign.whitelistAddresses, a Batch operation with a call outside it is signed as "prompt". An External wallet always shows its own prompt.

export type FluentSigningMode = "prompt" | "silent";

Literals: prompt, silent.

Auth and errors​

FluentAuthError​

value

A refused Auth token or X link. code is a FluentAuthErrorCode; status is the service's HTTP status when the service refused it.

export class FluentAuthError extends Error {
constructor(
readonly code: FluentAuthErrorCode,
message: string,
readonly status?: number,
) { … }
}
MemberTypeOptionalExperimentalDescription
codeFluentAuthErrorCodenono
statusnumberyesno

FluentAuthErrorCode​

type

Why getAuthToken(), linkX(), or widget.signMessage() / widget.signTypedData() was refused (the signatures throw not_connected and root_signer_required). The codes are the SDK's own, named for the product rather than for a vendor or a mechanism: where the service answers with a code of its own, the SDK maps it here before it reaches the App, and a service code the SDK does not name reaches getAuthToken() as service_refused and linkX() as link_failed. Keep a default branch in a switch over them: a later release may add a code.

export type FluentAuthErrorCode =
// the service's codes, as the SDK names them
| "unknown_app"
| "app_not_auth_enabled"
| "origin_not_allowed"
| "origin_missing"
| "bad_request"
| "challenge_unknown"
| "challenge_used"
| "challenge_expired"
| "app_mismatch"
| "invalid_signature"
| "signature_prefix_rejected"
| "address_already_linked"
| "invalid_login_credential"
| "no_signer"
| "invalid_refresh_token"
| "refresh_token_reused"
| "rate_limited"
| "internal"
// client-side
| "not_connected"
| "root_signer_required"
| "login_credential_missing"
| "origin_mismatch"
| "request_failed"
// the service refused with a code this SDK does not name; the message carries that code
| "service_refused"
// client-side, linking an X account (`linkX()`)
| "not_authenticated"
| "user_rejected"
| "linked_to_another_user"
| "link_failed";

Literals: address_already_linked, app_mismatch, app_not_auth_enabled, bad_request, challenge_expired, challenge_unknown, challenge_used, internal, invalid_login_credential, invalid_refresh_token, invalid_signature, link_failed, linked_to_another_user, login_credential_missing, no_signer, not_authenticated, not_connected, origin_mismatch, origin_missing, origin_not_allowed, rate_limited, refresh_token_reused, request_failed, root_signer_required, service_refused, signature_prefix_rejected, unknown_app, user_rejected.

FluentWidgetConfigError​

value

A value in FluentWidgetConfig the widget refuses to start with.

Thrown when <FluentWidget> renders with a config it has not resolved yet: on its first render, and again on any render that passes a new config object. So it surfaces at startup where the integrator is looking rather than at the first send. The message names the option and how to fix it; code, a FluentWidgetConfigErrorCode, is what to branch on.

Example

import { FluentWidgetConfigError } from "@fluent.xyz/connect";

export function WidgetBoundaryFallback({ error }: { error: unknown }) {
// More than one option is required, and the message names the one that is missing.
if (error instanceof FluentWidgetConfigError && error.code === "missing_option") {
return <p>The widget config is missing a required option: {error.message}</p>;
}
throw error;
}
export class FluentWidgetConfigError extends Error {
readonly code: FluentWidgetConfigErrorCode;

constructor(code: FluentWidgetConfigErrorCode, message: string) { … }
}
MemberTypeOptionalExperimentalDescription
codeFluentWidgetConfigErrorCodenono

FluentWidgetConfigErrorCode​

type

Which value in FluentWidgetConfig was refused, for a host that branches rather than reads the message.

export type FluentWidgetConfigErrorCode =
| "invalid_fee_source"
| "invalid_receipt_source"
| "invalid_chain_check_before_sign"
/** A required option is absent or empty. */
| "missing_option"
/** An option holds a value the widget cannot use. */
| "invalid_option"
/** An option an earlier release removed or renamed is still passed. */
| "removed_option";

Literals: invalid_chain_check_before_sign, invalid_fee_source, invalid_option, invalid_receipt_source, missing_option, removed_option.

FluentReviewRejectedError​

value

The person dismissed a Review (transaction or signature) instead of confirming it.

A class with a code, not a message, so a host branches on code and callers that translate errors for another party — the iframe bridge maps it to EIP-1193 4001 — recognise it without parsing prose.

Example

import { FluentReviewRejectedError, type FluentBatchApi } from "@fluent.xyz/connect";

const calls = [{ to: "0x000000000000000000000000000000000000dEaD", value: 1n, data: "0x" }] as const;

export async function send(widget: FluentBatchApi) {
try {
await widget.createBatchOp({ calls }).execute();
} catch (error) {
if (error instanceof FluentReviewRejectedError) return; // the person said no
throw error;
}
}
export class FluentReviewRejectedError extends Error {
readonly code: FluentReviewRejectedErrorCode;

constructor(readonly review: "transaction" | "signature") { … }
}
MemberTypeOptionalExperimentalDescription
codeFluentReviewRejectedErrorCodenono
review"transaction" | "signature"nono

FluentReviewRejectedErrorCode​

type

Which Review the person dismissed: a Batch operation's or a signature's.

export type FluentReviewRejectedErrorCode =
| "transaction_review_rejected"
| "signature_review_rejected";

Literals: signature_review_rejected, transaction_review_rejected.

FluentSponsorshipFallbackError​

value

Both the sponsored submission and the fallback, the account paying its own gas, failed. code (equal to sponsorshipReason) says why Sponsorship was refused: no_token, denied, unauthorized or unreachable, never not_requested. fallbackError is what the own-gas submission failed with, and cause the sponsored submission's error.

Neither is the error that was thrown. Each is a plain object { name, message, status? }: name is the thrown error's name ("Error" for a thrown value that is not an Error), message the text of the innermost error along its cause chain that has any (an AggregateError gives the texts of its first four errors), and status the first integer status along that chain, such as an HTTP status, present only when there is one; the chain itself is not kept. A thrown value that cannot be read without an exception is summarized as { name: "Error", message: "[unreadable error]" }.

In message, here and in each of the two, a balanced {…} or […] span becomes [body], a scheme:// URL [url], a Bearer token or a JWT [token], a did: id or a UUID [id], an email address [email], and 0x with 40 or 64 hex digits [address] or [hash]. Any other 0x run of 40 or more hex digits becomes [data 0x…] with its first four bytes, and the reason of an Error(string) revert is decoded and redacted in the same way.

export class FluentSponsorshipFallbackError extends Error {
readonly code: FluentSponsorshipReason;
readonly sponsorshipReason: FluentSponsorshipReason;
readonly fallbackError: unknown;

constructor(params: {
sponsorshipReason: FluentSponsorshipReason;
sponsorshipError: unknown;
fallbackError: unknown;
}) { … }
}
MemberTypeOptionalExperimentalDescription
codeFluentSponsorshipReasonnono
sponsorshipReasonFluentSponsorshipReasonnono
fallbackErrorunknownnono

FluentSponsorshipReason​

type

Why an operation was not sponsored, and the code of a FluentSponsorshipFallbackError. not_requested is the only one that is not a failure: the caller asked to pay its own gas, so the sponsorship paymaster was never contacted. For the same reason it is never the error's code, which is thrown only after a sponsored attempt failed: the error carries one of the other four.

export type FluentSponsorshipReason =
| "no_token"
| "denied"
| "unauthorized"
| "unreachable"
| "not_requested";

Literals: denied, no_token, not_requested, unauthorized, unreachable.

FluentGasPaymentError​

value

A Gas token payment the widget could not prepare. Thrown by execute() of a Fluent ID's Batch operation paid in a Gas token, before any signature is requested and before anything is sent, so the operation can be tried again as it is. code, a FluentGasPaymentErrorCode, identifies the failure; cause holds the error of the failed request when there was one.

export class FluentGasPaymentError extends Error {
constructor(
readonly code: FluentGasPaymentErrorCode,
message: string,
options?: { cause?: unknown },
) { … }
}
MemberTypeOptionalExperimentalDescription
codeFluentGasPaymentErrorCodenono

FluentGasPaymentErrorCode​

type

Why a FluentGasPaymentError was thrown. paymaster_unavailable: the request for the address of the paymaster that charges Gas tokens on the operation's Network failed, or its answer held no address, so the widget has no spender to approve the Gas token to. Keep a default branch in a switch over it: a later release may add a code.

export type FluentGasPaymentErrorCode = "paymaster_unavailable";

Literals: paymaster_unavailable.

useLinkX​

value

Linking X as a component sees it: the call, and what the last one did.

redirecting is the status a page is unlikely to render for long — it is set just before the browser leaves for X — and linked after a return is what the mount-time re-entry produces without the component asking for anything.

Throws From linkX, as the render context's linkX() does: a FluentAuthError whose code is today not_authenticated, user_rejected, linked_to_another_user, bad_request or link_failed (keep a default branch); the same error is kept in error.

Example

import { useLinkX } from "@fluent.xyz/connect";

export function LinkXButton() {
const { linkX, status, x, error } = useLinkX();
if (x) return <span>Linked as @{x.handle}</span>;
return (
<button onClick={() => void linkX().catch(() => undefined)} disabled={status === "pending"}>
{error ? `Retry (${error.code})` : "Link X"}
</button>
);
}
export function useLinkX(): {
linkX: () => Promise<FluentLinkXResult>;
status: FluentLinkXStatus;
x: FluentXAccount | null;
error: FluentAuthError | null;
} { … }

FluentLinkXStatus​

type

Where useLinkX()'s last link stands: idle before any, pending while one runs, redirecting just before the browser leaves for X, linked once the Linked X account is there, error when the last one was refused.

export type FluentLinkXStatus = "idle" | "pending" | "redirecting" | "linked" | "error";

Literals: error, idle, linked, pending, redirecting.

FluentLinkXResult​

type

What a linkX() resolves: linked with the Linked X account, or redirecting while the page leaves for X.

export type FluentLinkXResult =
| { status: "linked"; x: FluentXAccount }
/** The page is leaving for X. Call linkX() again after the return; useLinkX() does it on mount. */
| { status: "redirecting" };

FluentXAccount​

type

One X account as the Fluent auth service reports it.

export type FluentXAccount = {
id: string;
handle: string;
avatarUrl: string;
};
MemberTypeOptionalExperimentalDescription
idstringnono
handlestringnono
avatarUrlstringnono

Iframe​

useFluentIframeBridge​

value

Lets a page embedded in iframeRef use the account signed in here, over the JSON-RPC postMessage protocol @ledgerhq/iframe-provider speaks. The bridge lives while the component is mounted; the bridge states the chain and account before its first reply, and sign-in and sign-out reach the iframe as accountsChanged.

Parameter iframeRef The <iframe> the embedded page runs in; read on every message, so an element swapped in after mount is still answered.

Parameter options allowedOrigin, the one origin whose messages are answered.

Throws Error, from the effect that builds the bridge (so to the nearest error boundary), when allowedOrigin is not an http: or https: origin: "*" and a bare host are refused.

Example

import { useFluentIframeBridge } from "@fluent.xyz/connect";
import { useRef } from "react";

export function Marketplace() {
const iframeRef = useRef<HTMLIFrameElement>(null);
useFluentIframeBridge(iframeRef, { allowedOrigin: "https://market.example" });
return <iframe ref={iframeRef} src="https://market.example/collection/cards" />;
}
export function useFluentIframeBridge(
iframeRef: RefObject<FluentIframeElement | null>,
{ allowedOrigin }: UseFluentIframeBridgeOptions,
): void { … }

UseFluentIframeBridgeOptions​

type

The options of useFluentIframeBridge.

export type UseFluentIframeBridgeOptions = {
allowedOrigin: string;
};
MemberTypeOptionalExperimentalDescription
allowedOriginstringnonoThe exact origin the embedded page is served from. Messages from any other origin are dropped without a reply: this is the only origin check in the exchange.

FluentIframeElement​

type

The iframe element, reduced to the window the bridge replies to. Read on every message, so an element swapped in later is picked up without rebuilding the bridge.

export type FluentIframeElement = {
contentWindow: { postMessage: (message: unknown, targetOrigin: string) => void } | null;
};
MemberTypeOptionalExperimentalDescription
contentWindow{ postMessage: (message: unknown, targetOrigin: string) => void } | nullnono
contentWindow.postMessage(message: unknown, targetOrigin: string) => voidnono

Chains and tokens​

fluentMainnet​

value

Fluent mainnet (chain id 25363)

export const fluentMainnet: Chain;

fluentTestnet​

value

Fluent public testnet (chain id 20994)

export const fluentTestnet: Chain;

getFluentChainByChainId​

value

Looks up a Fluent chain by its EVM chain id, among the chains the registry knows: Fluent testnet (20994) and Fluent mainnet (25363).

Parameter chainId The EVM chain id, as a wallet or eth_chainId reports it.

Returns The chain's FluentChainDefinition, or undefined for any other chain id, an L1's included.

Example

import { getFluentChainByChainId } from "@fluent.xyz/connect";

export const explorer = getFluentChainByChainId(20994)?.blockExplorers.default.url;
export function getFluentChainByChainId(
chainId: number,
): FluentChainDefinition | undefined { … }

getFluentChainForNetwork​

value

The viem chain of a Network, for a host's own chain config.

Example

import { getFluentChainForNetwork } from "@fluent.xyz/connect";

export const chain = getFluentChainForNetwork("mainnet"); // chain.id === 25363
export function getFluentChainForNetwork(network: FluentWidgetNetwork): Chain { … }

FluentChainDefinition​

type

One Fluent chain as the registry describes it: its ids, native currency, RPC and explorer endpoints, its L1 parent and its contracts. Declared rather than inferred from fluentChainSchema, so its shape is what this file says; chainDefinitionMatchesSchema below fails the build if the two drift apart.

export type FluentChainDefinition = {
id: string;
name: string;
chainId: number;
testnet: boolean;
nativeCurrency: { name: string; symbol: string; decimals: number };
rpcUrls: { default: { http: string[]; webSocket?: string[] } };
blockExplorers: { default: { name: string; url: string } };
infoURL: string;
faucets?: string[];
parent?: { type: "L2"; chainId: number; bridgeUrl: string };
contracts?: {
fluentBridge?: { address: string; implementation?: string };
nativeGateway?: { address: string; implementation?: string };
erc20Gateway?: { address: string; implementation?: string };
wethGateway?: { address: string; implementation?: string };
universalTokenFactory?: { address: string; implementation?: string };
paymentGateway?: { address: string; implementation?: string };
peggedTokenPrecompile?: { address: string };
};
};
MemberTypeOptionalExperimentalDescription
idstringnonoThe registry's own id: "fluent-testnet", "fluent-mainnet".
namestringnono
chainIdnumbernono
testnetbooleannono
nativeCurrency{ name: string; symbol: string; decimals: number }nono
nativeCurrency.namestringnono
nativeCurrency.symbolstringnono
nativeCurrency.decimalsnumbernono
rpcUrls{ default: { http: string[]; webSocket?: string[] } }nono
rpcUrls.default{ http: string[]; webSocket?: string[] }nono
rpcUrls.default.httpstring[]nono
rpcUrls.default.webSocketstring[]yesno
blockExplorers{ default: { name: string; url: string } }nono
blockExplorers.default{ name: string; url: string }nono
blockExplorers.default.namestringnono
blockExplorers.default.urlstringnono
infoURLstringnono
faucetsstring[]yesno
parent{ type: "L2"; chainId: number; bridgeUrl: string }yesnoThe L1 the chain settles to, and where its bridge is.
parent.type"L2"nono
parent.chainIdnumbernono
parent.bridgeUrlstringnono
contracts{ fluentBridge?: { address: string; implementation?: string }; nativeGateway?: { address: string; implementation?: string }; erc20Gateway?: { address: string; implementation?: string }; wethGateway?: { address: string; implementation?: string }; universalTokenFactory?: { address: string; implementation?: string }; paymentGateway?: { address: string; implementation?: string }; peggedTokenPrecompile?: { address: string }; }yesnoAddresses of the chain's system contracts, each with its implementation when proxied.
contracts.fluentBridge{ address: string; implementation?: string }yesno
contracts.fluentBridge.addressstringnono
contracts.fluentBridge.implementationstringyesno
contracts.nativeGateway{ address: string; implementation?: string }yesno
contracts.nativeGateway.addressstringnono
contracts.nativeGateway.implementationstringyesno
contracts.erc20Gateway{ address: string; implementation?: string }yesno
contracts.erc20Gateway.addressstringnono
contracts.erc20Gateway.implementationstringyesno
contracts.wethGateway{ address: string; implementation?: string }yesno
contracts.wethGateway.addressstringnono
contracts.wethGateway.implementationstringyesno
contracts.universalTokenFactory{ address: string; implementation?: string }yesno
contracts.universalTokenFactory.addressstringnono
contracts.universalTokenFactory.implementationstringyesno
contracts.paymentGateway{ address: string; implementation?: string }yesno
contracts.paymentGateway.addressstringnono
contracts.paymentGateway.implementationstringyesno
contracts.peggedTokenPrecompile{ address: string }yesno
contracts.peggedTokenPrecompile.addressstringnono

FluentTokenDefinition​

type

A token a host adds to the list the widget shows: an Integrator token. It describes the token and grants nothing — whether a token can pay for gas is a property only Default tokens have, so the widget would strip any such field from a host's token anyway.

export type FluentTokenDefinition = {
chainId: number;
address?: Address;
name: string;
symbol: string;
decimals: number;
logoURI?: string;
};
MemberTypeOptionalExperimentalDescription
chainIdnumbernonoThe chain the token lives on. The widget does not filter by it: pass the Network's tokens.
addressAddressyesnoThe token's contract address; absent for the chain's native token.
namestringnono
symbolstringnono
decimalsnumbernono
logoURIstringyesnoAn image for the token, a URL or a data URI.

Experimental​

FluentPrivyAdapter​

type · experimental

Privy runtime supplied by a host using a native SDK. Its Provider owns session restoration, login and wallet RPC; web hosts keep the normal React SDK. Keep one stable adapter per runtime. Changing its Provider remounts descendants so React hooks from different SDKs never share a component lifetime.

Experimental. May change or be removed in a minor release: the one provider seam of the widget, kept for hosts that run a native login SDK.

export type FluentPrivyAdapter = Pick<
typeof web,
| "PrivyProvider"
| "Captcha"
| "usePrivy"
| "useSignMessage"
| "useSignTypedData"
| "useWallets"
| "useIdentityToken"
| "useUser"
| "useLinkAccount"
| "useLoginWithSiwe"
| "useModalStatus"
| "useCreateWallet"
| "useLoginWithEmail"
| "useLoginWithOAuth"
| "useLoginWithPasskey"
| "useLinkWithPasskey"
>;

FluentExternalWalletState​

type · experimental

An External wallet as the widget sees it: whether one is connected, its address, chain and client, and how to open, connect, switch and disconnect it. Its inline choices are FluentWalletChoices.

Experimental. May change or be removed in a minor release: a host that brings its own wallet connection is not a 0.6.0 commitment.

export type FluentExternalWalletState = {
configured: boolean;
connected: boolean;
address?: string;
chainId?: number;
walletClient?: WalletClient;
reconnecting?: boolean;
choices?: readonly FluentWalletChoice[];
connectChoice?: (id: string) => Promise<void>;
open: () => void;
disconnect: () => void;
switchChain: (chainId: number) => Promise<void>;
};
MemberTypeOptionalExperimentalDescription
configuredbooleannono
connectedbooleannono
addressstringyesno
chainIdnumberyesno
walletClientWalletClientyesno
reconnectingbooleanyesnoTrue while the wallet is being restored from a previous visit and neither connected nor "no wallet" is known yet. Optional so hosts supplying their own wallet state keep working; omitting it just means the widget reports "disconnected" slightly earlier during a reload.
choicesreadonly FluentWalletChoice[]yesnoOptional inline choices; hosts without them keep their existing open() picker.
connectChoice(id: string) => Promise<void>yesno
open() => voidnono
disconnect() => voidnono
switchChain(chainId: number) => Promise<void>nono

FluentWalletChoice​

type · experimental

One wallet the connect dialog offers inline, as a host's FluentExternalWalletState lists it.

Experimental. May change or be removed in a minor release: a host that brings its own wallet connection is not a 0.6.0 commitment.

export type FluentWalletChoice = {
id: string;
name: string;
icon?: string;
handoff?: boolean;
};
MemberTypeOptionalExperimentalDescription
idstringnono
namestringnono
iconstringyesno
handoffbooleanyesnoThis connector opens an external wallet/QR surface.

FluentWidgetExecution​

type · experimental

FluentWidgetConfig.execution, every field optional.

Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.

export type FluentWidgetExecution = {
feeSource?: FluentWidgetExecutionSource;
receiptSource?: FluentWidgetExecutionSource;
chainCheckBeforeSign?: boolean;
};
MemberTypeOptionalExperimentalDescription
feeSourceFluentWidgetExecutionSourceyesyesWhere the fees of a user operation come from, a FluentWidgetExecutionSource. chain reads the widget chain's eth_gasPrice and caps maxFeePerGas 10 % above it; bundler takes the bundler's own gas price quote unchanged. Defaults to chain. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.
receiptSourceFluentWidgetExecutionSourceyesyesWhere inclusion is read from, a FluentWidgetExecutionSource. chain polls the EntryPoint's UserOperationEvent with eth_getLogs; bundler polls eth_getUserOperationReceipt. Defaults to chain. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.
chainCheckBeforeSignbooleanyesyesWhether the Signer's chain is read before every signature. The widget gives the Signer's provider its own chain as the only one, and the smart account checks the chain once when it is created, so the repeat costs a provider round trip per signature and switches nothing. Defaults to false. Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.

FluentWidgetExecutionSource​

type · experimental

Where a Fluent ID's send reads a value it could ask either side for: the widget chain's own RPC, or the bundler.

Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.

export type FluentWidgetExecutionSource = "chain" | "bundler";

Literals: bundler, chain.

FluentUserOperationGas​

type · experimental

Gas policy for a Fluent ID batch. Estimation is enabled unless skipEstimation is true. Skipping requires account limits. Omit both paymaster limits to use provider-authorized paymaster gas and calldata overhead within preVerificationGas; supply both to fix all gas. The fixed limits are a FluentFixedAccountGas and a FluentFixedPaymasterGas.

Experimental. May change or be removed in a minor release while the gas policy is measured; omitting it, which lets the widget estimate, is the recommended setting.

export type FluentUserOperationGas =
| (FluentFixedAccountGas & FluentFixedPaymasterGas & {
/** Skip the account gas hook and bundler gas estimation. Defaults to false. */
skipEstimation: true;
callGasBuffer?: never;
})
| { skipEstimation?: false; callGasLimit: bigint; callGasBuffer?: never }
| {
skipEstimation?: false;
callGasLimit?: never;
callGasBuffer: {
/** Extra percent of the bundler estimate, rounded up. Must be a nonnegative safe integer. */
percentage?: number;
/** Extra gas added after the percentage buffer. Must be nonnegative. */
fixed?: bigint;
};
};

FluentUserOperationGasError​

value · experimental

An invalid skip-estimation policy or paymaster authorization for different gas limits. code, a FluentUserOperationGasErrorCode, identifies the failure; message names the option or gas field to correct.

Experimental. May change or be removed in a minor release while the gas policy is measured; omitting it, which lets the widget estimate, is the recommended setting.

export class FluentUserOperationGasError extends Error {
constructor(readonly code: FluentUserOperationGasErrorCode, message: string) { … }
}
MemberTypeOptionalExperimentalDescription
codeFluentUserOperationGasErrorCodenono

FluentUserOperationGasErrorCode​

type · experimental

Reasons explicit gas preparation can fail before signing.

Experimental. May change or be removed in a minor release while the gas policy is measured; omitting it, which lets the widget estimate, is the recommended setting.

export type FluentUserOperationGasErrorCode = "invalid_policy" | "paymaster_gas_mismatch";

Literals: invalid_policy, paymaster_gas_mismatch.

FluentFixedAccountGas​

type · experimental

The account's own gas limits, all three fixed, for a FluentUserOperationGas that skips estimation.

Experimental. May change or be removed in a minor release while the gas policy is measured; omitting it, which lets the widget estimate, is the recommended setting.

export type FluentFixedAccountGas = {
callGasLimit: bigint;
verificationGasLimit: bigint;
preVerificationGas: bigint;
};
MemberTypeOptionalExperimentalDescription
callGasLimitbigintnonoWhole-batch execution budget, including any Gas token approval. Positive uint128.
verificationGasLimitbigintnonoAccount validation and deployment budget. Positive uint128.
preVerificationGasbigintnonoCalldata overhead, or its ceiling with provider-owned paymaster gas. Positive uint256.

FluentFixedPaymasterGas​

type · experimental

The paymaster's gas limits for a FluentUserOperationGas that skips estimation: both, or neither to use the limits the paymaster authorizes.

Experimental. May change or be removed in a minor release while the gas policy is measured; omitting it, which lets the widget estimate, is the recommended setting.

export type FluentFixedPaymasterGas = {
paymasterVerificationGasLimit?: bigint;
paymasterPostOpGasLimit?: bigint;
};
MemberTypeOptionalExperimentalDescription
paymasterVerificationGasLimitbigintyesnoFixed validation budget, a positive uint128. Omit both paymaster limits to use its answer.
paymasterPostOpGasLimitbigintyesnoFixed post-operation budget, a nonnegative uint128. Supplied together with validation.

FluentExecuteTiming​

type · experimental

Where one send's milliseconds went, stage by stage.

Two clocks, and the difference decides how these numbers may be read. prepareMs, signMs, broadcastMs, retryMs and totalMs are direct monotonic measurements on the client (performance.now()). inclusionMs and detectMs are signed estimates across the client's clock and the chain's: block.timestamp has one-second resolution and no relation to the browser's clock, so either may legitimately be negative when it is small, and neither is clamped — a clamp would turn a known skew into a silent zero. instants keeps the inputs, so the subtraction can be redone by whoever doubts it.

totalMs is measured, not summed: it runs from the start of the first submission to the instant the receipt was in hand, and excludes both the smart-account and authentication setup before the send and the block read after it. It does not equal the sum of the stages and must not be displayed as if it did.

Collected for every send, whatever debugLogging is set to — the numbers are the answer to "where did the four seconds go", which is a question a host asks in production. A failure inside the measuring code costs the stages it could not establish, never the send.

Experimental. May change or be removed in a minor release while the numbers are measured.

export type FluentExecuteTiming = {
prepareMs?: number;
signMs?: number;
broadcastMs?: number;
inclusionMs?: number;
detectMs?: number;
retryMs: number;
totalMs: number;
instants: FluentExecuteTimingInstants;
};
MemberTypeOptionalExperimentalDescription
prepareMsnumberyesnoBuilding the UserOperation — nonce, gas estimate, paymaster — up to the signature request. Absent when the clock could not be read at that boundary: every stage here is absent rather than approximated, because a stage silently widened to cover its neighbours is worse than a stage the display can mark as unmeasured.
signMsnumberyesnoProducing the signature. Absent when the settled attempt never got that far.
broadcastMsnumberyesnoHanding the signed operation to the bundler. Absent when signing never finished.
inclusionMsnumberyesnoBundler acceptance to the including block's timestamp: bundling plus block time. Signed estimate; absent when the including block could not be read.
detectMsnumberyesnoThe including block's timestamp to the instant this client held the receipt: how long the inclusion took to notice. Signed estimate; absent when the including block could not be read.
retryMsnumbernonoTime spent on submissions that did not settle — a rejected sponsorship bearer, a refused sponsorship falling back to own gas — and on the token exchange between them. It belongs to no stage, is counted inside totalMs, and is 0 for a send that submitted once.
totalMsnumbernonoFirst submission to receipt in hand. Measured directly; never a sum of the stages.
instantsFluentExecuteTimingInstantsnonoThe raw instants the signed estimates above were computed from.

FluentExecuteTimingInstants​

type · experimental

Absolute instants behind FluentExecuteTiming, kept so its signed estimates can be re-checked rather than believed. Epoch milliseconds (Date.now()), because the only other clock in the arithmetic — the including block's timestamp — is a wall clock too.

Experimental. May change or be removed in a minor release while the numbers are measured.

export type FluentExecuteTimingInstants = {
submittedAtEpochMs: number;
broadcastedAtEpochMs: number;
receivedAtEpochMs: number;
blockTimestampEpochMs?: number;
attempts: number;
};
MemberTypeOptionalExperimentalDescription
submittedAtEpochMsnumbernonoWhen the first submission attempt of this send began.
broadcastedAtEpochMsnumbernonoWhen the bundler accepted the operation that settled.
receivedAtEpochMsnumbernonoWhen that operation's receipt was in hand.
blockTimestampEpochMsnumberyesnoThe including block's timestamp, in milliseconds. Absent exactly when inclusionMs and detectMs are absent: the block could not be read.
attemptsnumbernonoHow many operations were submitted; more than one means a retry or an own-gas fallback.

FluentGasPaymentEthRates​

type · experimental

What one unit of each Gas token is worth in ETH, as a decimal string, keyed by its FluentGasTokenSymbol.

Experimental. May change or be removed in a minor release while the defaults are measured; the defaults are the recommended setting.

export type FluentGasPaymentEthRates = Partial<Record<FluentGasTokenSymbol, string>>;