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

# Use Turnkey wallets with Relay

## Overview

[Relay](https://relay.link/) is a cross-chain protocol for bridging tokens and swapping assets across networks. This guide demonstrates integrating Turnkey wallets with Relay to enable cross-chain bridging and same-chain token swaps, covering authentication, quote fetching, multi-step execution, and intent status polling.

The [`relay turnkey example`](https://github.com/relayprotocol/relay-wallet-provider-examples/tree/main/turnkey) is a Next.js application that bridges ETH from Base to Arbitrum and swaps ETH for USDC on Base.

***

## Prerequisites

Complete the Turnkey Quickstart first. You'll need:

* A Turnkey organization and Auth Proxy Config ID
* (Optional) A [Relay API key](https://docs.relay.link/references/api/api-keys) for higher rate limits

***

## Installation

```bash theme={"system"}
npm install @turnkey/react-wallet-kit @turnkey/viem viem wagmi
```

## Wallet configuration

The `@turnkey/react-wallet-kit` package handles browser-based authentication. Each new user gets a Turnkey sub-organization with an HD wallet provisioned automatically on first login. Use `@turnkey/viem`'s `createAccount` to turn the active session into a viem `Account`:

```tsx theme={"system"}
"use client";

import { useTurnkey } from "@turnkey/react-wallet-kit";
import { createAccount } from "@turnkey/viem";
import { createWalletClient, createPublicClient, http } from "viem";

export function useTurnkeyWallet() {
  const { httpClient, session, fetchWalletAccounts, wallets } = useTurnkey();

  const accounts = await fetchWalletAccounts({ wallet: wallets[0] });
  const ethAccount = accounts[0];

  const turnkeyAccount = await createAccount({
    client: httpClient!,
    organizationId: ethAccount.organizationId,
    signWith: ethAccount.address,
    ethereumAddress: ethAccount.address,
  });

  function makeWalletClient(chain: Chain) {
    return createWalletClient({ account: turnkeyAccount, chain, transport: http() });
  }

  function makePublicClient(chain: Chain) {
    return createPublicClient({ chain, transport: http() });
  }
}
```

## Relay integration

Call the Relay API from Next.js server actions to keep the API key server-side. The `/quote/v2` endpoint returns an executable quote with all transaction calldata and signature payloads:

```tsx theme={"system"}
"use server";

export async function getQuote(params: QuoteRequest): Promise<QuoteResponse> {
  const res = await fetch("https://api.relay.link/quote/v2", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(process.env.RELAY_API_KEY
        ? { Authorization: `Bearer ${process.env.RELAY_API_KEY}` }
        : {}),
    },
    body: JSON.stringify(params),
  });
  return res.json();
}
```

Request a bridge quote by specifying origin and destination chains and currencies:

```tsx theme={"system"}
import { parseEther } from "viem";
import { base, arbitrum } from "viem/chains";

const NATIVE = "0x0000000000000000000000000000000000000000";

const quote = await getQuote({
  user: address,
  originChainId: base.id,
  destinationChainId: arbitrum.id,
  originCurrency: NATIVE,
  destinationCurrency: NATIVE,
  amount: parseEther("0.001").toString(),
  tradeType: "EXACT_INPUT",
});
```

## Executing a quote

A Relay quote contains an array of steps, each of kind `"transaction"` or `"signature"`. Iterate through all steps and dispatch them in order using the Turnkey-backed wallet client:

```tsx theme={"system"}
"use client";

export async function executeQuote({ quote, account, chains, makeWalletClient, makePublicClient }) {
  for (const step of quote.steps) {
    for (const item of step.items) {
      if (item.status === "complete") continue;

      const chain = chains.find((c) => c.id === item.data.chainId) ?? chains[0];

      if (step.kind === "signature") {
        const client = makeWalletClient(chain);
        const signature = await signItem(client, item);
        await submitSignature(item.data.post.endpoint, signature, item.data.post.body);

      } else if (step.kind === "transaction") {
        const client = makeWalletClient(chain);
        const publicClient = makePublicClient(chain);

        const hash = await client.sendTransaction({
          account,
          chain,
          to: item.data.to,
          data: item.data.data ?? "0x",
          value: item.data.value ? BigInt(item.data.value) : 0n,
        });

        await publicClient.waitForTransactionReceipt({ hash });
      }
    }
  }
}
```

## Polling for completion

After submitting a step (e.g. the deposit transaction), Relay’s solver detects the deposit and fills the request on the destination chain. To know when the full flow is done, poll the **intent status** endpoint.

Each step item in the quote can include a `check` object with an endpoint to call:

```tsx theme={"system"}
// From the quote response: item.check
{
  "endpoint": "/intents/status?requestId=0x8a9b3c...",
  "method": "GET"
}
```

Call this endpoint (e.g. `https://api.relay.link/intents/status/v3?requestId=<requestId>`) periodically (e.g. once per second) until the status indicates completion. The `requestId` is available on each step in the quote response.

**Status lifecycle:** Typical values include `waiting` (user submitted deposit, not yet indexed), `depositing` (deposit confirmed, preparing fill), `pending` (deposit indexed, solver preparing fill on destination), and `success` (fill executed, funds reached the recipient). For the full list and behavior, see Relay’s [status lifecycle](https://docs.relay.link/references/api/quickstart#status-lifecycle) documentation.

For a real-time stream instead of polling, you can use [Relay’s WebSocket API](https://docs.relay.link/references/api/api_guides/websockets).

## Handling EIP-191 and EIP-712 signatures

Some Relay steps (particularly for swaps) require off-chain signatures before the deposit transaction. Handle both signature kinds with the Turnkey-backed viem wallet client:

```tsx theme={"system"}
"use client";

import { type WalletClient, type Hex } from "viem";

async function signItem(walletClient: WalletClient, item: StepItem): Promise<Hex> {
  const sign = item.data.sign!;

  if (sign.signatureKind === "eip191") {
    return walletClient.signMessage({
      account: walletClient.account!,
      message: sign.message,
    });
  }

  // EIP-712: strip EIP712Domain — viem constructs it from the domain object
  const { EIP712Domain: _, ...types } = sign.types ?? {};

  return walletClient.signTypedData({
    account: walletClient.account!,
    domain: sign.domain as any,
    types,
    primaryType: sign.primaryType!,
    message: sign.value as any,
  });
}
```

## Key takeaways

✅ Successfully implemented:

* Turnkey authentication using `@turnkey/react-wallet-kit` with passkey and email OTP support
* Relay cross-chain bridging and same-chain swaps via the `/quote/v2` API
* Multi-step quote execution handling both `transaction` and `signature` step kinds
* Intent status polling to confirm completion using the quote’s `check` endpoint and `requestId`
* EIP-191 and EIP-712 signing through the Turnkey-backed viem account
* Server-side API key protection using Next.js server actions

To dive deeper into Relay’s API (chain config, quoting, execution, and monitoring), see the [Relay API Quickstart](https://docs.relay.link/references/api/quickstart).
