# `useWalletAuth`

:::warning
**External wallet (SIWE) connection is Ethereum-only.** Solana has no external wallet support via the wagmi bridge. For Solana, use only Openfort embedded wallets.
:::

Use [@openfort/react/wagmi](/docs/products/embedded-wallet/react) for React apps that need external wallet support (MetaMask, WalletConnect, Coinbase Wallet, etc.) alongside the embedded wallet.

::::steps

### Install dependencies

```sh
pnpm add @openfort/react @tanstack/react-query viem wagmi
```

### Provider setup

`OpenfortWagmiBridge` (from `@openfort/react/wagmi`) must sit inside `WagmiProvider` and wrap `OpenfortProvider`. Wagmi uses TanStack Query for caching, so wrap with `QueryClientProvider`. Nesting order:

`QueryClientProvider` → `WagmiProvider` → `OpenfortWagmiBridge` → `OpenfortProvider` → your app

```tsx [Providers.tsx]
import { AuthProvider, OpenfortProvider, RecoveryMethod } from "@openfort/react"
import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { WagmiProvider, createConfig } from "wagmi"
import { baseSepolia } from "viem/chains"

const config = createConfig(
  getDefaultConfig({
    appName: "Openfort Demo App",
    chains: [baseSepolia],
    walletConnectProjectId: "YOUR_WALLETCONNECT_PROJECT_ID",
  })
)
const queryClient = new QueryClient()

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <WagmiProvider config={config}>
        <OpenfortWagmiBridge>
          <OpenfortProvider
            publishableKey="YOUR_OPENFORT_PUBLISHABLE_KEY"
            walletConfig={{
              shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",
              ethereum: { chainId: 84532 },
              createEncryptedSessionEndpoint: "YOUR_BACKEND_ENDPOINT",
            }}
            uiConfig={{
              authProviders: [AuthProvider.EMAIL_OTP, AuthProvider.GUEST, AuthProvider.WALLET],
              walletRecovery: { defaultMethod: RecoveryMethod.AUTOMATIC },
            }}
          >
            {children}
          </OpenfortProvider>
        </OpenfortWagmiBridge>
      </WagmiProvider>
    </QueryClientProvider>
  )
}
```

