LayerHopdocs

SDKs & tools

React hooks

@layerhop/react — LayerHopProvider plus useQuote, useSwap, useOrderStatus, useTokens and friends, on top of the SDK and TanStack Query.

Open in ClaudeOpen in ChatGPT

@layerhop/react wraps the TypeScript SDK in React hooks built on TanStack Query v5. It is wallet-agnostic: useSwap asks your sign(tx) callback to sign, so it works with wagmi/viem, ethers or the Solana wallet adapter.

Source: github.com/layerhop/layerhop-js (packages/react) — source repository (opens at launch).

The hooks never hold keys. They fetch quotes and build unsigned transactions; your wallet signs.

Install#

Terminal
npm install @layerhop/react @layerhop/sdk @tanstack/react-query

Peer dependencies: react >= 18, @tanstack/react-query >= 5. Inside the monorepo use "@layerhop/react": "workspace:*".

LayerHopProvider#

TSX
import { LayerHopProvider } from "@layerhop/react";

<LayerHopProvider options={{ integrator: "myapp" }}>
  <App />
</LayerHopProvider>
Prop Description
client A new LayerHop({...}) instance (or any object with the same methods). Wins over options.
options LayerHopOptions for a client the provider creates.
queryClient Your TanStack QueryClient. If you leave it out, an existing <QueryClientProvider> above is reused, or the provider creates one.

Quickstart: quote → build → sign → report → track#

Amounts are integer strings in base units (100 USDC = "100000000").

TSX
import { useQuote, useSwap } from "@layerhop/react";
import { useAccount, useSendTransaction } from "wagmi";

function Swap() {
  const { address } = useAccount();
  const { sendTransactionAsync } = useSendTransaction();
  const quote = useQuote({
    srcChain: "base", srcToken: "USDC", amount: "100000000",
    dstChain: "solana", dstToken: "SOL",
    sender: address, recipient: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
  });
  const swap = useSwap({
    sign: async (tx) => {
      if (tx.vm !== "evm") throw new Error("connect a Solana wallet for this route");
      // send tx.approval first when present (see Approvals)
      return sendTransactionAsync({ to: tx.to as `0x${string}`, data: tx.data as `0x${string}`, value: BigInt(tx.value) });
    },
  });
  return (
    <button disabled={!quote.data || swap.isBusy} onClick={() => swap.start({ quote: quote.data!.best })}>
      {swap.phase === "idle" ? `Get ${quote.data?.best.dstAmount ?? "…"} lamports` : swap.phase}
    </button>
  );
}

useSwap#

useSwap({ sign, pollIntervalMs?, trackTimeoutMs?, onPhase? }) runs one swap as a state machine:

idle → quoting → building → awaiting-signature → submitted → tracking → done | error

  1. start(params) gets quotes and takes the best one. start({ quote }) skips quoting.
  2. It builds the transaction, then calls sign(tx, { quote }). Your callback returns the broadcast tx hash, either as a string or as { txHash }. For Solana txs with submitViaApi, return { signedTransaction } (base64) and the hook submits it for you.
  3. When the SDK has reportSubmitted, the hook reports the hash (POST /v1/orders/{quoteId}/submitted). This is best effort: a failed report never fails the swap.
  4. It polls the order status until filled → done. refunded or failed → error with a SwapError (code ORDER_REFUNDED / ORDER_FAILED). Same-chain providers always report unknown, which counts as done.

The result is { phase, quote, tx, txHash, status, error, start, reset, isBusy }. reset() goes back to idle and abandons a run in flight, for example while the wallet prompt is open.

Other hooks#

Hook What it does
useQuote(params, { enabled, refetchInterval, refreshMarginSeconds }) Quotes from every provider. Idle until params are complete and amount > 0. refetchInterval: "auto" (default) refetches 5 s before the best quote's expiresAt. 4xx errors are not retried.
useBuildTx() Mutation: mutate(quoteId) → the BuiltTx.
useOrderStatus(provider, id, { intervalMs, enabled }) Polls every 3 s until filled / refunded / failed, then stops. Adds isFinal.
useChains() Supported chains (cached for the session).
useTokens(chain) Built-in tokens for one chain; idle while chain is empty.
useLayerHop() The client from the provider.

Query keys are exported as layerhopKeys for invalidation. API failures are LayerHopApiErrors with a stable error code.