npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liberfi.io/ui-trade

v0.1.26

Published

Trade hooks for Liberfi React SDK

Readme

@liberfi.io/ui-trade

Trade hooks and widgets for the Liberfi React SDK. This package provides chain-agnostic swap logic and ready-to-use trade form components that work across Solana, Ethereum, and BSC.

The package is organized in three layers:

  • Hooks (useSwap, useSwapRoutePolling, useTxConfirmation) — pure-logic building blocks with no UI side-effects, designed for IoC.
  • Swap Widget (SwapWidget / useSwapScript / SwapUI / SwapPreviewModal) — a full swap form with preview-before-confirm flow, following the Script/Widget/UI three-layer architecture, composable at any level.
  • Instant Trade (InstantTradeWidget / InstantTradeProvider / MultiChainPresetFormWidget / PresetFormWidget) — a configurable quick-trade form with buy/sell tabs, preset management, customizable quick-amount buttons, and trade settings (slippage, priority fee, tip fee, anti-MEV). Includes list-optimised variants (InstantTradeSwapProvider / InstantTradeListButtonWidget) that share a single set of context subscriptions across N buttons.
  • State atoms (presetAtomFamily / instantTradeAmountAtomFamily) — Jotai-based persistent state for trade presets and amount/preset selection, storage-backend agnostic via atomWithStorage.

Design Philosophy

  • IoC (Inversion of Control) — Side-effects (toast, analytics, navigation) are injected via callbacks. Hooks never hardcode any feedback mechanism.
  • Script/Widget/UI architecture — The swap form follows the monorepo's three-layer pattern: useSwapScript (data + state), SwapWidget (orchestration), SwapUI (presentation). Consumers can use the widget directly or compose Script + custom UI.
  • Chain-agnostic — Works across Solana (Jupiter) and EVM chains (KyberSwap). Chain-specific differences (dex selection, tx format) are handled by the underlying @liberfi.io/client and WalletAdapter abstractions.
  • UI via @liberfi.io/ui — Uses the shared UI library (Button, Input, Avatar, Modal, etc.) for consistent styling across the monorepo. No direct @heroui/react dependency.
  • Layered architecture — Builds on @liberfi.io/react (data layer), @liberfi.io/wallet-connector (signing layer), and @liberfi.io/ui (presentation layer) without duplicating their logic.

Installation

pnpm add @liberfi.io/ui-trade

Peer dependencies the consumer must provide:

  • react (>=18)
  • react-dom (>=18)
  • jotai (>=2.15.1) — used for persistent preset state via atomWithStorage
  • @liberfi.io/react (provides DexClientProvider and useDexClient)
  • @liberfi.io/ui (provides UI components: Button, Input, Modal, Avatar, etc.)
  • @liberfi.io/ui-chain-select (provides ChainSelectMobileUI for MultiChainPresetFormWidget)
  • @liberfi.io/wallet-connector (provides WalletAdapter type)

API Reference

Hooks

useSwap(options?: UseSwapOptions)

Orchestrates the full swap flow: route -> sign -> send.

Parameters:

| Name | Type | Description | | --------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------- | | options.onSubmitted | (result: SwapResult) => void | Called when the transaction is successfully submitted on-chain. | | options.onError | (error: Error, phase: SwapPhase) => void | Called when an error occurs. phase indicates where it failed: "route", "sign", or "send". |

Returns: { swap, isSwapping }

| Name | Type | Description | | ------------ | ------------------------------------------- | ---------------------------------------------------------------------------- | | swap | (input: SwapInput) => Promise<SwapResult> | Executes the swap. Resolves with SwapResult on success, throws on failure. | | isSwapping | boolean | Whether a swap is currently in progress. |

useSwapRoutePolling(params, options?)

Polls for swap route quotes at a configurable interval. Built on useSwapRouteQuery (TanStack Query).

Parameters:

| Name | Type | Description | | ------------------ | ------------------------- | ---------------------------------------------------------------------- | | params | Partial<API.SwapParams> | Route parameters. Polling starts when all required fields are present. | | options.interval | number | Polling interval in ms. Defaults to 12000. | | options.paused | boolean | Pause polling (e.g. during swap execution). | | options.onError | (error: Error) => void | Called when a route fetch fails. |

Returns: { route, isRouting, error }

