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

@spicenet-io/spiceflow-ui

v4.13.1

Published

Spiceflow UI SDK

Readme

SpiceFlow UI SDK

A React component library for building DeFi swap interfaces with multi-chain support.

Version and migration

Release 4.11.0 includes quote prewarming, scoped fee-hint reuse, concurrent fee checks, configurable fee-estimate tolerance, and faster execution-status polling. It uses @spicenet-io/[email protected]. Source spend caps, destination minimums and required destination receipts remain enforced. See the fee-estimate tolerance section below for the default and configuration.

Check the actual registry tag and client pin before adopting it. See CONSISTENCY-MIGRATION.md for the earlier inventory/action migration. Publishing the SDK does not upgrade consuming apps automatically.

Installation and provider setup

Select an approved exact version, preserving the app's dependency-age and security policies:

bun add --exact @spicenet-io/[email protected]

Reuse compatible host-owned Privy, wagmi and QueryClient providers. Read the exact package's peer dependencies; do not replace the wallet stack as part of a version bump.

The SDK adds its stylesheet (spinner keyframes, focus ring, reduced-motion rule) to the page when it loads, so no CSS import is needed. @spicenet-io/spiceflow-ui/styles.css is still exported for a host whose Content Security Policy forbids inline styles; importing it as well is harmless.

import { SpiceFlowProvider } from "@spicenet-io/spiceflow-ui";

// Inside the host's existing WagmiProvider → QueryClientProvider → PrivyProvider stack.
<SpiceFlowProvider
  provider="privy"
  network="mainnet"
  nativeChainId={8453}
  rpcOverrides={clientRpcUrls}
>
  {children}
</SpiceFlowProvider>;

Configure Privy credentials/policy on the host's PrivyProvider. Supply client RPCs through rpcOverrides for the chains you exercise, and keep the SDK/wallet chain sets aligned. An entry in rpcOverrides always wins; a chain without one uses the chain's built-in endpoint, so an integration that passes none keeps working. Wallet-library transports alone do not configure SDK reads. Rollup price lookups read only a host-configured RPC. Keep private credentials out of browser bundles.

Integration ownership

  • Use stock components when they fit the approved UI; use useSpiceSupply for custom action forms. useSpiceLock and legacy useSpiceAction delegate to that controller.
  • In this source (not yet published), useSpiceSupply defaults enabled to true. Omit it for a mounted action form, or pass enabled: isOpen when a hidden form stays mounted. Use canExecute for submit-button readiness; fee calculation does not require toggling enabled. Published 4.10.0 still requires an explicit value.
  • Declare the destination token by chain and address: destinationToken={{ chainId: 8453, address: USDC }}. The SDK takes the symbol and decimals from the chain's token registry, then from the token contract, and holds the action until they are known. Decimals that cannot be read block the action; a symbol that cannot be read falls back to the shortened address. Any field you pass wins, so a full { address, symbol, decimals, chainId } object behaves exactly as before.
  • Supply destination metadata, protocol buildActionCalls or destination-only lockBatches, beneficiary semantics, and awaited application updates. The SDK owns inventory, routing, Max, fees, accepted plans, funding, execution and recovery.
  • For the account, use useSpiceAccount() instead of reading SpiceFlowProviderContext: mode ("embedded" or "external"), setMode, ownerAddress for the current mode, both wallet addresses and the whitelist state. A provider with a fixed mode prop ignores setMode.
  • For custom holdings, use useSpiceInventory and assetPositionId. Keep raw balance, default visibility and spend eligibility separate. Preserve rows during refresh; stale/failed balances cannot authorize spending, and confirmed zero wins.
  • In this unreleased source, eligible External-mode actions with a matching wallet token and destination chain execute directly from the external signer, without embedded funding. Other assets/chains still use embedded funding and the shared routed executor. onPayExecute and onLockExecute remain blocked; the deprecated external-caller hint supports only the direct case. Verify contract caller/beneficiary and NFT receiver behavior. Published .16 does not contain this restoration.
  • Supply input adds fees to principal; input-inclusive uses a total budget; Pay output fixes the destination amount. Use controller readiness/Max/status instead of constructing an executor context or duplicating fee arithmetic in the host.
  • Use controller recovery for uncertain funding and retryFinalization() for failed application updates after settlement. Preserve original host terms across reload.
  • Deposit and rollup Withdraw retain distinct execution semantics while sharing inventory/policy. Account wallet withdrawal uses the fee-only Supply path.

