To use paymaster transactions on ZKsync, the Viem implementation requires you to import {eip712WalletActions} from 'viem/zksync'and extend a WalletClient with it.
However, this modifies the WalletClient.SendTransaction() function by replacing the eth_sendTransaction JSON RPC API method by the eth_sendRawTransaction method, which is not supported on several mobile wallets.
The current work-around for making paymaster transactions work with all wallets is to sign the transaction using a WalletClient, and then send the signed transaction using a PublicClient supporting the eth_sendRawTransaction. The simplest way is to use a public JSON RPC endpoint, such as https://mainnet.era.zksync.io.
Classic Paymaster implementation (not compatible with all wallets):
// Paymaster Data received from Zyfi's API
const pmData = await submitTxDataToAPI(API_URL, dataTxRequest)
// Extend wallet client to support paymaster transactions
const wClient = walletClient.extend(eip712WalletActions())
// Prepare transaction request
const txReq = await wClient.prepareTransactionRequest({
account: address,
to: toAddress,
value: ETH_AMOUNT,
chain: zkSync,
gas: BigInt(pmData.gasLimit),
gasPerPubdata: BigInt(pmData.txData.customData.gasPerPubdata),
maxFeePerGas: BigInt(pmData.txData.maxFeePerGas),
maxPriorityFeePerGas: 0n,
data: pmData.txData.data,
paymaster: pmData.txData.customData.paymasterParams.paymaster,
paymasterInput: pmData.txData.customData.paymasterParams.paymasterInput,
})
// Signs and sends to an endpoint that might be incompatible with eth_sendRawTransaction
const hash = await wClient.sendTransaction(txReq)
Work-around Paymaster Implementation (Works with all wallets)
// Paymaster Data received from Zyfi's API
const pmData = await submitTxDataToAPI(API_URL, dataTxRequest)
// Extend wallet client to support paymaster transactions
const wClient = walletClient.extend(eip712WalletActions())
// Create custom public client with an endpoint that suports eth_sendRawTransaction
const publicClient = createPublicClient({
chain: zkSync,
transport: http('https://mainnet.era.zksync.io')
})
const txReq = await wClient.prepareTransactionRequest({
account: address,
to: toAddress,
value: ETH_AMOUNT,
chain: zkSync,
gas: BigInt(pmData.gasLimit),
gasPerPubdata: BigInt(pmData.txData.customData.gasPerPubdata),
maxFeePerGas: BigInt(pmData.txData.maxFeePerGas),
maxPriorityFeePerGas: 0n,
data: pmData.txData.data,
paymaster: pmData.txData.customData.paymasterParams.paymaster,
paymasterInput: pmData.txData.customData.paymasterParams.paymasterInput,
})
// Sign with Wallet and send signed transaction directly to a compatible endpoint
const signature = await wClient.signTransaction(txReq)
const hash = await publicClient.sendRawTransaction({ serializedTransaction: signature })
It's a workaround that works perfectly for Dapps wanting to support paymaster functionality on mobile within ZKsync chains.