Skip to main content

Wagmi Integration

This guide demonstrates how to integrate the CoW Protocol SDK with Wagmi and Viem for a production-ready React application with proper wallet management.

Why Wagmi?

Wagmi provides:
  • Easy wallet connection with multiple providers
  • Automatic account and network management
  • React hooks for blockchain interactions
  • TypeScript support
  • Better developer experience

Installation

npm install @cowprotocol/cow-sdk @cowprotocol/sdk-viem-adapter wagmi viem @tanstack/react-query

Setup Wagmi

Configure Wagmi with your app settings:
import { http, createConfig } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'
import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors'

const projectId = 'YOUR_WALLETCONNECT_PROJECT_ID'

export const config = createConfig({
  chains: [mainnet, sepolia],
  connectors: [
    injected(),
    walletConnect({ projectId }),
    metaMask(),
    safe(),
  ],
  transports: {
    [mainnet.id]: http(),
    [sepolia.id]: http(),
  },
})

declare module 'wagmi' {
  interface Register {
    config: typeof config
  }
}

Setup CoW SDK

Create a Trading SDK instance:
import { SupportedChainId, TradingSdk } from '@cowprotocol/cow-sdk'

export const tradingSdk = new TradingSdk({
  chainId: SupportedChainId.MAINNET,
  appCode: 'MyWagmiApp',
})

Bind SDK to Wagmi Hook

Create a custom hook to sync the SDK with Wagmi’s wallet state:
import { useAccount, usePublicClient, useWalletClient } from 'wagmi'
import { useEffect, useState } from 'react'
import { ViemAdapter } from '@cowprotocol/sdk-viem-adapter'
import { tradingSdk } from './cowSdk'
import { setGlobalAdapter } from '@cowprotocol/cow-sdk'

export function useBindCoWSdkToWagmi(): boolean {
  const account = useAccount()
  const { chainId } = account
  const { data: walletClient } = useWalletClient()
  const publicClient = usePublicClient()

  const [isSdkReady, setIsSdkReady] = useState(false)

  useEffect(() => {
    if (!walletClient || !chainId) {
      setIsSdkReady(false)
      return
    }

    setGlobalAdapter(
      new ViemAdapter({
        provider: publicClient,
        walletClient,
      })
    )

    tradingSdk.setTraderParams({ chainId })

    setIsSdkReady(true)

    return () => {
      setIsSdkReady(false)
    }
  }, [publicClient, walletClient, chainId])

  return isSdkReady
}

Swap Component

Create a swap form that uses Wagmi hooks:
import {
  isSupportedChain,
  EVM_NATIVE_CURRENCY_ADDRESS,
  OrderKind,
  type QuoteAndPost,
  WRAPPED_NATIVE_CURRENCIES,
} from '@cowprotocol/cow-sdk'
import { useEffect, useState } from 'react'
import { tradingSdk } from './cowSdk'
import { useAccount } from 'wagmi'
import { formatUnits, parseUnits } from 'viem'

