viem Integration

viem is a popular TypeScript library for interacting with EVM chains. This topic explains how to integrate viem with IBM Digital Asset Haven wallets.

Setup

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

Creating a IBM Digital Asset Haven-backed viem account

  1. 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'
  2. Initialize the IBM Digital Asset Haven 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,
    })
  3. Initialize the IBM Digital Asset Haven wallet.
    const dfnsWallet = await DfnsWallet.init({
      walletId: process.env.DFNS_WALLET_ID!,
      dfnsClient,
    })
  4. 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)

IBM Digital Asset Haven wallets work with account abstraction providers for gasless transactions. The SDK includes examples for:
Provider Use case
Alchemy Modular accounts with gas policies
Biconomy Smart accounts with paymasters
Pimlico Bundler and paymaster services
ZeroDev Kernel 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 },
    ]],
  }),
})

Reading contract data

  • Use a public client for read operations (signing is not required).
    import { createPublicClient, http, parseAbi } from 'viem'
    import { mainnet } from 'viem/chains'
    
    const publicClient = createPublicClient({
      chain: mainnet,
      transport: http(),
    })
  • Read the ERC-20 balance.
    const balance = await publicClient.readContract({
      address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
      abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
      functionName: 'balanceOf',
      args: [walletAddress],
    })

Using the IBM Digital Asset Haven Broadcast API

For better policy integration, you may prefer to use the IBM Digital Asset Haven 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 IBM Digital Asset Haven.
    const result = await dfnsClient.wallets.broadcastTransaction({
      walletId,
      body: {
        kind: 'Evm',
        to: tokenAddress,
        data,
      },
    })

This approach integrates with IBM Digital Asset Haven policies and provides consistent transaction tracking.