# Wallets and embedded wallets

> Execute LayerHop quotes from any wallet stack with @layerhop/wagmi and @layerhop/solana. EIP-5792 batching sends approve + swap (+ fee) in one confirmation, with snippets for wagmi/Reown, Privy, Dynamic, thirdweb and Coinbase Smart Wallet.

Source: https://docs.layerhop.com/guides/wallets

Two small packages turn a LayerHop quote into a confirmed transaction in the user's wallet. **`@layerhop/wagmi`** covers EVM chains through wagmi or a viem `WalletClient`. **`@layerhop/solana`** covers Solana through `@solana/wallet-adapter`, Wallet Standard or an injected provider. Both build the transaction, ask the wallet to sign it, and report the hash with [`reportSubmitted`](/sdk) so [order tracking](/concepts/order-tracking) and webhooks work. Neither one holds keys: every transaction is confirmed by the user in their wallet.

> Test keys (`lh_test_…`) return placeholder transactions. Both packages detect them (`sandbox: true`), never pass them to the wallet, and report a placeholder hash so the test order fills. See [test mode](/guides/test-mode).

## Install

```bash
# EVM (wagmi v2 / viem v2)
npm install @layerhop/wagmi @layerhop/sdk wagmi viem @tanstack/react-query
# Solana
npm install @layerhop/solana @layerhop/sdk @solana/web3.js
```

> `@layerhop/wagmi` and `@layerhop/solana` are on npm from 0.7.0: `npm install @layerhop/wagmi` or `npm install @layerhop/solana`.

## One confirmation with EIP-5792

An EVM route can need up to three calls: an ERC-20 `approve` (see [approvals](/concepts/approvals)), the swap or payment, and `extraTxs` such as LayerHop's fee transfer on a same-token [checkout](/guides/checkout) payment. `executeQuote` handles them like this:

1. It switches the wallet to the transaction's chain if needed.
2. It reads the allowance. The approval call is added only when the allowance is short, and only for the exact amount (never unlimited).
3. If there is more than one call and `wallet_getCapabilities` reports atomic batching for the chain (`atomic.status` is `supported` or `ready`), every call goes in **one** `wallet_sendCalls` request with `atomicRequired: true`. The user confirms once and the calls succeed or fail together. The returned `hash` is the mined transaction's hash, read from `wallet_getCallsStatus`.
4. Otherwise it sends them one by one: approve and wait for it, then the main call, then each extra call. An extra call that fails or is rejected never fails the payment. If the wallet rejects the batch as unsupported (codes 4200, 5700, 5710, 5740, 5750, 5760 or -32601), it falls back to the same sequential flow. A user rejection (4001) is never retried.

Pass `batch: "never"` to always send sequentially, or `batch: "require"` to throw instead of falling back. Smart-contract wallets (Coinbase Smart Wallet, Safe) and EIP-7702 accounts (for example MetaMask smart accounts) batch. Most plain EOA extensions send sequentially.

## wagmi and Reown AppKit

`executeQuote(config, { quote })` is the action and `useExecuteQuote()` is the hook. Both work with any wagmi config. With Reown AppKit, use the adapter's config (`wagmiAdapter.wagmiConfig`). This is how the LayerHop app itself sends.

```tsx
import { LayerHopProvider, useExecuteQuote, useLayerHopQuote, useOrderStatus } from "@layerhop/wagmi";
import { useAccount } from "wagmi";

// Inside <WagmiProvider> + <QueryClientProvider> + <LayerHopProvider options={{ integrator: "myapp" }}>
function Swap() {
  const { address } = useAccount();
  const quote = useLayerHopQuote(address && {
    srcChain: "base", srcToken: "USDC", amount: "25000000",
    dstChain: "arbitrum", dstToken: "ETH", sender: address, recipient: address,
  });
  const exec = useExecuteQuote();
  const best = quote.data?.best;
  const order = useOrderStatus(best?.provider, exec.hash);
  return (
    <button disabled={!best || exec.isPending} onClick={() => best && exec.execute({ quote: best })}>
      {exec.isPending ? exec.status : exec.mode === "batch" ? "Sent in one confirmation" : "Swap"}
    </button>
  );
}
```