export function SwapForm({ isSdkReady }: { isSdkReady: boolean }) {
  const { address: account, chainId, status } = useAccount()

  const [sellAmount, setSellAmount] = useState('0.1')
  const [quoteAndPost, setQuoteAndPost] = useState<QuoteAndPost | null>(null)
  const [swapError, setSwapError] = useState<Error | null>(null)
  const [postedOrderHash, setPostedOrderHash] = useState<string | null>(null)
  const [isOrderPostingInProgress, setIsOrderPostingInProgress] = useState(false)
  const [slippagePercent, setSlippagePercent] = useState(0.5)

  const slippageBps = slippagePercent * 100
  const isLoading =
    isOrderPostingInProgress ||
    Boolean(account && sellAmount && !quoteAndPost)

  const WETH = chainId && isSupportedChain(chainId)
    ? WRAPPED_NATIVE_CURRENCIES[chainId]
    : null
  const USDC =
    chainId === 1
      ? {
          address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
          decimals: 6,
          symbol: 'USDC',
        }
      : chainId === 11155111
      ? {
          address: '0xbe72E441BF55620febc26715db68d3494213D8Cb',
          decimals: 18,
          symbol: 'USDC',
        }
      : null

  const postOrder = () => {
    if (!quoteAndPost) return

    setIsOrderPostingInProgress(true)

    quoteAndPost
      .postSwapOrderFromQuote({
        appData: {
          metadata: {
            quote: {
              slippageBips: slippageBps,
            },
          },
        },
      })
      .then((response) => {
        if (!response) {
          setSwapError(new Error('No response from order posting'))
          return
        }
        setPostedOrderHash(response.orderId)
      })
      .catch(setSwapError)
      .finally(() => {
        setIsOrderPostingInProgress(false)
      })
  }

  useEffect(() => {
    const sellAmountNum = Number(sellAmount)

    if (!isSdkReady) return
    if (!chainId || !account || Number.isNaN(sellAmountNum) || sellAmountNum <= 0) return
    if (!WETH || !USDC) return

    setQuoteAndPost(null)

    tradingSdk
      .getQuote({
        chainId,
        kind: OrderKind.SELL,
        owner: account,
        amount: parseUnits(sellAmount, WETH.decimals).toString(),
        sellToken: WETH.address,
        sellTokenDecimals: WETH.decimals,
        buyToken: USDC.address,
        buyTokenDecimals: USDC.decimals,
        slippageBps,
      })
      .then((quote) => {
        setQuoteAndPost(quote)
        setSwapError(null)
      })
      .catch(setSwapError)
  }, [slippageBps, sellAmount, chainId, account, isSdkReady])

  const buyAmountRaw = quoteAndPost?.quoteResults.amountsAndCosts.afterNetworkCosts.buyAmount
  const buyAmountView =
    buyAmountRaw && USDC
      ? Number(formatUnits(buyAmountRaw, USDC.decimals)).toFixed(6)
      : undefined

  if (status !== 'connected') {
    return (
      <div>
        <h3>Please connect your wallet</h3>
        <p>Wallet status: {status}</p>
      </div>
    )
  }

  return (
    <div>
      {postedOrderHash && (
        <div className="box success">
          <h4>Order has been posted!</h4>
          <p>
            <strong>Order UID:</strong>
            <br />
            <code>{postedOrderHash}</code>
          </p>
          <p>
            <a
              href={`https://explorer.cow.fi/orders/${postedOrderHash}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              View in CoW Explorer
            </a>
          </p>
        </div>
      )}

      <div className="box">
        <strong>Sell</strong>
        <input
          type="number"
          value={sellAmount}
          onChange={(e) => setSellAmount(e.target.value)}
        />
        <span>{WETH?.symbol}</span>
      </div>

      <div className="box">
        <strong>Buy</strong>
        <input type="number" value={buyAmountView ?? 'Loading...'} disabled />
        <span>{USDC?.symbol}</span>
      </div>

      <div className="box">
        <label>Slippage:</label>
        <input
          type="number"
          value={slippagePercent}
          min={0}
          max={10}
          step={0.5}
          onChange={(e) => setSlippagePercent(+e.target.value)}
        />
        <span>%</span>
      </div>

      {swapError && (
        <div className="box error">
          {swapError.message || JSON.stringify(swapError)}
        </div>
      )}

      <button disabled={isLoading} onClick={postOrder}>
        {isOrderPostingInProgress ? 'Posting...' : 'Post order'}
      </button>
    </div>
  )
}

Main App Component

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider, useAccount } from 'wagmi'
import { config } from './wagmi'
import { SwapForm } from './components/SwapForm'
import { useBindCoWSdkToWagmi } from './hooks/useBindCoWSdkToWagmi'

const queryClient = new QueryClient()

function SwapApp() {
  const { address } = useAccount()
  const isSdkReady = useBindCoWSdkToWagmi()

  return (
    <div className="App">
      <h1>CoW Protocol Swap</h1>

      {address && (
        <div className="account">
          Connected: {address}
        </div>
      )}

      <SwapForm isSdkReady={isSdkReady} />
    </div>
  )
}

export default function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <SwapApp />
      </QueryClientProvider>
    </WagmiProvider>
  )
}

Main Entry Point

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './index.css'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

Token Approval with Wagmi

Handle token approvals using the SDK:
import { tradingSdk } from './cowSdk'
import { parseUnits } from 'viem'

const [approvalTxHash, setApprovalTxHash] = useState<string | null>(null)

const approveToken = async () => {
  if (!sellToken || !chainId) return

  try {
    const amount = parseUnits(sellAmount, sellToken.decimals)
    const txHash = await tradingSdk.approveCowProtocol({
      tokenAddress: sellToken.address,
      amount,
      chainId,
    })

    setApprovalTxHash(txHash)
  } catch (err) {
    console.error('Approval failed:', err)
  }
}

Key Benefits

  1. Automatic Network Switching: Wagmi handles network changes automatically
  2. Multi-Wallet Support: Users can connect with various wallets
  3. TypeScript Safety: Full type safety with Viem
  4. React Query Integration: Efficient data fetching and caching
  5. Better UX: Professional wallet connection UI

Connect Wallet Button

Add a proper wallet connection button:
import { useConnect, useDisconnect, useAccount } from 'wagmi'

function ConnectButton() {
  const { connect, connectors } = useConnect()
  const { disconnect } = useDisconnect()
  const { address, isConnected } = useAccount()

  if (isConnected) {
    return (
      <div>
        <span>{address}</span>
        <button onClick={() => disconnect()}>Disconnect</button>
      </div>
    )
  }

  return (
    <div>
      {connectors.map((connector) => (
        <button
          key={connector.id}
          onClick={() => connect({ connector })}
        >
          Connect {connector.name}
        </button>
      ))}
    </div>
  )
}

Network Switching

Handle network switching gracefully:
import { useSwitchChain, useAccount } from 'wagmi'
import { SupportedChainId } from '@cowprotocol/cow-sdk'

function NetworkSwitcher() {
  const { chainId } = useAccount()
  const { switchChain } = useSwitchChain()

  const isSupportedNetwork =
    chainId === SupportedChainId.MAINNET ||
    chainId === SupportedChainId.SEPOLIA

  if (!isSupportedNetwork) {
    return (
      <div className="warning">
        <p>Please switch to a supported network</p>
        <button onClick={() => switchChain({ chainId: SupportedChainId.MAINNET })}>
          Switch to Mainnet
        </button>
      </div>
    )
  }

  return null
}

Next Steps

Last modified on March 4, 2026