@layerhop/react
v0.7.0
Published
React hooks for LayerHop cross-chain swaps: quotes, transactions, order tracking and a wallet-agnostic useSwap state machine
Maintainers
Readme
@layerhop/react
React hooks for LayerHop cross-chain and same-chain swaps, built on
@layerhop/sdk and TanStack Query v5.
Never holds keys. The hooks fetch quotes and build unsigned transactions. Signing is done by a
sign(tx)callback you provide (wagmi/viem, ethers, Solana wallet adapter…), so the package works with any wallet stack.
Install
npm install @layerhop/react @layerhop/sdk @tanstack/react-query
# peers: react >= 18, @tanstack/react-query >= 5Setup
import { LayerHopProvider } from "@layerhop/react";
export function App() {
return (
<LayerHopProvider options={{ integrator: "myapp" }}>
<Swap />
</LayerHopProvider>
);
}LayerHopProvider takes either client (a new LayerHop({...}) instance) or options (passed to
new LayerHop). It reuses a <QueryClientProvider> above it, accepts one via queryClient, or
creates its own.
Quickstart: quote → build → sign → report → track
Amounts are integer strings in base units (100 USDC = "100000000"), never floats.
import { useQuote, useSwap } from "@layerhop/react";
import { useAccount, useSendTransaction } from "wagmi";
function Swap() {
const { address } = useAccount();
const { sendTransactionAsync } = useSendTransaction();
const params = {
srcChain: "base", srcToken: "USDC", amount: "100000000",
dstChain: "solana", dstToken: "SOL",
sender: address, recipient: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
};
// Refetches shortly before the best quote's expiresAt, so the quote on screen stays buildable.
const quote = useQuote(params);
const swap = useSwap({
// Your wallet signs. Send tx.approval first when present (EVM), then return the tx hash.
sign: async (tx) => {
if (tx.vm !== "evm") throw new Error("connect a Solana wallet for this route");
return sendTransactionAsync({ to: tx.to as `0x${string}`, data: tx.data as `0x${string}`, value: BigInt(tx.value) });
},
});
return (
<div>
<p>Best: {quote.data?.best.provider} → {quote.data?.best.dstAmount} lamports</p>
<button disabled={!quote.data || swap.isBusy} onClick={() => swap.start({ quote: quote.data!.best })}>Swap</button>
<p>{swap.phase} {swap.status?.state} {swap.error?.message}</p>
</div>
);
}useSwap runs idle → quoting → building → awaiting-signature → submitted → tracking → done | error:
start(params)quotes (orstart({ quote })reuses one), then builds the tx.- It calls your
sign(tx, { quote }). Return the broadcast tx hash (stringor{ txHash }). For Solana txs withsubmitViaApi: true, return{ signedTransaction }(base64) instead. The hook then sends it throughPOST /v1/orders/submit. - If the SDK has
reportSubmitted, it reports the hash (POST /v1/orders/{quoteId}/submitted). This is best effort: a failed report never fails the swap. - It polls the order status until
filled(→done), orrefunded/failed. Those two end inerror, with aSwapErrorwhosecodeisORDER_REFUNDEDorORDER_FAILED. Same-chain providers (Jupiter, 0x, KyberSwap) reportunknown, which counts as done.
reset() returns to idle and abandons any run still in flight.
Hooks
| Hook | Returns |
|---|---|
| useChains() | query of supported chains (API chains() when the client has it, else the SDK's built-in list) |
| useTokens(chain) | query of the built-in tokens of one chain; idle while chain is empty |
| useQuote(params, { enabled, refetchInterval: "auto" \| ms \| false, refreshMarginSeconds }) | query of QuoteResponse; idle until params are complete and amount > 0; 4xx errors are not retried |
| useBuildTx() | mutation: mutate(quoteId) → BuiltTx |
| useOrderStatus(provider, id, { intervalMs, enabled }) | query of OrderStatus; polls until final; adds isFinal |
| useSwap({ sign, pollIntervalMs, trackTimeoutMs, onPhase }) | { phase, quote, tx, txHash, status, error, start, reset, isBusy } |
| useLayerHop() | the client from the provider |
Query keys are exported as layerhopKeys, so you can invalidate them yourself. API errors are
LayerHopApiErrors with a stable code (see error codes).
License
MIT