`exec.status` goes `idle → building → (switching) → (approving) → signing → reporting → submitted`, or ends in `error`. `exec.mode` is `batch`, `sequential` or `sandbox`. Without React, use the action:

```ts
import { executeQuote } from "@layerhop/wagmi/actions";
import { LayerHop } from "@layerhop/sdk";

const lh = new LayerHop({ apiKey: process.env.LAYERHOP_API_KEY });
const { best } = await lh.quote(params);
const sent = await executeQuote(wagmiConfig, { quote: best, client: lh });
await sent.wait(); // true once confirmed
```

For Solana with Reown AppKit, wrap the Solana `walletProvider`:

```ts
import { useAppKitProvider } from "@reown/appkit/react";
import type { Provider } from "@reown/appkit-adapter-solana/react";
import { executeSolanaQuote, fromInjectedProvider } from "@layerhop/solana";

const { walletProvider } = useAppKitProvider<Provider>("solana");
await executeSolanaQuote(fromInjectedProvider(walletProvider), { quote: best, client: lh, connection });
```

## Coinbase Smart Wallet

Coinbase Smart Wallet supports EIP-5792, so approve + swap (+ fee) take one passkey confirmation. With plain wagmi, add the connector. `smartWalletOnly` hides the extension and mobile app, and `all` offers both.

```ts
import { createConfig, http } from "wagmi";
import { base } from "wagmi/chains";
import { coinbaseWallet } from "wagmi/connectors";

export const wagmiConfig = createConfig({
  chains: [base],
  connectors: [coinbaseWallet({ appName: "My App", preference: "smartWalletOnly" })],
  transports: { [base.id]: http() },
});
// then: useExecuteQuote() / executeQuote(wagmiConfig, { quote })
```

Reown AppKit includes the Coinbase connector by default. Its `coinbasePreference` option takes the same values (the LayerHop app sets `"all"`).

## Privy

Privy's wagmi integration gives you a normal wagmi config, so `useExecuteQuote` works unchanged. Import `createConfig` and `WagmiProvider` from `@privy-io/wagmi` and nest `PrivyProvider` → `QueryClientProvider` → `WagmiProvider`.

```tsx
import { PrivyProvider } from "@privy-io/react-auth";
import { createConfig, WagmiProvider } from "@privy-io/wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http } from "wagmi";
import { base } from "viem/chains";
import { LayerHopProvider } from "@layerhop/wagmi";

const wagmiConfig = createConfig({ chains: [base], transports: { [base.id]: http() } });
const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <PrivyProvider appId="your-privy-app-id">
      <QueryClientProvider client={queryClient}>
        <WagmiProvider config={wagmiConfig}>
          <LayerHopProvider options={{ integrator: "myapp" }}>{children}</LayerHopProvider>
        </WagmiProvider>
      </QueryClientProvider>
    </PrivyProvider>
  );
}
```

Privy's Solana hooks work with bytes. Wrap them in a `SolanaSigner`:

```ts
import { useSignAndSendTransaction, useSignTransaction, useWallets } from "@privy-io/react-auth/solana";
import { VersionedTransaction } from "@solana/web3.js";
import { bytesToBase58, executeSolanaQuote, type SolanaSigner } from "@layerhop/solana";

const { wallets } = useWallets();
const { signTransaction } = useSignTransaction();
const { signAndSendTransaction } = useSignAndSendTransaction();
const wallet = wallets[0]!;
const signer: SolanaSigner = {
  signTransaction: async (tx) =>
    VersionedTransaction.deserialize((await signTransaction({ transaction: tx.serialize(), wallet })).signedTransaction),
  signAndSendTransaction: async (tx) =>
    bytesToBase58((await signAndSendTransaction({ transaction: tx.serialize(), wallet })).signature),
};
await executeSolanaQuote(signer, { quote: best, client: lh, connection });
```