Inventory isolation does not establish support for simultaneous differently configured providers: underlying quote/execution transports still have global state.

Liquidity venues

When a flow has to swap on the destination chain, the SDK quotes every liquidity venue that covers that chain and takes the best fill: the most output on a supply, the least input on a pay. Routing is decided here, on your RPC. The solver only executes the calls the winning quote produced.

Leave liquidityVenues off and you get the built-in venues. Declare it once on the provider to add your own, and every component picks them up.

<SpiceFlowProvider
  network="mainnet"
  nativeChainId={4663}
  rpcOverrides={{ 4663: ROBINHOOD_RPC }}
  liquidityVenues={{
    venues: [
      {
        protocol: "uniswap-v3",
        chainId: 4663,
        swapRouter: "0x1e406484F1F204b23cE84B9901C0171a738fd406",
        quoterV2: "0x3E290e5E01818002A0b672148BdC7514D861C7B3",
        hubCurrencies: ["0x0bd7d308f8e1639fab988df18a8011f41eacad73"],
      },
    ],
  }}
>

Your venues race the built-ins on price; they do not override them. Set includeDefaults: false to drop the built-ins and route only through what you declared. On a chain with no venue at all, quoting fails with that chain named rather than falling back to something you did not configure.

Every entry names one chain and one protocol family, and serves both swap directions. Supply the deployment's addresses, not a pool address: a v3-style pool calls back into msg.sender, so swaps go through the router and quotes through the quoter. The SDK finds the pools from the fee tiers (or tick spacings) and hub currencies.

| protocol | Required | Optional | | ---------------------- | ------------------------------------------------- | -------------------------------------------------------- | | uniswap-v3 | chainId, swapRouter, quoterV2 | name, feeTiers, hubCurrencies, routerHasDeadline | | uniswap-v4 | chainId, quoter, universalRouter, permit2 | name, hubCurrencies | | aerodrome-slipstream | chainId, swapRouter, quoterV2 | name, tickSpacings, hubCurrencies |

name is the label shown to the user as "via " once a swap is quoted, so give it the name people know the venue by. It defaults to the protocol, which means a swap always names something real rather than a family it does not belong to.

hubCurrencies are the intermediate tokens tried for two-hop routes, usually wrapped native and the chain's main stablecoin. Set routerHasDeadline when the deployment forked the original Uniswap SwapRouter, whose exact-output structs carry a deadline; SwapRouter02 forks do not.

Most DEXs are forks of one of these families, so adding one is addresses only. A venue from a different family (a Uniswap v2 pair, Curve, Balancer) needs an adapter in the SDK — send us the chain, router, quoter and one funded pool and it becomes a protocol everyone can name.

Components

  • SwapWidget - Main swap interface component
  • AssetSelector - Token selection component
  • SpiceDeposit - Deposit modal component
  • StatusDisplay - Transaction status display
  • ProviderLogin - Wallet connection component
  • DebugWidget - Development debugging component
  • Button, Input, Modal - UI primitives

Development

Rollup balance pricing

Rollup USD values use the host-configured RPC provider's pricing capability when the balance API has no valid value. The built-in account panel and withdrawal modal enable this while open; custom useSpiceAssets consumers can opt in with priceEnabled. Quantities arrive independently. Lookups follow receipt mappings, prefer nativeChainId, and fall back to other supported chains with a configured Alchemy endpoint. Explicit account tokenPrices overrides still take precedence over provider quotes.

Wallet discovery and rollup balances share a one-minute quote cache. Pricing polls only while the view is open and the page is visible. A failed refresh can retain a marked stale quote for up to five minutes; an unavailable quote displays —. No extra key, internal RPC, or symbol-based ERC-20 price fallback is used.

Host tokens

