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

0xtrails

v0.18.6

Published

SDK for Trails

Readme

0xtrails

Trails SDK for embedding the widget and calling Trails hooks in React apps.

Install

npm install 0xtrails @tanstack/react-query viem

If you want Trails to use your existing wagmi setup, also install @0xtrails/adapter-wagmi and wagmi.

Standalone Widget

For widget-only usage, TrailsWidget does not require QueryClientProvider or TrailsProvider.

import { TrailsWidget } from '0xtrails'

const apiKey = 'YOUR_TRAILS_API_KEY'

export function App() {
  return <TrailsWidget apiKey={apiKey} renderInline />
}

When there is no parent wallet context and no adapters, the widget uses its built-in EVM runtime.

Hooks With TrailsProvider

Use TrailsProvider when you want to call Trails hooks directly.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { TrailsProvider, useAccountTotalBalanceUsd } from '0xtrails'

const apiKey = 'YOUR_TRAILS_API_KEY'
const queryClient = new QueryClient()

function BalancePreview() {
  const address = '0x0000000000000000000000000000000000000000'
  const totalBalance = useAccountTotalBalanceUsd(address)

  return <pre>{JSON.stringify(totalBalance.data, null, 2)}</pre>
}

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <TrailsProvider config={{ trailsApiKey: apiKey }}>
        <BalancePreview />
      </TrailsProvider>
    </QueryClientProvider>
  )
}

Hooks

Hooks are for custom UI. Wrap your app with your existing wagmi and React Query providers, then add TrailsProvider.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { WagmiProvider } from "wagmi"
import { TrailsProvider } from "0xtrails"
import { wagmiConfig } from "./wagmi"

const queryClient = new QueryClient()

export function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <TrailsProvider config={{ trailsApiKey: "<YOUR_TRAILS_API_KEY>" }}>
          <YourRoutes />
        </TrailsProvider>
      </QueryClientProvider>
    </WagmiProvider>
  )
}

Widget Modes

Import mode-specific wrappers from 0xtrails/widget.

Pay

Use Pay when your app knows the exact recipient, destination token, chain, and amount.

import { Pay } from "0xtrails/widget"

export function PayInvoice() {
  return (
    <Pay
      apiKey="<YOUR_TRAILS_API_KEY>"
      to={{
        recipient: "0xMerchantAddress",
        chain: "base",
        token: "USDC",
        amount: "49.99",
      }}
      from={{ chain: "arbitrum", token: "ETH" }}
      paymentMethod="CONNECTED_WALLET"
      payMessage="Invoice #INV-1001"
      buttonText="Pay now"
    />
  )
}

Fund

Use Fund to let a user add funds to a wallet or destination account. You can allow different payment methods, use a connected wallet, qr code transfers, exchanges, or fiat on ramp.

The user selects payment method in the widget UI

import { Fund } from "0xtrails/widget"

export function FundWallet() {
  return (
    <Fund
      apiKey="<YOUR_TRAILS_API_KEY>"
      to={{
        recipient: "0xUserWallet",
        chain: "base",
        token: "USDC",
      }}
      onFundingSuccess={(data) => console.log("Funded", data.sessionId)}
    />
  )
}

The host app pre-selects paymentMethod to credit card for funding crypto with 100 USD.

import { Fund } from "0xtrails/widget"

export function FundWallet() {
  return (
    <Fund
      apiKey="<YOUR_TRAILS_API_KEY>"
      paymentMethod="CREDIT_DEBIT_CARD"
      from={{
        currency="USD",
        amount="100"
      }}
      to={{
        recipient: "0xUserWallet",
        chain: "base",
        token: "USDC",
      }}
      onFundingSuccess={(data) => console.log("Funded", data.sessionId)}
    />
  )
}

Swap

Use Swap for same-chain and cross-chain token swaps.

import { Swap } from "0xtrails/widget"

export function SwapToBaseUsdc() {
  return (
    <Swap
      apiKey="<YOUR_TRAILS_API_KEY>"
      onSwapSuccess={(data) => console.log("Swap complete", data.sessionId)}
    />
  )
}

Earn

Use Earn for a built-in UI cross-chain experience for earning yield.

import { Earn } from "0xtrails/widget"

export function EarnUsdc() {
  return (
    <Earn
      apiKey="<YOUR_TRAILS_API_KEY>"
      buttonText="Deposit into a protocol"
      onEarnSuccess={(data) => console.log("Earn deposit", data.sessionId)}
    />
  )
}

Withdraw

Use Withdraw for crypto withdrawal flows. Let users withdraw and swap their assets to any token on any chain.