| Name | Type | Description | | ----------- | ---------------------------- | --------------------------------- | | route | API.SwapRoute \| undefined | Current route quote. | | isRouting | boolean | Whether a route is being fetched. | | error | Error \| null | Latest route fetch error. |

useTxConfirmation(options?: UseTxConfirmationOptions)

Tracks transaction confirmation via backend SSE. Designed to compose with useSwap.

Parameters:

| Name | Type | Description | | --------------------- | ---------------------------------------- | -------------------------------------------------------- | | options.onConfirmed | (txHash: string) => void | Called when a tracked transaction is confirmed on-chain. | | options.onFailed | (txHash: string, error: Error) => void | Called when confirmation fails or times out. | | options.timeout | number | SSE timeout in milliseconds. Defaults to 60000. |

Returns: { track, clear, clearAll, transactions }

| Name | Type | Description | | -------------- | ---------------------------------------- | --------------------------------------------------- | | track | (chain: Chain, txHash: string) => void | Start tracking a transaction's confirmation. | | clear | (txHash: string) => void | Remove a single transaction from tracking. | | clearAll | () => void | Remove all tracked transactions. | | transactions | Map<string, TrackedTx> | All tracked transactions with their current status. |

Components (Swap Widget)

The swap widget follows the Script / Widget / UI three-layer architecture.

SwapWidget

Plug-and-play swap form. Calls useSwapScript internally and renders SwapUI with SwapPreviewModal. Clicking the swap button opens a preview modal; confirming in the modal executes the swap.

<SwapWidget
  chain={Chain.SOLANA}
  from="So11111111111111111111111111111111111111112"
  to="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
  onComplete={(result) => console.log(result)}
  className="my-swap"
/>

Props (SwapWidgetProps):

| Name | Type | Description | | ------------- | --------------------------------------------------------- | ----------------------------- | | chain | Chain | Target chain. | | from? | string | Initial from-token address. | | to? | string | Initial to-token address. | | onComplete? | (result: { success: boolean; txHash?: string }) => void | Called when swap completes. | | className? | string | External style customization. |

useSwapScript(params: UseSwapScriptParams)

Script layer hook. Encapsulates all data fetching, state management, and swap execution. No JSX, no UI imports. Use this to build a custom swap UI.

Parameters (UseSwapScriptParams):

| Name | Type | Description | | ------------- | --------------------------------------------------------- | --------------------------- | | chain | Chain | Target chain. | | from? | string | Initial from-token address. | | to? | string | Initial to-token address. | | onComplete? | (result: { success: boolean; txHash?: string }) => void | Called when swap completes. |

Returns (UseSwapScriptResult):

| Name | Type | Description | | --------------------- | ---------------------------------- | -------------------------------------------- | | fromTokenAddress | string | Currently selected from-token address. | | toTokenAddress | string | Currently selected to-token address. | | setFromTokenAddress | (addr: string) => void | Update from-token address. | | setToTokenAddress | (addr: string) => void | Update to-token address. | | fromToken | Token \| null | From-token metadata. | | toToken | Token \| null | To-token metadata. | | fromBalance | Portfolio \| null | User's from-token balance. | | toBalance | Portfolio \| null | User's to-token balance. | | amount | string \| undefined | Human-readable amount. | | setAmount | (v: string \| undefined) => void | Update amount. | | setHalfAmount | () => void | Set amount to half of from-token balance. | | setMaxAmount | () => void | Set amount to full from-token balance. | | amountInDecimals | string \| undefined | Amount in smallest unit. | | amountInUsd | string \| undefined | Amount in USD. | | outputAmount | string \| undefined | Formatted output amount from route. | | outputAmountInUsd | string \| undefined | Output amount in USD. | | route | API.SwapRoute \| undefined | Current route quote. | | isRouting | boolean | Whether a route is being fetched. | | routeError | Error \| null | Route fetch error. | | swap | () => Promise<void> | Execute the swap. | | isSwapping | boolean | Whether a swap is in progress. | | txStatus | TxConfirmationStatus | Confirmation status of the last transaction. | | isLoading | boolean | Whether initial data is loading. |

SwapUI

Pure presentational component for the swap form. Renders the From/To token inputs with token selector buttons, balance display, Half/Max shortcuts, and a preview button. Styled with @liberfi.io/ui components and Tailwind CSS.

Props (SwapUIProps):