<details>
  <summary style={{ margin: '20px 0 12px 0', fontSize: 16, fontWeight: 500 }}>WalletConnect support</summary>

  If using WalletConnect, add a project ID from the [WalletConnect dashboard](https://cloud.walletconnect.com):

  ```tsx
  const config = createConfig(
    getDefaultConfig({
      appName: "Openfort demo",
      chains: [baseSepolia],
      ssr: true,
      walletConnectProjectId: "YOUR_WALLET_CONNECT_PROJECT_ID",
    })
  )
  ```
</details>

### @openfort/react/wagmi API reference

**OpenfortWagmiBridge** — React component that bridges wagmi wallet state into Openfort. Must be inside `WagmiProvider` and wrap `OpenfortProvider`. No props except `children`.

:::info\[How the bridge works]
`OpenfortWagmiBridge` registers the Openfort embedded wallet as a native wagmi connector. Once it's in the tree, the embedded wallet and any connected external wallet (MetaMask, WalletConnect, etc.) both work through the same wagmi hooks — `useSendTransaction`, `useWriteContract`, `useBalance`, `useSignMessage`, `useWalletClient`. One API, regardless of wallet type. No Openfort-specific wrappers needed.
:::

**getDefaultConfig(opts)** — Returns wagmi `CreateConfigParameters`. Params: `appName`, `appIcon?`, `appDescription?`, `appUrl?`, `walletConnectProjectId?`, `coinbaseWalletPreference?`, `chains` (default: mainnet, polygon, optimism, arbitrum), plus any `CreateConfigParameters` overrides. Uses `getDefaultConnectors` for connectors when `connectors` not provided.

**getDefaultConnectors(opts)** — Returns `CreateConnectorFn[]`. Params: `app: { name, icon?, description?, url? }`, `walletConnectProjectId?`, `coinbaseWalletPreference?`. Includes Safe (in iframes), Injected (MetaMask), Coinbase Wallet, WalletConnect (if project ID provided).

**useWalletAuth(hookOptions?)** — Returns `{ availableWallets, connectWallet, linkWallet, walletConnectingTo, isLoading, isError, isSuccess, error }`. Optional `hookOptions` (default `{}`) supports `onSuccess`, `onError`, `throwOnError` like other auth hooks. Use `connectWallet(connectorId, callbacks?)` or `linkWallet(connectorId, callbacks?)` for the connect/link flow. See [SIWE connection flow](#usewalletauth-siwe-connection-flow) below.

<h3 id="usewalletauth-siwe-connection-flow">useWalletAuth — SIWE connection flow</h3>

`useWalletAuth` from `@openfort/react/wagmi` is the recommended way to connect external wallets with Sign-In with Ethereum. It handles connect + SIWE sign-in in one flow.

```tsx
import { useWalletAuth } from "@openfort/react/wagmi"

function WalletConnectList() {
  const {
    availableWallets,
    connectWallet,
    linkWallet,
    walletConnectingTo,
    isLoading,
    isError,
    error,
  } = useWalletAuth({
    onSuccess: () => console.log("Connected"),
    onError: (err) => console.error(err),
  })

  return (
    <div>
      {walletConnectingTo && <p>Connecting to {walletConnectingTo}…</p>}
      {availableWallets.map((w) => (
        <button
          key={w.id}
          onClick={() =>
            connectWallet(w.id, {
              onConnect: () => console.log("Connected"),
              onError: (msg, openfortError) => console.error(msg, openfortError),
            })
          }
          disabled={isLoading}
        >
          {w.name}
        </button>
      ))}
    </div>
  )
}
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `hookOptions` | `OpenfortHookOptions?` | Optional. `onSuccess`, `onError`, `throwOnError`. Default `{}`. |

| Return | Type | Description |
|--------|------|-------------|
| `availableWallets` | `AvailableWallet[]` | List of connectable wallets (excludes Openfort embedded). See [AvailableWallet](#availablewallet-type) below. |
| `connectWallet(connectorId, callbacks?)` | `(string, WalletAuthCallbacks?) => Promise<void>` | Connect + SIWE sign-in (new session) |
| `linkWallet(connectorId, callbacks?)` | `(string, WalletAuthCallbacks?) => Promise<void>` | Connect + SIWE link to existing account |
| `walletConnectingTo` | `string \| null` | Connector id currently in progress, or `null` when idle. Use for “Connecting to MetaMask…”-style UI. |
| `isLoading` | `boolean` | `true` while a connect/link is in progress. |
| `isError` | `boolean` | `true` when the last connect/link failed. |
| `isSuccess` | `boolean` | `true` when the last connect/link succeeded. |
| `error` | `OpenfortError \| null` | Error from the last failed connect/link, or `null`. |

**WalletAuthCallbacks** (per call) — `{ onConnect?: () => void, onError?: (error: string, openfortError?: OpenfortError) => void }`. Called in addition to `hookOptions.onSuccess` / `hookOptions.onError`. On success: hook sets status to `'success'`, calls `onSuccess(hookOptions, {})`, then the per-call `onConnect`. On error: hook sets status to `'error'`, calls `onError(hookOptions, error)`, then the per-call `onError(message, openfortError)`. String errors are normalized to `OpenfortError` for `hookOptions.onError` and `error`. If the app is already connected and the bridge disconnects before connecting the new wallet, state is reset and both hook and per-call error callbacks are invoked.

:::info\[API compatibility]
Existing code that only uses `availableWallets`, `connectWallet`, and `linkWallet` continues to work. New code can optionally use `hookOptions`, `walletConnectingTo`, and `isLoading` / `isError` / `isSuccess` / `error` for loading and error UI.
:::

<h4 id="availablewallet-type">AvailableWallet type</h4>

```ts
type AvailableWallet = {
  id: string
  name: string
  icon?: string
  connector: OpenfortEthereumBridgeConnector  // Openfort bridge connector for the external wallet
}
```

`connectWallet` creates a new session; `linkWallet` links the external wallet to the current user's account.

### Using wagmi hooks for transactions

When using `OpenfortWagmiBridge`, wagmi's native hooks work with the Openfort embedded wallet. Example with `useWriteContract` for an ERC-20 transfer:

```tsx
import { useWriteContract } from "wagmi"
import { parseUnits } from "viem"

const usdcAbi = [
  { name: "transfer", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [{ name: "success", type: "bool" }] },
] as const

function TransferButton() {
  const { writeContract, data: hash, isPending } = useWriteContract()

  return (
    <button
      onClick={() =>
        writeContract({
          address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          abi: usdcAbi,
          functionName: "transfer",
          args: ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", parseUnits("10", 6)],
        })
      }
      disabled={isPending}
    >
      Send USDC
    </button>
  )
}
```

`useSendTransaction`, `useAccount`, `useBalance`, `useSignMessage`, and `useWalletClient` also work through the bridge.

### Using wallet\_sendCalls directly

Use `useWalletClient` from wagmi for direct `wallet_sendCalls` RPC:

```tsx
import { useWalletClient } from "wagmi"
import { encodeFunctionData, parseUnits } from "viem"

const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
const usdcAbi = [
  { name: "transfer", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [{ name: "success", type: "bool" }] },
] as const

function useSendUsdc() {
  const { data: walletClient } = useWalletClient()

  return async (to: `0x${string}`, amount: string) => {
    if (!walletClient?.chain || !walletClient.account) throw new Error("Wallet client not ready")
    const data = encodeFunctionData({ abi: usdcAbi, functionName: "transfer", args: [to, parseUnits(amount, 6)] })
    return walletClient.request({
      method: "wallet_sendCalls",
      params: [{
        version: "1.0",
        chainId: `0x${walletClient.chain.id.toString(16)}`,
        from: walletClient.account.address,
        calls: [{ to: usdcAddress, value: "0x0", data }],
      }],
    })
  }
}
```

### Gas sponsorship

Wire `ethereumFeeSponsorshipId` into `walletConfig.ethereum`. Create policies at [Dashboard → Gas sponsorship](https://dashboard.openfort.io/policies):

```tsx
walletConfig={{
  shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",
  ethereum: {
    chainId: 84532,
    ethereumFeeSponsorshipId: "pol_...",
  },
}}
```

### EOA wallets on a custom chain

For EOA wallets on a custom chain, define the chain with `defineChain` and pass it to `getDefaultConfig`. Set `accountType: AccountTypeEnum.EOA` in `walletConfig.ethereum` and `enforceSupportedChains: false` in `uiConfig`:

```tsx [Providers.tsx]
import { defineChain } from "viem"
import { WagmiProvider, createConfig, http } from "wagmi"
import { AccountTypeEnum, AuthProvider, OpenfortProvider, RecoveryMethod } from "@openfort/react"
import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"

const customMainnet = defineChain({
  id: 12345,
  name: "Custom Mainnet",
  network: "custom",
  nativeCurrency: { name: "Cust", symbol: "CUST", decimals: 18 },
  rpcUrls: {
    default: { http: ["https://rpc.custom.xyz"] },
  },
  blockExplorers: {
    default: { name: "customscan", url: "https://customscan.io" },
  },
  testnet: true,
})

const wagmiConfig = createConfig(
  getDefaultConfig({
    appName: "Your App Name",
    chains: [customMainnet],
    transports: { [customMainnet.id]: http() },
    walletConnectProjectId: "YOUR_WALLETCONNECT_PROJECT_ID",
  })
)

const queryClient = new QueryClient()

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <WagmiProvider config={wagmiConfig}>
        <OpenfortWagmiBridge>
          <OpenfortProvider
            publishableKey="YOUR_OPENFORT_PUBLISHABLE_KEY"
            walletConfig={{
              shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",
              ethereum: { chainId: customMainnet.id, rpcUrls: { [customMainnet.id]: "https://rpc.custom.xyz" }, accountType: AccountTypeEnum.EOA },
            }}
            uiConfig={{
              enforceSupportedChains: false,
            }}
          >
            {children}
          </OpenfortProvider>
        </OpenfortWagmiBridge>
      </WagmiProvider>
    </QueryClientProvider>
  )
}
```

### Ethereum-only UI options that require wagmi

* `AuthProvider.WALLET` — external wallet auth via SIWE
* `enforceSupportedChains` — enforces wagmi's chain list
* `walletConnectCTA` / `walletConnectName` — WalletConnect UI options
* `truncateLongENSAddress` — ENS display
* `linkWalletOnSignUp` — link external wallet on signup

::::

## Related

* [Quickstart](/docs/products/embedded-wallet/react) — Provider setup with wagmi
* [useConnectWithSiwe](/docs/products/embedded-wallet/react/hooks/useConnectWithSiwe) — Lower-level SIWE hook
* [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions) — Send transactions with wagmi hooks