import { Withdraw } from "0xtrails/widget"

export function WithdrawToBase() {
  return (
    <Withdraw
      apiKey="<YOUR_TRAILS_API_KEY>"
      onWithdrawSuccess={(data) => console.log("Withdrawn", data.sessionId)}
    />
  )
}

Custom Labels

Every mode accepts a labels object that renames the widget's own copy. Each field is optional and falls back to the built-in string, so you can override one word or all four. A blank string counts as no override too — binding a field straight to a text input does the right thing when the box is cleared. A donation flow, for instance, is fund mode with the funding vocabulary swapped out:

import { Fund } from "0xtrails/widget"

export function Donate() {
  return (
    <Fund
      apiKey="<YOUR_TRAILS_API_KEY>"
      to={{ recipient: "0xReliefFund", chain: "polygon", token: "USDC" }}
      labels={{
        confirmButton: "Donate",
        formTitle: "Donate",
        methodsTitle: "Choose a donation method",
        tokensTitle: "Donate with any token",
      }}
    />
  )
}

| Field | Renames | Default | | --------------- | --------------------------------------------- | ---------------------------------------------------------------- | | confirmButton | The primary form button | "Fund", "Pay", "Swap", "Deposit", "Withdraw", by mode | | formTitle | The amount screen's title, and the crypto-transfer screens that continue it | "Fund", "Pay", "Swap", "Withdraw", "Earn" by mode; "Deposit" on the transfer screens | | methodsTitle | The payment method picker's title | "Choose a funding method", "Choose a payment method" in pay and earn, "Payment methods" on the pay flow's own picker | | tokensTitle | The title of every "pay with this" token list | Varies by mode and screen — "Fund with any token", "Select From Token", "Select Token to Withdraw", … |

formTitle deliberately follows the user into the crypto-transfer screens (the QR code and deposit-address steps), so a donation flow keeps saying "Donate" instead of switching to "Deposit" halfway through. The card on-ramp screens are left alone — they hand off to a third party and read that way by design.

tokensTitle covers the origin pickers only. Destination pickers ("Receive", "Select destination token") keep their own titles, since the token the user sends and the token the recipient gets are different questions.

Two things the overrides deliberately leave alone:

  • Transient button states. "Enter an amount", "Getting quote...", "Connect your wallet" and "Insufficient Balance" report what the widget is doing, not what the button does, so they keep their own copy.
  • Screens borrowed from another mode. Overrides apply only to the configured mode. Open the swap tab from a fund widget's header and the button still says "Swap" — a "Donate" label there would be wrong.

For the modal trigger button specifically, buttonText still wins over labels.confirmButton.

Consent Checkbox

consentCheckbox puts a consent line directly above the primary button, with a link the user can open before they commit. Setting the prop is what makes the tick mandatory — there is no separate flag, because an optional consent checkbox is not a thing anyone wants.

import { Fund } from "0xtrails/widget"

export function DonateWithTerms() {
  return (
    <Fund
      apiKey="<YOUR_TRAILS_API_KEY>"
      to={{ recipient: "0xReliefFund", chain: "polygon", token: "USDC" }}
      labels={{ confirmButton: "Donate" }}
      consentCheckbox={{
        label:
          "I've read and agree to the " +
          '<a href="https://example.org/terms">Terms &amp; Conditions</a>',
        requiredMessage: "Please accept the Terms & Conditions to donate.",
      }}
    />
  )
}

| Field | Purpose | Default | | ----------------- | ---------------------------------------------------- | -------------------- | | label | The consent line, as inline HTML. Required. | — | | requiredMessage | Standing notice raised by a press on an unticked box | none — see below |

A blank label removes the gate. Setting the prop is what makes the tick mandatory, so a whitespace-only label means no checkbox renders and the confirm button is not gated — the flow keeps working with the consent step silently gone. That is deliberate (there is nothing to render), but it is the failure mode to watch if you bind label to a CMS field or an i18n key that might not resolve. The SDK logs a warning when it happens.

The button stays live. While the box is unticked the confirm button keeps its normal enabled styling, so a ready quote still reads as ready. Pressing it does not submit: the row shakes, tints, and focus moves to the checkbox.

requiredMessage decides what a failed press leaves behind. Set it and the message stands under the checkbox until the box is ticked, so a user who missed the shake still has the reason on screen. Omit it and the shake and tint are the whole response — lighter, and it never changes the resting layout, but a second press teaches nothing the first did not. Either way the failed press moves focus to the checkbox, so assistive technology announces the unticked control.