| Name | Type | Description | | ------------------- | ---------------------------------- | --------------------------------- | | fromToken | Token \| null | From-token metadata. | | toToken | Token \| null | To-token metadata. | | fromBalance | Portfolio \| null | User's from-token balance. | | toBalance | Portfolio \| null | User's to-token balance. | | amount | string \| undefined | Human-readable input amount. | | amountInUsd | string \| undefined | Input amount in USD. | | onAmountChange | (v: string \| undefined) => void | Amount change handler. | | onHalfAmount? | () => void | Half balance shortcut handler. | | onMaxAmount? | () => void | Max balance shortcut handler. | | outputAmount | string \| undefined | Formatted output amount. | | outputAmountInUsd | string \| undefined | Output amount in USD. | | onFromTokenSelect | (addr: string) => void | From-token selection handler. | | onToTokenSelect | (addr: string) => void | To-token selection handler. | | route | API.SwapRoute \| undefined | Current swap route quote. | | isRouting | boolean | Whether a route is being fetched. | | routeError | Error \| null | Route error. | | onPreview | () => void | Open preview modal handler. | | isSwapping | boolean | Whether a swap is in progress. | | className? | string | External style customization. |

SwapPreviewModal

Modal component that previews swap details before confirmation. Shows from/to token cards with amounts, an expandable route plan details section, and a confirm button.

Props (SwapPreviewModalProps):

| Name | Type | Description | | ------------------- | ---------------------------- | --------------------------------- | | isOpen | boolean | Whether the modal is open. | | onOpenChange | (isOpen: boolean) => void | Modal open/close handler. | | fromToken | Token \| null | From-token metadata. | | toToken | Token \| null | To-token metadata. | | fromBalance | Portfolio \| null | User's from-token balance. | | inputAmount | string \| undefined | Input amount (human-readable). | | inputAmountInUsd | string \| undefined | Input amount in USD. | | outputAmount | string \| undefined | Output amount (human-readable). | | outputAmountInUsd | string \| undefined | Output amount in USD. | | route | API.SwapRoute \| undefined | Current swap route. | | isRouting | boolean | Whether a route is being fetched. | | routeError | Error \| null | Route error. | | onConfirm | () => void | Confirm swap handler. | | isSwapping | boolean | Whether a swap is in progress. |

Types

SwapInput

interface SwapInput {
  chain: Chain;
  wallet: WalletAdapter;
  input: string; // input token address
  output: string; // output token address
  amount: string; // amount in smallest unit (lamports / wei)
  mode?: SwapMode; // default: EXACT_IN
  slippage?: number; // 0-100, default: 1
  // Solana-specific
  priorityFee?: string;
  tipFee?: string;
  isAntiMev?: boolean;
  // EVM-specific
  permit?: string;
  deadline?: number;
}

SwapResult

interface SwapResult {
  txHash: string;
  extra?: Record<string, unknown>;
}

SwapPhase

type SwapPhase = "route" | "sign" | "send";

UseSwapOptions

interface UseSwapOptions {
  onSubmitted?: (result: SwapResult) => void;
  onError?: (error: Error, phase: SwapPhase) => void;
}

TxConfirmationStatus

type TxConfirmationStatus = "idle" | "pending" | "confirmed" | "failed";

UseTxConfirmationOptions

interface UseTxConfirmationOptions {
  onConfirmed?: (txHash: string) => void;
  onFailed?: (txHash: string, error: Error) => void;
  timeout?: number; // default: 60000
}

UseSwapRoutePollingOptions

interface UseSwapRoutePollingOptions {
  interval?: number; // default: 12000
  paused?: boolean;
  onError?: (error: Error) => void;
}

Constants

version

const version: string; // e.g. "0.1.0"

Usage Examples

SwapWidget (Plug-and-Play)

import { Chain } from "@liberfi.io/types";
import { SwapWidget } from "@liberfi.io/ui-trade";

function TradePage() {
  return (
    <SwapWidget
      chain={Chain.SOLANA}
      from="So11111111111111111111111111111111111111112"
      to="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
      onComplete={(result) => {
        if (result.success) {
          toast.success(`Swap confirmed: ${result.txHash}`);
        } else {
          toast.error("Swap failed");
        }
      }}
    />
  );
}

Custom UI with useSwapScript + SwapPreviewModal

import { Chain } from "@liberfi.io/types";
import { useDisclosure } from "@liberfi.io/ui";
import { useSwapScript, SwapUI, SwapPreviewModal } from "@liberfi.io/ui-trade";

