> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ravn.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# @ravnexchange/react

> Hooks for dapps building their own swap UI on top of @ravnexchange/sdk.

Three hooks, one per [`@ravnexchange/sdk`](/sdks/core) call that benefits from React state: quote
fetching with expiry tracking, execute as an async action, and status as a poll. You still bring
your own wallet; these hooks never sign or send anything.

<ParamField body="react" type="peer dependency">Requires React 18 or later.</ParamField>
<ParamField body="@ravnexchange/sdk" type="peer dependency">Create one `RavnClient` and pass it to every hook.</ParamField>

```bash theme={null}
npm install @ravnexchange/react @ravnexchange/sdk
```

```ts theme={null}
import { RavnClient } from "@ravnexchange/sdk";
import { useRavnQuote, useRavnExecute, useRavnStatus } from "@ravnexchange/react";

const client = new RavnClient(); // create once, pass to every hook below
```

## useRavnQuote

```ts theme={null}
const { quote, isLoading, error, isExpired, refetch } = useRavnQuote(client, {
  inputChainId: 1,
  outputChainId: -2,
  inputToken: "0xEeee...EEeE",
  outputToken: "So1111...1112",
  inputAmount: amount, // e.g. from a controlled input
  userAddress: address,
});
```

Fetches whenever `params` changes (compared by value, not by reference, so a fresh object
literal on every render is fine), and sets `isExpired` on its own timer when `quote.expiresAt`
passes: no polling, one `setTimeout` per quote. Pass `params: null` to skip fetching, for
example when the amount hasn't been entered yet.

<Note>
  Does not auto-refetch on expiry. Silently re-pricing behind the user's back is worse than
  telling them to ask again, so call `refetch()` yourself in response to `isExpired`.
</Note>

<ResponseField name="quote" type="QuoteDTO | null" />

<ResponseField name="isLoading" type="boolean" />

<ResponseField name="error" type="unknown">A `RavnApiError` on a failed request. See [Errors](/sdks/core#errors).</ResponseField>
<ResponseField name="isExpired" type="boolean">True once `quote.expiresAt` has passed. Stop letting the user sign, and call `refetch()`.</ResponseField>

<ResponseField name="refetch" type="() => void" />

## useRavnExecute

```ts theme={null}
const { execute, data, isLoading, error } = useRavnExecute(client);

const handleSwap = async () => {
  const execution = await execute({ quoteToken: quote.quoteToken });
  switch (execution.executionType) {
    case "TRANSACTION": /* ... */ break;
    case "SIGNATURE": /* ... */ break;
    case "DEPOSIT": /* ... */ break;
  }
};
```

A thin state wrapper around `client.execute()`. Branch on the returned `executionType` and hand
it to whatever signer you already have: wagmi, ethers, a hardware wallet. Same contract as the
[Quickstart](/quickstart).

<ResponseField name="execute" type="(params: ExecuteParams) => Promise<ExecutionDTO>">Also throws on failure. Handle the rejection or read `error`, whichever fits your flow.</ResponseField>

<ResponseField name="data" type="ExecutionDTO | null" />

<ResponseField name="isLoading" type="boolean" />

<ResponseField name="error" type="unknown" />

## useRavnStatus

```ts theme={null}
const { status, isLoading, error } = useRavnStatus(
  client,
  execution ? { quoteToken: execution.quoteToken, ref: statusRef } : null,
  4_000 // poll interval in ms, optional, default 4000
);
```

Polls [`/status`](/api-reference/status) until the status leaves `pending`/`processing`:
`expired`, `success`, `refunded`, `failed`, `not_found`, and `unknown` all stop the poll. Pass
`params: null` to not poll at all, for example before execution has produced a ref.

<Note>
  BTC-settled `DEPOSIT` routes (Chainflip, NEAR Intents, Relay, Rift) commonly take 30+ minutes, tracked
  for up to 90 minutes. The 4s default is fine for EVM/Solana swaps, but pass a longer interval,
  `10_000`–`15_000`, for a BTC deposit so you're not polling every 4 seconds for half an hour.
</Note>

<Warning>
  `expired` stops this hook, but it isn't a final outcome. Per [Status](/api-reference/status), a
  refund can still land later and flip it to `refunded`. This hook has no `refetch`, so to catch
  that later transition, call `client.getStatus()` yourself after a delay, or remount the hook.
</Warning>

<Note>
  Keeps polling through a transient network or `5xx` error. Stops immediately on a permanent one,
  `UNAUTHORIZED`, `INVALID_REQUEST`, `QUOTE_INVALID`, or `NOT_FOUND`, since retrying with the same
  arguments can never turn one of those into success.
</Note>

<ResponseField name="status" type="StatusDTO | null" />

<ResponseField name="isLoading" type="boolean" />

<ResponseField name="error" type="unknown" />

## Putting it together

`statusRef` comes back on `execute()` for `DEPOSIT`, and on some `TRANSACTION`s. For
`SIGNATURE`, you get it from `submitSignature()` instead, after signing, so track it in state
rather than reading it off `execution` directly. A `TRANSACTION` without one uses the
transaction hash your wallet returns as the ref. Pair every ref with `execution.quoteToken`, not
`quote.quoteToken`, and poll it to a terminal state like any other swap. That poll is what
settles the swap for fee payout. See
[Execution Types](/execution-types) and [Settlement](/api-reference/status#settlement).

```tsx theme={null}
function SwapButton({ params }: { params: GetQuoteParams }) {
  const { quote, isExpired, refetch } = useRavnQuote(client, params);
  const { execute } = useRavnExecute(client);
  const [track, setTrack] = useState<{ quoteToken: string; ref: string } | null>(null);

  const { status } = useRavnStatus(
    client,
    track,
    10_000 // longer than the 4s default: this swap may include a BTC DEPOSIT leg
  );

  const handleSwap = async () => {
    const result = await execute({ quoteToken: quote!.quoteToken });
    const { quoteToken } = result; // execute's token, not quote.quoteToken
    if (result.executionType === "DEPOSIT") {
      setTrack({ quoteToken, ref: result.statusRef });
    } else if (result.executionType === "SIGNATURE") {
      const sig = await wallet.signTypedData(result.typedData);
      const { statusRef } = await client.submitSignature({ quoteToken, signature: sig });
      setTrack({ quoteToken, ref: statusRef });
    } else {
      // TRANSACTION: send result.approval first when present, and wait for it to be mined.
      const hash = await wallet.sendTransaction(result.transaction);
      setTrack({ quoteToken, ref: result.statusRef ?? hash });
    }
  };

  if (isExpired) return <button onClick={refetch}>Refresh quote</button>;
  return (
    <button onClick={handleSwap}>
      {status ? `Status: ${status.status}` : "Swap"}
    </button>
  );
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.