Farcaster Frames Integration

The @daimo/pay SDK supports integration with Farcaster Frames v2 via a custom connector. This allows users to make payments directly from a Frame using Farcaster wallets. Follow the steps below to integrate Farcaster.

Farcaster connector demo

Step 1: Create a Custom Farcaster Connector

To add Farcaster wallet support, create a custom connector using the @farcaster/frame-sdk package. Below is an example implementation taken largely from the Farcaster connector SDK.

import FrameSDK from '@farcaster/frame-sdk'
import {
  ChainNotConfiguredError,
  type Connector,
  createConnector,
} from '@wagmi/core'
import { SwitchChainError, fromHex, getAddress, numberToHex } from 'viem'

farcasterConnector.type = 'farcasterFrame' as const

let accountsChanged: Connector['onAccountsChanged'] | undefined
let chainChanged: Connector['onChainChanged'] | undefined
let disconnect: Connector['onDisconnect'] | undefined

export function farcasterConnector() {
  return createConnector<typeof FrameSDK.wallet.ethProvider>((config) => ({
    id: 'farcaster',
    name: 'Farcaster Wallet',
    rdns: 'xyz.farcaster',
    icon: 'https://pay.daimo.com/wallet-logos/farcaster-logo.svg',
    type: farcasterConnector.type,

    async connect({ chainId } = {}) {
      const provider = await this.getProvider()
      const accounts = await provider.request({
        method: 'eth_requestAccounts',
      })

      if (!accountsChanged) {
        accountsChanged = this.onAccountsChanged.bind(this)
        // @ts-expect-error - provider type is stricter
        provider.on('accountsChanged', accountsChanged)
      }
      if (!chainChanged) {
        chainChanged = this.onChainChanged.bind(this)
        provider.on('chainChanged', chainChanged)
      }
      if (!disconnect) {
        disconnect = this.onDisconnect.bind(this)
        provider.on('disconnect', disconnect)
      }

      let currentChainId = await this.getChainId()
      if (chainId && currentChainId !== chainId) {
        const chain = await this.switchChain!({ chainId })
        currentChainId = chain.id
      }

      return {
        accounts: accounts.map((x) => getAddress(x)),
        chainId: currentChainId,
      }
    },
    async disconnect() {
      const provider = await this.getProvider()

      if (accountsChanged) {
        // @ts-expect-error - provider type is stricter
        provider.removeListener('accountsChanged', accountsChanged)
        accountsChanged = undefined
      }

      if (chainChanged) {
        provider.removeListener('chainChanged', chainChanged)
        chainChanged = undefined
      }

      if (disconnect) {
        provider.removeListener('disconnect', disconnect)
        disconnect = undefined
      }
    },
    async getAccounts() {
      const provider = await this.getProvider()
      const accounts = await provider.request({
        method: 'eth_accounts',
      })
      return accounts.map((x) => getAddress(x))
    },
    async getChainId() {
      const provider = await this.getProvider()
      const hexChainId = await provider.request({ method: 'eth_chainId' })
      return fromHex(hexChainId, 'number')
    },
    async isAuthorized() {
      try {
        const accounts = await this.getAccounts()
        return !!accounts.length
      } catch {
        return false
      }
    },
    async switchChain({ chainId }) {
      const provider = await this.getProvider()
      const chain = config.chains.find((x) => x.id === chainId)
      if (!chain) {
        throw new SwitchChainError(new ChainNotConfiguredError())
      }

      await provider.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: numberToHex(chainId) }],
      })

      config.emitter.emit('change', { chainId })

      return chain
    },
    onAccountsChanged(accounts) {
      if (accounts.length === 0) {
        this.onDisconnect()
      } else {
        config.emitter.emit('change', {
          accounts: accounts.map((x) => getAddress(x)),
        })
      }
    },
    onChainChanged(chain) {
      const chainId = Number(chain)
      config.emitter.emit('change', { chainId })
    },
    async onDisconnect() {
      config.emitter.emit('disconnect')
    },
    async getProvider() {
      return FrameSDK.wallet.ethProvider
    },
  }))
}

Step 2: Add the Connector to Your SDK Configuration

Add the farcasterConnector to the additionalConnectors parameter in the wagmi config to register the Farcaster connector:

const config = createConfig(
  getDefaultConfig({
    appName: 'Farcaster Daimo Pay Demo',
    additionalConnectors: [farcasterConnector()],
  }),
)

Once the Farcaster connector is added, the wallet will automatically surface in the DaimoPayButton modal.

By following these steps, you can integrate Farcaster wallets into your payment flow using the @daimo/pay SDK. For more details about building a Farcaster Frame, see the Farcaster Frames Documentation.

Was this page helpful?