label is sanitised. It is the one prop rendered as HTML, against a fixed allowlist:

  • Kept: a, b, strong, i, em, span, br. Every other tag is dropped rather than escaped, so an unsupported tag renders as clean text.
  • Every attribute is stripped. An anchor keeps its href if that href resolves to http(s) or mailto — which includes relative (/terms) and protocol-relative (//host/path) forms, emitted exactly as written. Anything else (javascript:, data:) is unwrapped to plain text so nothing inert looks clickable.
  • Surviving anchors are rewritten to target="_blank" rel="noopener noreferrer", so reading the terms never costs the user a filled-in quote.

This allowlist keeps a label from breaking the widget or injecting an element. It is not a validator for a URL you interpolate from somewhere else — if the href comes from a CMS, a query string or user input, check it yourself before it gets here.

Opening the link never ticks the box — the two are deliberately separate acts. If you would rather the link opened your own in-page modal, add a delegated click listener and call preventDefault(); the widget renders inside a shadow root, but its events are composed, so composedPath() sees the anchor.

Consent is per session and never persisted. A reload asks again, which is what makes the tick evidence of assent rather than a remembered preference.

Like labels, the checkbox belongs to the configured mode's screens. Open the swap tab from a fund widget's header and it does not follow.

useQuote

useQuote gives you a quote and an executable send function for a fully custom UI. Exactly one amount should be provided: from.amount or from.amountRaw for exact-input, or to.amount or to.amountRaw for exact-output. You can use "feeOptions" to allow paying gas in stablecoins.

import { useQuote, QuoteError } from "0xtrails"
import { useAccount, useWalletClient } from "wagmi"

export function CustomSwap() {
  const { address } = useAccount()
  const { data: walletClient } = useWalletClient()

  const {
    quote,
    send,
    isLoadingQuote,
    quoteError,
    quoteErrorPrettified,
    feeOptions,
    refetchQuote,
    abort,
  } = useQuote({
    from: {
      chain: "base",
      token: "USDC",
      amount: "10",
    },
    to: {
      chain: "arbitrum",
      token: "ETH",
      recipient: address,
    },
    onStatusUpdate: (states) => console.log("status", states),
  })

  if (quoteError instanceof QuoteError) {
    console.log("Quote failed with code", quoteError.code)
  }

  return (
    <section>
      {isLoadingQuote && <p>Loading quote...</p>}
      {quoteErrorPrettified && <p>{quoteErrorPrettified}</p>}

      {quote && (
        <>
          <p>
            Spend {quote.originAmountDisplay} for{" "}
            {quote.destinationAmountDisplay}
          </p>
          <p>Estimated time: {quote.completionEstimateDisplay}</p>
          <p>Fee options: {feeOptions.length}</p>
        </>
      )}

      <button disabled={!send} onClick={() => send?.()}>
        Send
      </button>
      <button onClick={() => refetchQuote()}>Refresh</button>
      <button onClick={() => abort()}>Abort</button>
    </section>
  )
}

For previewing a quote, without needing a connected wallet, pass walletAddress.

useTrailsSendTransaction

useTrailsSendTransaction is a destination-first transaction hook with Trails routing. It mirrors useQuote's from/to shape and can open the Trails widget modal when the user needs to choose the origin chain, token, or amount so you don't have to build this step.

Render TrailsHookModal once inside TrailsProvider so modal-driven sends have somewhere to mount.

Describe what the recipient should receive via to and let the user pick how to pay. Set to.amount for an exact-output send (the modal only needs origin token selection).

import { useTrailsSendTransaction } from "0xtrails"

export function RequestUsdcPayment() {
  const { sendTransaction, isPending } = useTrailsSendTransaction({
    receiptActionButtonText: "Back to app",
  })

  return (
    <>
      <button
        disabled={isPending}
        onClick={() =>
          sendTransaction({
            to: {
              chain: "polygon",
              token: "USDC", // or a contract address
              recipient: "0xMerchantAddress",
              amount: "25",
            },
          })
        }
      >
        Pay 25 USDC
      </button>
    </>
  )
}

Provide from ({ chain, token, amount }) when your app already knows the source asset to skip the modal. Use from.amountRaw/to.amountRaw when you already have smallest-unit amounts.

Composable Actions

Composable actions describe what should happen after funds arrive on the destination chain. They are resolved into destination calls by the SDK and can be used from both useQuote and useTrailsSendTransaction.

Available action builders include:

  • swap for destination-chain swaps.
  • lend and deposit for supported earn markets.
  • custom for bulding your own action.
  • assertCondition for runtime checks.
  • dynamic() to spend the intent wallet's runtime balance when value amounts are unknown.

Quote And Execute Actions

import {
  assertCondition,
  dynamic,
  lend,
  swap,
  useQuote,
} from "0xtrails"
import { useAccount, useWalletClient } from "wagmi"

export function QuoteActions() {
  const { address } = useAccount()
  const { data: walletClient } = useWalletClient()

  const actions = [
    // Check if we received enough USDC on the destination chain
    assertCondition({
      erc20Balance: { token: "USDC", gte: "100" },
    }),
    // Swap 100 USDC in WETH (defaults to Uniswap V3)
    swap({
      tokenIn: "USDC",
      tokenOut: "WETH",
      amountIn: "100",
      fee: "0.05"
    }),
    // Lend all WETH received from the swap 
    lend({
      marketId: "base-weth-aave-v3-lending",
      amount: dynamic(),
      receiverAddress: address,
    }),
  ]

  const { quote, send, isLoadingQuote } = useQuote({
    from: { chain: "base", token: "USDC" },
    to: { chain: "base", token: "USDC", amount: "100" },
    actions,
  })

  return (
    <button disabled={!send || isLoadingQuote} onClick={() => send?.()}>
      {quote ? "Execute recipe" : "Get quote"}
    </button>
  )
}

actions is mutually exclusive with to.calls and deprecated to.calldata.

Execute Actions With The Widget Modal

import { dynamic, erc20Utils, lend, swap, useTrailsSendTransaction } from "0xtrails"

export function WidgetActionRecipe({ userAddress }: { userAddress: `0x${string}` }) {
  const actions = [
    swap({
      tokenIn: "USDC",
      tokenOut: "USDT",
      amountIn: dynamic(),
      fee: "0.05",
    }),
    lend({
      marketId: "polygon-usdt-fluid-lending",
      amount: dynamic(),
      receiverAddress: userAddress,
    }),
  ]

  const { sendTransaction, isPending } = useTrailsSendTransaction({
    actions,
    receiptActionButtonText: "Done",
  })

  return (
    <>
      <button
        disabled={isPending}
        onClick={() =>
          sendTransaction({
            to: {
              chain: "polygon",
              token: erc20Utils.USDC.addressOn("polygon"),
              recipient: userAddress,
              amount: "50"
            },
          })
        }
      >
        Choose payment token
      </button>
    </>
  )
}

Raw Destination Calls

Use to.calls when you need low-level ABI-encoded calls instead of action builders.

import { buildApproveAndCall, buildCall, erc20Utils, useQuote } from "0xtrails"
import { useAccount } from "wagmi"
import { stakingAbi } from "./stakingAbi"

export function RawCallsQuote() {
  const { address } = useAccount()

  const stakingCall = buildCall({
    to: "0xStakingContract",
    data: {
      abi: stakingAbi,
      functionName: "stake",
      args: [1_000_000n],
    },
  })

  const calls = buildApproveAndCall({
    token: erc20Utils.USDC.addressOn("base"),
    call: stakingCall,
  })

  const { quote, send } = useQuote({
    from: { chain: "base", token: "USDC" },
    to: {
      chain: "base",
      token: "USDC",
      amount: "1",
      calls,
    },
  })

  return (
    <button disabled={!send} onClick={() => send?.()}>
      {quote ? "Stake USDC" : "Loading quote"}
    </button>
  )
}

Utilities

Common exports from 0xtrails:

  • getSupportedChains, useSupportedChains, and resolveChainId.
  • getChainInfo, getAllChains, and attemptSwitchChain.
  • useTokenList, useSupportedTokens, useTokenBalances, useTokenBalancesForMultipleAccounts, useAccountTotalBalanceUsd, useHasSufficientBalanceToken, and useHasSufficientBalanceUsd.
  • getTokenPrice, getTokenPrices, useTokenPrice, useTokenPrices
  • erc20Utils, buildCall, buildApproveAndCall, buildErc20Approve, and getAmountWithSlippage for token metadata, ERC-20 calls, approvals, and custom calldata.
  • uniswapV3 and sushiswapV3 for custom Uniswap V3 / SushiSwap V3 pool lookup, quote, and swap call construction.
  • useEarnProviders, useEarnMarkets, useEarnBalances, getEarnProviders, getEarnMarkets, and getEarnBalances.
  • useResolveActions, encodeDestinationForIntentProtocol, createHydratePayload, and encodeMulticallHydrateExecute for advanced destination-call and hydrate flows.
  • prepareSend, commitIntent, quoteIntent, sendOriginTransaction, getIntent, useGetIntent, pollIntentReceipt, and useIntentReceiptMonitor.
  • getAccountTransactionHistory, useAccountTransactionHistory, useIntentTransactionHistory, and getTxTimeDiff.
  • useFiatCurrencyList, useExchangeRate, getCurrencyInfo
  • getTrailsClient, useTrailsClient, TrailsClient
  • useIntentRecover, useIntentRecoverWithAddress, buildRefundTransactionWithSignature, and signPayload.

TrailsProvider mounts the built-in wallet runtime by default. Add config.adapters when Trails should use explicit wallet runtime adapters instead.

Use Trails with wagmi

If your app already uses wagmi, add @0xtrails/adapter-wagmi so Trails can share your wallet setup. Wagmi 3 is the primary integration target; wagmi 2 remains supported through the same adapter peer range.

For more options and wagmi 2/3 examples, see packages/wallet/adapters/wagmi/README.md, apps/examples/wagmi3, and apps/examples/wagmi2.

import { TrailsProvider, TrailsWidget } from '0xtrails'
import { wagmiAdapter } from '@0xtrails/adapter-wagmi'
import { WagmiProvider } from 'wagmi'

const apiKey = 'YOUR_TRAILS_API_KEY'
// Create wagmiConfig with your chains, transports, and connectors.
const adapters = [wagmiAdapter({ wagmiConfig })]

export function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <TrailsProvider config={{ trailsApiKey: apiKey, adapters }}>
        <TrailsWidget renderInline />
      </TrailsProvider>
    </WagmiProvider>
  )
}