function MyCustomSwapForm() {
  const script = useSwapScript({
    chain: Chain.SOLANA,
    from: "So11111111111111111111111111111111111111112",
    to: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    onComplete: (result) => console.log("Done:", result),
  });

  const { isOpen, onOpen, onOpenChange } = useDisclosure();

  return (
    <>
      <SwapUI
        fromToken={script.fromToken}
        toToken={script.toToken}
        fromBalance={script.fromBalance}
        toBalance={script.toBalance}
        amount={script.amount}
        amountInUsd={script.amountInUsd}
        onAmountChange={script.setAmount}
        onHalfAmount={script.setHalfAmount}
        onMaxAmount={script.setMaxAmount}
        outputAmount={script.outputAmount}
        outputAmountInUsd={script.outputAmountInUsd}
        onFromTokenSelect={script.setFromTokenAddress}
        onToTokenSelect={script.setToTokenAddress}
        route={script.route}
        isRouting={script.isRouting}
        routeError={script.routeError}
        onPreview={onOpen}
        isSwapping={script.isSwapping}
      />
      <SwapPreviewModal
        isOpen={isOpen}
        onOpenChange={onOpenChange}
        fromToken={script.fromToken}
        toToken={script.toToken}
        fromBalance={script.fromBalance}
        inputAmount={script.amount}
        inputAmountInUsd={script.amountInUsd}
        outputAmount={script.outputAmount}
        outputAmountInUsd={script.outputAmountInUsd}
        route={script.route}
        isRouting={script.isRouting}
        routeError={script.routeError}
        onConfirm={script.swap}
        isSwapping={script.isSwapping}
      />
    </>
  );
}

Swap with Confirmation Tracking (Low-Level Hooks)

import { Chain } from "@liberfi.io/types";
import { useSwap, useTxConfirmation } from "@liberfi.io/ui-trade";
import { useWallets } from "@liberfi.io/wallet-connector";

function SwapWithConfirmation() {
  const wallets = useWallets();
  const solWallet = wallets.find((w) => w.chainNamespace === "SOL");

  const { track, transactions } = useTxConfirmation({
    onConfirmed: (txHash) => {
      toast.success(`Transaction confirmed: ${txHash}`);
    },
    onFailed: (txHash, error) => {
      toast.error(`Transaction failed: ${error.message}`);
    },
  });

  const { swap, isSwapping } = useSwap({
    onSubmitted: (result) => {
      toast.info(`Transaction submitted: ${result.txHash}`);
      track(Chain.SOLANA, result.txHash);
    },
    onError: (error, phase) => {
      toast.error(`Swap failed at ${phase}: ${error.message}`);
    },
  });

  const handleSwap = async () => {
    if (!solWallet) return;
    await swap({
      chain: Chain.SOLANA,
      wallet: solWallet,
      input: "So11111111111111111111111111111111111111112",
      output: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      amount: "1000000000",
      slippage: 1,
    });
  };

  const pendingCount = [...transactions.values()].filter(
    (tx) => tx.status === "pending",
  ).length;

  return (
    <div>
      <button onClick={handleSwap} disabled={isSwapping || !solWallet}>
        {isSwapping ? "Swapping..." : "Swap SOL -> USDC"}
      </button>
      {pendingCount > 0 && <span>Confirming {pendingCount} tx...</span>}
    </div>
  );
}

EVM Swap (ETH/BSC)

import { Chain } from "@liberfi.io/types";
import { useSwap, useTxConfirmation } from "@liberfi.io/ui-trade";
import { useWallets } from "@liberfi.io/wallet-connector";

function EvmSwapButton() {
  const wallets = useWallets();
  const evmWallet = wallets.find((w) => w.chainNamespace === "EVM");

  const { track } = useTxConfirmation({
    onConfirmed: (txHash) => console.log("Confirmed:", txHash),
    onFailed: (txHash, err) => console.error("Failed:", txHash, err),
  });

  const { swap, isSwapping } = useSwap({
    onSubmitted: (result) => track(Chain.ETHEREUM, result.txHash),
    onError: (error, phase) => console.error(`Failed at ${phase}:`, error),
  });

  const handleSwap = async () => {
    if (!evmWallet) return;
    await swap({
      chain: Chain.ETHEREUM,
      wallet: evmWallet,
      input: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
      output: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      amount: "1000000000000000000",
      slippage: 1,
    });
  };

  return (
    <button onClick={handleSwap} disabled={isSwapping || !evmWallet}>
      {isSwapping ? "Swapping..." : "Swap ETH -> USDC"}
    </button>
  );
}

