Skip to main content
viem is a popular TypeScript library for interacting with EVM chains. This guide shows how to use viem with Dfns wallets.
See complete working examples in the dfns-sdk-ts repository, including account abstraction integrations with Alchemy, Biconomy, Pimlico, and ZeroDev.

Setup

Install the required packages:
npm install viem @dfns/sdk @dfns/lib-viem

Creating a Dfns-backed viem account

Use DfnsWallet and toAccount from the SDK to create a viem-compatible account:
import { DfnsWallet } from '@dfns/lib-viem'
import { DfnsApiClient } from '@dfns/sdk'
import { AsymmetricKeySigner } from '@dfns/sdk-keysigner'
import { createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { toAccount } from 'viem/accounts'

// Initialize Dfns client
const signer = new AsymmetricKeySigner({
  credId: process.env.DFNS_CRED_ID!,
  privateKey: process.env.DFNS_PRIVATE_KEY!,
})

const dfnsClient = new DfnsApiClient({
  authToken: process.env.DFNS_AUTH_TOKEN!,
  baseUrl: process.env.DFNS_API_URL!,
  signer,
})

// Initialize Dfns wallet
const dfnsWallet = await DfnsWallet.init({
  walletId: process.env.DFNS_WALLET_ID!,
  dfnsClient,
})

// Create viem wallet client
const walletClient = createWalletClient({
  account: toAccount(dfnsWallet),
  chain: mainnet,
  transport: http(),
})

Sending transactions

Simple ETH transfer

import { parseEther } from 'viem'

const hash = await walletClient.sendTransaction({
  to: '0x...',
  value: parseEther('0.1'),
})

// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash })

Contract interaction

import { parseAbi, parseUnits } from 'viem'

const hash = await walletClient.writeContract({
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  abi: parseAbi(['function transfer(address to, uint256 amount) returns (bool)']),
  functionName: 'transfer',
  args: ['0xRecipient...', parseUnits('100', 6)], // 100 USDC
})

EIP-712 typed data signing

const signature = await walletClient.signTypedData({
  domain: {
    name: 'My App',
    version: '1',
    chainId: 1,
    verifyingContract: '0x...',
  },
  types: {
    Order: [
      { name: 'maker', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
  },
  primaryType: 'Order',
  message: {
    maker: walletAddress,
    amount: 1000000n,
  },
})

Account abstraction (gasless transactions)

Dfns wallets work with account abstraction providers for gasless transactions. The SDK includes examples for:
ProviderUse case
AlchemyModular accounts with gas policies
BiconomySmart accounts with paymasters
PimlicoBundler and paymaster services
ZeroDevKernel smart accounts

Example with Alchemy

import { createModularAccountAlchemyClient } from '@alchemy/aa-alchemy'
import { LocalAccountSigner } from '@alchemy/aa-core'
import { sepolia } from 'viem/chains'

const smartAccountClient = await createModularAccountAlchemyClient({
  apiKey: process.env.ALCHEMY_API_KEY!,
  chain: sepolia,
  signer: new LocalAccountSigner(toAccount(dfnsWallet)),
  gasManagerConfig: {
    policyId: process.env.ALCHEMY_GAS_POLICY_ID!,
  },
})

// Transactions are now gasless for your users
const hash = await smartAccountClient.sendTransaction({
  to: '0x...',
  data: '0x...',
})

Multicall (batch transactions)

Batch multiple contract calls in a single transaction:
import { encodeFunctionData, parseAbi } from 'viem'

const multiCallAddress = '0x...' // Multicall3 contract

const hash = await walletClient.sendTransaction({
  to: multiCallAddress,
  data: encodeFunctionData({
    abi: multicallAbi,
    functionName: 'aggregate3',
    args: [[
      { target: token1, callData: transferData1 },
      { target: token2, callData: transferData2 },
    ]],
  }),
})
See the multicall example for complete implementation.

Reading contract data

Use a public client for read operations (no signing needed):
import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
})

// Read ERC-20 balance
const balance = await publicClient.readContract({
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
  functionName: 'balanceOf',
  args: [walletAddress],
})

Using Dfns Broadcast API instead

For better policy integration, you may prefer using the Dfns Broadcast API directly:
import { encodeFunctionData, parseAbi } from 'viem'

// Encode the contract call with viem
const data = encodeFunctionData({
  abi: parseAbi(['function transfer(address to, uint256 amount) returns (bool)']),
  functionName: 'transfer',
  args: [recipient, amount],
})

// Broadcast via Dfns
const result = await dfnsClient.wallets.broadcastTransaction({
  walletId,
  body: {
    kind: 'Evm',
    to: tokenAddress,
    data,
  },
})
This approach integrates with Dfns policies and provides consistent transaction tracking.