Use the same wagmiConfig instance for both WagmiProvider and wagmiAdapter(...) when Trails should bridge into the host-owned wagmi runtime. If there is no matching surrounding WagmiProvider, the adapter mounts its own runtime with the provided config.

Runtime ownership

Adapter runtime selection and lifecycle ownership are separate:

  • wagmi runtime selection is based on wagmiConfig identity
    • same config instance as the surrounding WagmiProvider → bridge to the host runtime
    • no matching surrounding provider → mount an adapter-owned runtime
  • managedBy is for composite adapter lists and controls lifecycle ownership in Trails
    • managedBy: 'host' means Trails can use the wallet but should not own host lifecycle actions like disconnect
    • managedBy: 'trails' means Trails owns that adapter's wallet lifecycle
const adapters = [
  { adapter: wagmiAdapter({ wagmiConfig: hostWagmiConfig }), managedBy: 'host' },
  { adapter: wagmiAdapter({ wagmiConfig: trailsWagmiConfig }), managedBy: 'trails' },
]

Development

Regenerate widget SVG components from a folder of raw SVGs with pnpm --dir packages/0xtrails icons:generate <source-dir>.

Rules

  • wagmi is optional and only needed for adapter-based integrations; wagmi 3 is the primary target and wagmi 2 remains supported for compatibility
  • hooks require both QueryClientProvider and TrailsProvider
  • adapters can live on TrailsProvider config.adapters or TrailsWidget adapters
  • do not provide adapters in both places at the same time
  • TrailsWidget adapters cannot be used inside an existing parent wallet context, including the default runtime mounted by TrailsProvider