Components (Instant Trade)

The instant trade module provides a configurable quick-trade form with preset management and customizable quick-amount buttons.

InstantTradeWidget

Full instant-trade form with buy/sell tabs, amount input, quick buttons, preset management, and trade execution.

<InstantTradeWidget
  chain={Chain.SOLANA}
  tokenAddress="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
  onSwapSubmitted={(result) => console.log(result)}
  onSwapError={(error, phase) => console.error(error)}
/>

Props (InstantTradeWidgetProps):

| Name | Type | Description | | ------------------- | ------------------------------------------ | ----------------------------------------------------- | | chain | Chain | Target chain. | | tokenAddress | string | Address of the token being traded. | | onSwapSubmitted? | (result: SwapResult) => void | Called when a swap is submitted. | | onSwapError? | (error: Error, phase: SwapPhase) => void | Called on swap error. | | settings? | InstantTradeSettings | Controlled settings (omit for localStorage). | | onSettingsChange? | (settings: InstantTradeSettings) => void | Called when settings change. | | headerExtra? | ReactNode | Slot for extra header content (e.g. wallet switcher). | | className? | string | External style customization. |

InstantTradeProvider

Context provider for instant trade state. Use with InstantTradeButton for compact inline trading.

useInstantTradeScript(params)

Script-layer hook for the instant trade form. Must be used inside an InstantTradeProvider. Encapsulates token queries, balance queries, form state, and swap execution.

AmountPresetInputUI

Pure presentational compact amount input with token icon, lightning icon, and 3 preset buttons (P1/P2/P3) with tooltips. No context dependency — all data is received via props.

Props (AmountPresetInputUIProps):

| Name | Type | Description | | ----------------- | -------------------------------- | ------------------------------------------------------------------- | | token | PredefinedToken | Payment token (provides symbol, decimals, and icon). | | chain | Chain | Target chain — used by preset tooltips to show chain-specific info. | | amount? | number | Current amount value. | | onAmountChange | (amount?: number) => void | Called when the amount changes. | | preset? | number | Currently selected preset index (0–2). Defaults to 0. | | onPresetChange? | (preset: number) => void | Called when the user selects a different preset. | | onPresetClick? | (preset: number) => void | Called when the user clicks the already-selected preset. | | presetValues? | TradePresetValues[] | Preset configurations for tooltip display. Falls back to defaults. | | size? | "sm" \| "lg" | Controls overall component size. Defaults to "sm". | | variant? | "default" \| "bordered" | Visual variant. | | radius? | "full" \| "lg" \| "md" \| "sm" | Border radius. | | fullWidth? | boolean | Whether the input takes full width. | | className? | string | External style customization. |

AmountPresetInputWidget

Atom-backed widget that wraps AmountPresetInputUI with atomWithStorage persistence. Amount and preset selection are persisted per id + chain + token.address so different payment tokens and chains have separate saved values.

Props (AmountPresetInputWidgetProps):

| Name | Type | Description | | ------------------- | -------------------------------- | ---------------------------------------------------------------------------- | | id | string | Business identifier for storage key (e.g. "token-detail"). | | chain | Chain | Target chain. | | token | PredefinedToken | Payment token (provides symbol, decimals, address for storage key). | | storageKeyPrefix? | string | Storage key prefix. Must match PresetFormWidget. Defaults to "liberfi.". | | onAmountChange? | (amount?: number) => void | Notification callback (does not control state). | | onPresetChange? | (preset: number) => void | Notification callback (does not control state). | | onPresetClick? | (preset: number) => void | Called when the user clicks the already-selected preset. | | size? | "sm" \| "lg" | Controls overall component size. Defaults to "sm". | | variant? | "default" \| "bordered" | Visual variant. | | radius? | "full" \| "lg" \| "md" \| "sm" | Border radius. | | fullWidth? | boolean | Whether the input takes full width. | | className? | string | External style customization. |

InstantTradeButton

Trade execution button that reads state from InstantTradeProvider. Handles wallet resolution, amount conversion, and swap execution.

InstantTradeSwapProvider

Context provider that calls useSwap, useConnectedWallet, and useAuthCallback once and exposes a single stable swap function to descendant InstantTradeListButtonWidget components. Use this when rendering many trade buttons in a list to avoid N redundant context subscriptions.