A token the SDK does not know (a protocol's own token, a receipt token) is declared once on the provider. Every surface that lists balances then reads it: Supply, Pay, Lock, the account panel and Withdraw.

<SpiceFlowProvider
  additionalSourceTokens={[{ address: TOKEN, chainId: 8453, symbol: "TOKEN", decimals: 18 }]}
>

A component's or hook's own additionalSourceTokens still works and is added to the provider's list.

Host token prices

A host token usually has no price the SDK can find, so the account panel lists it without a USD value. AccountDisplay.tokenPrices takes the host's own price for it, keyed by chainId:address. The value counts toward the account total and the row sorts with the rest.

<AccountDisplay tokenPrices={{ [`8453:${TOKEN}`]: tokenUsdPrice }} />

The SDK's own price wins whenever it has one. A symbol key prices rollup balances only: any wallet token can carry any symbol, so a wallet balance is matched by address alone. The price is for display and never changes which balance an action spends.

Host-supplied token logos

AccountDisplay.tokenLogos maps a token symbol to a logo URL, the same key shape tokenPrices uses. A discovered embedded-wallet balance falls back to it when the RPC provider returns no logo, which is what happens for a token that is not in the chain's configured token list. Provider metadata still wins when it has a logo.

<AccountDisplay tokenLogos={{ TOKEN: tokenLogoUrl }} />

Account with Deposit and Withdraw

SpiceAccount renders AccountDisplay together with SpiceDeposit and SpiceWithdraw, and owns their open state. Withdraw on an idle rollup balance opens the modal with that balance selected. It takes every AccountDisplay prop except the two click handlers; depositProps and withdrawProps pass through to the modals. styles and onDepositSuccess are shared with the modals unless those props set their own.

<SpiceAccount appName="MyApp" onDepositSuccess={refresh} />

Use AccountDisplay directly to hide one of the buttons or to open your own flow from them.

Withdraw from an account balance

AccountDisplay.onWithdrawClick receives the rollup receipt-token ID when a user clicks Withdraw on an idle rollup balance. The main Withdraw button calls it without an ID. Pass the ID to SpiceWithdraw.initialSourceAssetId to open the existing modal with that asset selected; the user chooses the destination network inside the modal. Existing callbacks that take no arguments continue to work.

const [withdrawOpen, setWithdrawOpen] = useState(false);
const [withdrawAssetId, setWithdrawAssetId] = useState<string>();

<AccountDisplay
  onWithdrawClick={(receiptTokenId) => {
    setWithdrawAssetId(receiptTokenId);
    setWithdrawOpen(true);
  }}
/>
<SpiceWithdraw
  isOpen={withdrawOpen}
  onClose={() => setWithdrawOpen(false)}
  initialSourceAssetId={withdrawAssetId}
/>

buildSpicenetBatch defaults to the SDK's buildSpicenetWithdrawalBatch. An integration that already passes a builder keeps it as an override, and its callbacks are unchanged. initialSourceAssetId selects a current balance by ID; it does not supply a cached balance or change the receipt's supported withdrawal routes.

Funding sources

Supply, Pay and Lock list every balance that can fund the action: the embedded wallet and rollup balances, and the connected external wallet, in either wallet mode. To keep a mode to its own wallet, pass includeExternalWalletAssets={false} (Embedded mode) or includeCrossChainEmbeddedAssets={false} (External mode). A user who holds the same token in both wallets sees one row per wallet; a host that preselects a source should match on assetPositionId, not on symbol or address alone.

Action Fee Readiness

  • Routed Supply, Pay and Lock actions must surface fee estimation before enabling submission.
  • Eligible direct external actions still require an accepted plan but charge no Spicenet fee. Network gas remains payable in the external wallet; native Max reserves gas.
  • Deposit gas and rollup withdrawal semantics remain distinct from action intent fees. Preserve their existing quote/readiness requirements.
  • Account wallet withdrawal is a fee-only Supply action. Do not bypass its fee/readiness gates based on the label "withdraw".
  • Custom actions use the shared controller's canExecute; an available balance or old quote alone is insufficient.
  • onSubmit on SpiceSupply, SpiceLock, SpiceDeposit and the shared controller fires once when the user submits and the SDK accepts it, before any wallet prompt. Use it for "transaction in progress" feedback instead of listening for clicks on SDK buttons.
  • max() on the shared controller (useSpiceSupply, useSpiceLock) resolves the fee-aware Max for the selected source, or null when there is none or the action fixes its output. The host sets the result as the amount.

Fee funding for funded actions

Set feeFunding: ["input", "proceeds", "balance"] on useSpiceSupply, SpiceSupply, useSpiceAction, useSpiceLock or SpiceLock to use the shared hierarchy for wallet-funded actions on the destination chain. This applies to arbitrary protocol calls: swaps, deposits, staking, lending and repayments. Direct payment precedes conversion within each tier. Existing defaults remain unchanged; feeFunding: [] retains the existing selected-source path.

  • amountDirection: "input-inclusive" offers the selected budget as input. Fees reduce that input and the SDK requotes the conversion before building protocol calls. The builder must honour the amount passed to it.
  • "input" preserves the selected principal; "output" preserves the exact payment or repayment. Neither offers required principal to the input tier.
  • actionProceeds: { token, resolve } declares an amount remaining in the executor after all protocol calls. Conversion output that is deposited, staked, used for repayment or sent away is not available for fees. The SDK resolves proceeds only if it reaches that tier and rechecks them before signing. The object declaration alone does not opt a funded action in: it requires a nonempty feeFunding order, preserving existing callers' selected-source planning.
  • Balance fallback reserves the selected principal. Only the remaining amount of that exact position can pay fees. fees.sourceSpendCap includes fee spending from the same position, and fees.fundingSource reports the chosen source.

For an input-inclusive swap whose output stays in the executor, actionProceeds: "output" enables the same hierarchy and declares the output as available proceeds. If proceeds pay fees, fees.destinationAmount and quote.estimatedOutput show the net minimum and expected output respectively. Do not use this shortcut for calls that consume or send away the conversion output.

Local external-wallet funding still transfers the principal to the executor first. Direct external execution keeps its native-gas path. External native Max reserves transfer gas, and fixed-principal Max reserves execution fees. Cross-chain and rollup principal routes retain their existing funding planner; this extension does not let destination proceeds pay earlier source or bridge costs, or add balance fallback to those routes. Custom fee orders on those routes fail explicitly instead of silently charging a different tier. Rollup balances can still fund fees for a local action through the existing fee-funding withdrawal leg. Standalone SpiceDeposit and rollup SpiceWithdraw retain their distinct transfer semantics; protocol deposits/withdrawals through the action controller use the contract above.

All paths retain fee readiness and accepted-plan validation. This capability requires an SDK release and explicit integration opt-in; it does not upgrade published packages or deployed apps.

Supply fee-estimate tolerance

In 4.11.0, SpiceFlowProvider defaults feeToleranceBps to 4000 (40%). Clients can override it, for example:

<SpiceFlowProvider {...config} feeToleranceBps={100}>
  {children}
</SpiceFlowProvider>

100 is 1%; 0 disables the drift allowance. Existing conservative fixed-fee retention still applies when estimates fall. Values must be integers from 0 to 10000. The allowance is relative to each accepted fee component: a fee of 100 allows a fresh estimate up to 140 at the default. A zero fee cannot become positive.

This setting covers fee convergence and pre-sign validation for user-transfer Supply plans, including stock/controlled Supply and input-mode Lock consumers. Within tolerance, the SDK keeps the quoted fee amounts and transaction calls; it does not add a 40% fee buffer, increase the source spend cap, or relax swap slippage/minimum outputs. Changing the setting invalidates prepared plans. Backend acceptance and funding checks still apply. Exact-output Pay, fee-only funding, deposits and backend-transfer convergence retain their separate rules.

The open Supply controller retains fee construction hints for at most 30 seconds across amount edits in the same wallet, route and configuration scope. These hints do not enable submission: the SDK rebuilds the current calls, then checks source gas and destination fees concurrently. User-transfer source gas uses the same configured fee tolerance; accepted amounts and source caps stay unchanged. A hint that no longer fits falls back to full preparation. Closing the form clears hints. Servers advertising fee validation price the already constructed destination fee calls; older servers retain the estimate path. Cold preparation still sizes fees from scratch. Input route quotes debounce for 100 ms; routed fee preparation adds no second debounce. These optimizations do not establish a latency SLA.

Fee-inclusive, user-transfer Supply also prewarms when an eligible source is selected with an empty/zero amount. It constructs a provisional quote for at most one token, capped by the available balance, and retains only fee hints. The amount, fee preview and executable plan remain empty. Typing can await in-flight prewarming before validating the real amount; closing or changing wallet/configuration cancels it. Failed provisional preparation leaves normal quoting available. This does not apply to direct external actions, exact-output Pay, fee-only actions or actions with explicit funding inputs/proceeds. No signature or submission occurs during prewarming. The time available before typing determines how much work it hides.

Fee funding for fee-only actions

  • A fee-only action (feeOnlyAction) runs on what the executing wallet already holds; the SDK funds only the Spicenet fees, in the chain's fee token (destinationToken).
  • With no selectedAsset, the SDK picks the fee source and the user is never asked. The action's input (actionInput) comes first, then its proceeds (actionProceeds), so the fee stays inside the action the user submitted; then other balances: same-chain embedded wallet before rollup, and within each, balances already in the fee token before balances that swap into it. The first source that simulates and covers the fee wins and is reported as fees.fundingSource; a source whose swap has no route, or that cannot cover the fee within slippage, is skipped for the next.
  • Declare actionInput for every token the action consumes and honour the amount passed to buildActionCalls: when fees come from an input, the action is built for the remainder. Declare actionProceeds for a token the action leaves in the wallet. Both are optional; with neither, only balances can pay. Omit actionProceeds when the output is delivered to another owner.
  • feeFunding overrides the level order per transaction, for example ["proceeds", "input", "balance"]; the order inside the balance level is fixed. An empty list leaves the choice to the host's selectedAsset.
  • A selectedAsset on a fee-only action is an explicit host choice and funds the fee from that balance as before.
  • Account wallet withdrawals use this hierarchy automatically and display the chosen fee asset without a selector. The exact withdrawal amount is reserved before ranking balances; only its remainder can pay fees. Custom useSpiceSupply actions that must preserve an exact principal can declare feeFundingReserve: { asset, amount } instead of offering that principal as actionInput.
  • feeOnlyAction alone declares the action: amountDirection defaults to "output" and paymentAmount to "0". Explicit values still win.
  • submit() on the controller runs the executor that matches amountDirection. submitSupply() and submitPay() remain for existing callers.

Updating and releasing

Change the shared inventory/policy/controller owner and audit affected stock and custom consumers. Use the central team's workflow, ui-quality and shared/sdk.md guidance; keep skills in the skills repo rather than copying them here.

Read the central shared/client-design-decisions.md register before an upgrade. Map applicable decision IDs to affected consumers and regression scenarios, and report each result with exact candidate evidence. Preserve agreed behavior and ownership even when types compile; a deliberate change needs an explicit product decision and migration plan.

Run focused regressions, bun test, bun run typecheck and bun run build, then validate affected consumer builds and rendered/financial behavior on the exact candidate. Record package identity, configuration, acceptance coverage and untested cases. Earlier candidate results do not automatically apply to newer main or a new client.

If core/API changes are needed, resolve those dependencies before their UI/app consumers. An inventory/controller-only change does not inherently require a core release. After explicit release authorization, publish a prerelease with a non-latest tag, verify its registry artifact, and pin/test that exact version in each adopting app. Stable promotion, client deployment and docs targeting are separate release decisions. This refactor adds no automatic npm publishing.

Building the Library

bun run build

Type Checking

bun run typecheck

CI runs this on every pull request, so a type error fails the build.

Formatting

bun run format

Prettier runs over staged files on commit via lint-staged. There is no ESLint setup in this repository.

Component Playground

This repository includes a lightweight React playground app for viewing and testing all components without the complexity of Storybook.

Running the Playground

  1. Install dependencies (if not already done):

    bun install --frozen-lockfile
  2. Navigate to the playground directory:

    cd playground
  3. Install playground dependencies:

    bun install --frozen-lockfile
  4. Start the development server:

    bun run dev

    The playground will open automatically at http://localhost:3000

Using the Playground

  • Left Sidebar: Browse available components
  • Main Area: View the selected component with different example variants
  • Example Tabs: Switch between different states/examples for each component

Adding a New Component Example

To add a new example variant for a component:

  1. Open playground/src/ComponentRegistry.tsx
  2. Find the component's entry in the examples object
  3. Add a new entry to the array:
    {
      label: 'Your Example Name',
      render: () => <YourComponent {...props} />,
    }

The playground will automatically pick up the new example and display it as a tab.

License

MIT