Use Trails with Solana

Install @0xtrails/svm to enable Solana edge payments — users fund from a Solana wallet and the payment lands on any EVM chain.

npm install @0xtrails/svm

Pass svmAdapter alongside your EVM adapter. With no wallet option, it auto-detects Wallet Standard Solana wallets such as Phantom and Solflare:

import { TrailsWidget, evmAdapter } from '0xtrails'
import { svmAdapter } from '@0xtrails/svm'

const adapters = [
  evmAdapter({ wallets: [{ id: 'injected', name: 'MetaMask' }] }),
  svmAdapter({ rpcUrls: ['https://api.mainnet-beta.solana.com'] }),
]

export function App() {
  return (
    <TrailsWidget
      apiKey="YOUR_TRAILS_API_KEY"
      adapters={adapters}
      fundOptions={{ fundMethodsList: ['connected-wallet'] }}
      renderInline
    />
  )
}

Solana funding is available through the connected-wallet funding path when a Solana adapter is configured. Supported origin tokens are SOL, USDC, USDT, and PYUSD.

With wagmi

Combine wagmiAdapter and svmAdapter when your app uses both EVM and Solana wallets:

import { wagmiAdapter } from '@0xtrails/adapter-wagmi'
import { svmAdapter } from '@0xtrails/svm'

const adapters = [
  wagmiAdapter({ wagmiConfig }),
  svmAdapter({ rpcUrls: ['https://api.mainnet-beta.solana.com'] }),
]

For more options and examples, see packages/0xtrails-svm/README.md.

License

Apache-2.0