<InstantTradeSwapProvider
  chain={Chain.SOLANA}
  onSwapSubmitted={(r) => toast.success(`TX: ${r.txHash}`)}
  onSwapError={(e, phase) => toast.error(`${phase}: ${e.message}`)}
>
  {tokens.map((t) => (
    <InstantTradeListButtonWidget key={t.address} ... />
  ))}
</InstantTradeSwapProvider>

Props (InstantTradeSwapProviderProps):

| Name | Type | Description | | ------------------ | ------------------------------------------ | --------------------------------------------- | | chain | Chain | Target chain. | | onSwapSubmitted? | (result: SwapResult) => void | Called when a swap transaction is submitted. | | onSwapError? | (error: Error, phase: SwapPhase) => void | Called when a swap error occurs at any phase. | | children | ReactNode | Child components (buttons, inputs, etc.). |

InstantTradeListButtonWidget

Memoized trade button optimised for list rendering. Visually identical to InstantTradeButtonWidget, but reads the swap function from the nearest InstantTradeSwapProvider instead of creating its own useSwap / useConnectedWallet / useAuthCallback subscriptions. Each button only subscribes to its own Jotai atoms (amount + preset) and the stable context function, eliminating unnecessary re-renders when wallet or auth state changes.

Must be used inside an InstantTradeSwapProvider.

Props (InstantTradeListButtonWidgetProps):

| Name | Type | Description | | ------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | id | string | Business identifier for atom key (must match AmountPresetInputWidget). | | chain | Chain | Target chain. | | token | PredefinedToken | Payment token (decimals, symbol, address for atom key and swap input). | | output | string | Output token address. | | storageKeyPrefix? | string | Storage key prefix. Must match AmountPresetInputWidget. Default "liberfi.". | | size? | "xs" \| "sm" \| "md" \| "lg" | Button size. Defaults to "sm". | | radius? | "full" \| "lg" \| "md" \| "sm" \| "none" | Button border radius. | | color? | "primary" \| "secondary" \| "success" \| "warning" \| "danger" \| "default" | Button color. Defaults to "primary". | | className? | string | External style customization. |

useInstantTradeListButtonScript(params)

Lightweight script hook for the list button. Same return type as useInstantTradeButtonScript but without useSwap / useConnectedWallet / useAuthCallback subscriptions. Must be called inside an InstantTradeSwapProvider.

useInstantTradeSwap()

Returns the auth-guarded swap function from the nearest InstantTradeSwapProvider. Throws if called outside the provider. The returned function has type InstantTradeSwapFn.

MultiChainPresetFormWidget

Self-contained multi-chain preset editor. Combines chain switching (ChainSelectMobileUI), preset index tabs (P1/P2/P3), and a persisted PresetFormWidget.

<MultiChainPresetFormWidget
  chains={[Chain.SOLANA, Chain.ETHEREUM, Chain.BINANCE]}
  onChange={(chain, idx, value) => console.log(chain, idx, value)}
/>

Props (MultiChainPresetFormWidgetProps):

| Name | Type | Description | | ------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------- | | chains | Chain[] | Available chains to switch between. | | defaultChain? | Chain | Initial chain. Defaults to first item in chains. | | storageKeyPrefix? | string | Storage key prefix. Defaults to "liberfi.". | | onChange? | (chain: Chain, presetIndex: number, value: TradePresetValues) => void | Notification callback with chain and preset context. | | className? | string | External style customization. |

PresetFormWidget

Atom-backed widget for editing a single trade preset (slippage, priority fee, tip fee, auto fee, anti-MEV, custom RPC). State is persisted via atomWithStorage (keyed by prefix + chain + preset index).

For a pure presentational form without persistence, use PresetFormUI directly.

<PresetFormWidget
  chain={Chain.SOLANA}
  presetIndex={0}
  storageKeyPrefix="myapp."
  onChange={(next) => console.log("Preset changed:", next)}
/>

Props (PresetFormWidgetProps):

| Name | Type | Description | | ------------------- | ------------------------------------ | ---------------------------------------------------- | | chain | Chain | Target chain — determines default values and fields. | | presetIndex? | number | Preset index (0, 1, or 2). Defaults to 0. | | storageKeyPrefix? | string | Storage key prefix. Defaults to "liberfi.". | | onChange? | (value: TradePresetValues) => void | Notification callback (does not control state). | | className? | string | External style customization. |