## Dynamic

Dynamic syncs its wallets into wagmi through `DynamicWagmiConnector`. Inside it, `useExecuteQuote` works unchanged.

```tsx
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
import { SolanaWalletConnectors } from "@dynamic-labs/solana";
import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createConfig, http, WagmiProvider } from "wagmi";
import { base } from "viem/chains";

const wagmiConfig = createConfig({ chains: [base], multiInjectedProviderDiscovery: false, transports: { [base.id]: http() } });
const queryClient = new QueryClient();

<DynamicContextProvider settings={{ environmentId: "ENV_ID", walletConnectors: [EthereumWalletConnectors, SolanaWalletConnectors] }}>
  <WagmiProvider config={wagmiConfig}>
    <QueryClientProvider client={queryClient}>
      <DynamicWagmiConnector>{/* your app: useExecuteQuote() */}</DynamicWagmiConnector>
    </QueryClientProvider>
  </WagmiProvider>
</DynamicContextProvider>;
```

For Solana, Dynamic's signer has `signTransaction` and `signAndSendTransaction` (it resolves `{ signature }`), which is the shape `fromInjectedProvider` expects:

```ts
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { isSolanaWallet } from "@dynamic-labs/solana";
import { executeSolanaQuote, fromInjectedProvider } from "@layerhop/solana";

const { primaryWallet } = useDynamicContext();
if (primaryWallet && isSolanaWallet(primaryWallet)) {
  const signer = await primaryWallet.getSigner();
  await executeSolanaQuote(fromInjectedProvider(signer), { quote: best, client: lh, connection });
}
```

## thirdweb

thirdweb v5 converts its wallets (in-app, smart and external) to a viem `WalletClient`. Pass that client to `executeQuoteWithWalletClient`.

```ts
import { viemAdapter } from "thirdweb/adapters/viem";
import { base } from "thirdweb/chains";
import { useActiveWallet } from "thirdweb/react";
import { executeQuoteWithWalletClient } from "@layerhop/wagmi/actions";

const wallet = useActiveWallet();
// `client` is your thirdweb client (createThirdwebClient)
const walletClient = viemAdapter.wallet.toViem({ client, chain: base, wallet: wallet! });
await executeQuoteWithWalletClient(walletClient, { quote: best, client: lh });
```

`executeQuoteWithWalletClient` works with any viem `WalletClient`, including `createWalletClient({ transport: custom(provider) })` over an EIP-1193 provider. Reads such as the allowance and receipts go through the wallet's provider. Pass `publicClient` to use your own RPC instead.

## Plain @solana/wallet-adapter

```ts
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { executeSolanaQuote, fromWalletAdapter } from "@layerhop/solana";

const wallet = useWallet();
const { connection } = useConnection();
const sent = await executeSolanaQuote(fromWalletAdapter(wallet), { quote: best, client: lh, connection });
await sent.wait();
```

`executeSolanaQuote` picks the send mode from the transaction itself:

- **`submitViaApi`:** the wallet only signs and the signed bytes go to `POST /v1/orders/submit` (see [submit via API](/concepts/submit-via-api)).
- **Co-signed:** the provider already signed it. The wallet only signs, and your `connection` broadcasts the exact bytes.
- **Plain:** the wallet signs and sends.

For Wallet Standard wallets, use `fromWalletStandard(wallet, account)`.

## Related

- [React hooks](/sdks/react): `useQuote`, `useSwap` and `useOrderStatus`, wallet-agnostic.
- [TypeScript SDK](/sdk): `quote`, `buildTx`, `submit`, `reportSubmitted`.
- [Approvals](/concepts/approvals) and [non-custodial design](/concepts/non-custodial).
