The popular Viem + Wagmi frontend setup can be used with Sophon. This section will provide a working PoC repository, relevant code snippets, and some cool gotchas that will help you working with this stack.

Check out our simple PoC repository that demonstrates how to use Viem and Wagmi together with the Paymaster .

Using Viem with a Paymaster

When building dApps on Sophon, it’s important to correctly integrate our Paymaster to sponsor transactions, especially before $SOPH is distributed.

This guide provides a working pattern, using Viem and Wagmi, for sending contract calls with Paymaster support on Sophon.

✅ Working Code Snippet

"use client";

import { getAccount, getPublicClient, switchChain, writeContract } from "@wagmi/core";
import {
  Abi,
  AbiItemArgs,
  Address,
  createWalletClient,
  custom,
  encodeFunctionData,
  Hash,
  Hex,
} from "viem";
import { Config } from "Wagmi";
import { eip712WalletActions, getGeneralPaymasterInput } from "viem/zksync";
  import { sophonTestnet, sophonMainnet } from "viem/chains";


export default async function sendTransaction(
  wagmiConfig: Config,
  chainId: number,
  address: Address,
  abi: Abi,
  functionName: string,
  args: AbiItemArgs,
  value?: bigint
): Promise<Hash> {
  const account = getAccount(wagmiConfig);

  if (!account.address) throw new Error("No account connected.");
  if (account.chainId !== chainId) await switchChain(wagmiConfig, { chainId });

  const publicClient = getPublicClient(wagmiConfig, { chainId });
  if (!publicClient) throw new Error("No public client available.");

  const estimateGas = await publicClient.estimateContractGas({
    account: account.address,
    address,
    abi,
    functionName,
    args,
    value,
  });

  //   const nextNonce = await publicClient.getTransactionCount({
  //     address: account.address,
  //   });

  // Handle Sophon-specific logic
  if (chainId === 50104 || chainId === 531050104) {
    const walletClient = createWalletClient({
      chain: chainId === 50104 ? sophonMainnet : sophonTestnet
      transport: custom(window.ethereum!),
    }).extend(eip712WalletActions());

    const paymaster: Address = "0x98546B226dbbA8230cf620635a1e4ab01F6A99B2";

    const paymasterInput: Hex = getGeneralPaymasterInput({
      innerInput: "0x",
    });

    const txData = encodeFunctionData({ abi, functionName, args });

    const hash = await walletClient.sendTransaction({
      account: account.address,
      to: address,
      data: txData,
      value,
      gas: estimateGas,
      chain,
      paymaster,
      paymasterInput,
      //   nonce: nextNonce, enable if you want to use a specific nonce
    });

    return hash;
  }

  // Fallback for non-zkSync chains
  return await writeContract(wagmiConfig, {
    address,
    chainId,
    abi,
    functionName,
    args,
    value,
    gas: estimateGas,
  });
}

⚠️ Key Gotchas to Watch Out For

1. Missing paymasterInput or incorrect format

paymasterInput must be passed, even if it’s an empty 0x using:

getGeneralPaymasterInput({ innerInput: "0x" }).

Manually hardcoding paymasterInput = "0x" will not work.

2. Use zkSync wallet extensions

Extend your wallet client with eip712WalletActions() to support sendTransaction with Paymasters.

const walletClient = createWalletClient({
      chain: sophonTestnet, // or `sophonMainnet`
      transport: custom(window.ethereum!),
    }).extend(eip712WalletActions());

3. Pre-calculating the nonce is possible, but optional

Based on your personal use-cases, you can pre-calculate nonces, but this is also handled internally by Wagmi’s publicClient provider wrapper.

const publicClient = getPublicClient(wagmiConfig, { chainId });

 const nextNonce = await publicClient.getTransactionCount({
      address: account.address,
    });

const hash = await walletClient.sendTransaction({
      account: account.address,
      to: address,
      data: txData,
      value,
      gas: estimateGas,
      chain: sophonTestnet, // chain client that is connected
      paymaster,
      paymasterInput,
      nonce: nextNonce,
    });