PresetFormUI

Pure presentational preset form. Accepts value/onChange props directly. No persistence — the caller owns the state.

useInstantTradeAmount(params)

Read-only hook for the persisted instant-trade amount and preset index. Reads from the same atomWithStorage atom family that AmountPresetInputWidget writes to.

Parameters (UseInstantTradeAmountParams):

| Name | Type | Description | | ------------------- | -------- | ----------------------------------------------------------------------------- | | id | string | Business identifier (must match the widget's id). | | chain | Chain | Target chain. | | tokenAddress | string | Payment token address. | | storageKeyPrefix? | string | Storage key prefix. Must match the widget's prefix. Defaults to "liberfi.". |

Returns: AmountPresetState

| Name | Type | Description | | -------- | --------------------- | ----------------------- | | amount | number \| undefined | Persisted amount value. | | preset | number | Persisted preset index. |

State Atoms

presetAtomFamily

Atom family for trade preset values, persisted via atomWithStorage. Each atom is keyed by "{chain}:{index}" (use presetKey to build the key). Default values are chain-specific.

import { presetAtomFamily, presetKey } from "@liberfi.io/ui-trade";

const atom = presetAtomFamily(presetKey(Chain.SOLANA, "buy", 0));

presetKey(chain, direction, index, prefix?)

Constructs an atomFamily key string from chain, direction, preset index and optional prefix.

| Name | Type | Description | | ----------- | ----------------- | --------------------------------------------- | | chain | Chain | Target chain. | | direction | "buy" \| "sell" | Trade direction. | | index | number | Preset index (0, 1, or 2). | | prefix? | string | Storage key prefix. Defaults to "liberfi.". |

Returns: string — key for use with presetAtomFamily.

instantTradeAmountAtomFamily

Atom family for instant-trade amount and preset index, persisted via atomWithStorage. Each atom is keyed by a string built with instantTradeAmountKey. The storage key includes id, chain, and tokenAddress so the same token on different chains has separate persisted values.

import {
  instantTradeAmountAtomFamily,
  instantTradeAmountKey,
} from "@liberfi.io/ui-trade";

const atom = instantTradeAmountAtomFamily(
  instantTradeAmountKey(
    "token-detail",
    Chain.SOLANA,
    "11111111111111111111111111111111",
  ),
);

instantTradeAmountKey(id, chain, tokenAddress, prefix?)

Constructs an atomFamily key string for instant-trade amount + preset state.

| Name | Type | Description | | -------------- | -------- | --------------------------------------------- | | id | string | Business identifier. | | chain | Chain | Target chain. | | tokenAddress | string | Payment token address. | | prefix? | string | Storage key prefix. Defaults to "liberfi.". |

Returns: string — key for use with instantTradeAmountAtomFamily.

Instant Trade Types

TradePresetValues

interface TradePresetValues {
  slippage: number | null;
  priorityFee: number | null;
  tipFee: number | null;
  autoFee: boolean;
  maxAutoFee: number | null;
  antiMev: "off" | "reduced" | "secure";
  customRPC: string | null;
}

InstantTradeSettings

interface InstantTradeSettings {
  buy: BuySettings;
  sell: SellSettings;
}

interface BuySettings {
  customAmounts: (number | null)[];
  presets: TradePresetValues[];
}

interface SellSettings {
  customPercentages: (number | null)[];
  presets: TradePresetValues[];
}

DEFAULT_SOL_TRADE_PRESET

Default preset values: slippage=20, priorityFee=0.001, tipFee=0.001, autoFee=false, maxAutoFee=0.1, antiMev="off".

DEFAULT_INSTANT_TRADE_SETTINGS

Default settings with 4 buy amounts (0.01, 0.1, 1, 10), 4 sell percentages (10, 25, 50, 100), and 3 default presets each.

Usage Examples

InstantTradeWidget (Full Trade Form)

import { Chain } from "@liberfi.io/types";
import { InstantTradeWidget } from "@liberfi.io/ui-trade";

function TokenTradePage({ tokenAddress }: { tokenAddress: string }) {
  return (
    <InstantTradeWidget
      chain={Chain.SOLANA}
      tokenAddress={tokenAddress}
      onSwapSubmitted={(result) => {
        toast.success(`Transaction submitted: ${result.txHash}`);
      }}
      onSwapError={(error, phase) => {
        toast.error(`Swap failed at ${phase}: ${error.message}`);
      }}
    />
  );
}

AmountPresetInputWidget (Persisted Quick-Buy)

import { Chain } from "@liberfi.io/types";
import { AmountPresetInputWidget } from "@liberfi.io/ui-trade";
import { SOLANA_TOKENS } from "@liberfi.io/utils";

function TokenHeader() {
  return (
    <AmountPresetInputWidget
      id="token-detail"
      chain={Chain.SOLANA}
      token={SOLANA_TOKENS.native}
      variant="bordered"
      size="sm"
      onPresetClick={(p) => console.log(`Open settings for preset ${p}`)}
    />
  );
}

AmountPresetInputUI (Controlled)

import { useState } from "react";
import { Chain } from "@liberfi.io/types";
import { AmountPresetInputUI } from "@liberfi.io/ui-trade";
import { SOLANA_TOKENS } from "@liberfi.io/utils";

function CustomAmountInput() {
  const [amount, setAmount] = useState<number | undefined>();
  const [preset, setPreset] = useState(0);

  return (
    <AmountPresetInputUI
      token={SOLANA_TOKENS.native}
      chain={Chain.SOLANA}
      amount={amount}
      onAmountChange={setAmount}
      preset={preset}
      onPresetChange={setPreset}
      variant="bordered"
      size="lg"
    />
  );
}

Reading Persisted Amount/Preset

import { Chain } from "@liberfi.io/types";
import { useInstantTradeAmount } from "@liberfi.io/ui-trade";

function useTokenDetailAmount(tokenAddress: string) {
  const { amount, preset } = useInstantTradeAmount({
    id: "token-detail",
    chain: Chain.SOLANA,
    tokenAddress,
  });
  return { amount, preset };
}

Multi-Chain Preset Settings Editor

import { Chain } from "@liberfi.io/types";
import { MultiChainPresetFormWidget } from "@liberfi.io/ui-trade";

function TradeSettings() {
  return (
    <MultiChainPresetFormWidget
      chains={[Chain.SOLANA, Chain.ETHEREUM, Chain.BINANCE]}
      storageKeyPrefix="myapp."
      onChange={(chain, idx, value) =>
        console.log(`chain=${chain}, preset=${idx}`, value)
      }
    />
  );
}

Token List with Shared Swap Provider

import { Chain } from "@liberfi.io/types";
import {
  InstantTradeSwapProvider,
  AmountPresetInputWidget,
  InstantTradeListButtonWidget,
} from "@liberfi.io/ui-trade";
import { SOLANA_TOKENS } from "@liberfi.io/utils";

function TokenList({
  tokens,
}: {
  tokens: { address: string; symbol: string }[];
}) {
  return (
    <InstantTradeSwapProvider
      chain={Chain.SOLANA}
      onSwapSubmitted={(r) => toast.success(`TX: ${r.txHash}`)}
      onSwapError={(e, phase) => toast.error(`${phase}: ${e.message}`)}
    >
      {/* Shared amount input — one for all buttons */}
      <AmountPresetInputWidget
        id="token-list"
        chain={Chain.SOLANA}
        token={SOLANA_TOKENS.native}
      />

      {/* N buttons — each only subscribes to its own atom + stable context */}
      {tokens.map((t) => (
        <InstantTradeListButtonWidget
          key={t.address}
          id="token-list"
          chain={Chain.SOLANA}
          token={SOLANA_TOKENS.native}
          output={t.address}
        />
      ))}
    </InstantTradeSwapProvider>
  );
}

Reading Preset Values from Atoms

import { Chain } from "@liberfi.io/types";
import { usePresetValues } from "@liberfi.io/ui-trade";

function useCurrentPreset(chain: Chain, index: number) {
  return usePresetValues({ chain, direction: "buy", presetIndex: index });
}

Future Improvements

  • Add useSwapQuote hook for fetching quotes without executing (read-only route info).
  • Add ERC20 approval detection and useTokenApproval hook for EVM chains that don't support permit.
  • Support ExactOut swap mode with output amount estimation.
  • Add a skeleton loading component (swap-skeleton.ui.tsx) for the swap form.
  • Support token-select callback props on SwapWidget for integrating with external token picker UI.
  • Add limit order and advanced trade modes to InstantTradeWidget.
  • Add token price conversion display in the instant trade form.
  • Support custom RPC endpoint validation in PresetFormWidget.