Using Wagmi with ZKsync
Learn how to use the Wagmi toolkit to create websites that interact with contracts on ZKsync
This guide outlines how to use the wagmi library to create websites that interact with ZKsync contracts. We provide some examples to help you get started.
What you'll learn:
- How to use wagmi to interact with ZKsync
- How to fetch data from ZKsync contracts
- How to send transactions to ZKsync contracts
The @wagmi/core
library provides a comprehensive VanillaJS toolkit for interacting with ZKsync Era.
It simplifies wallet connections, balance retrieval, message signing, and contract interactions.
For setup instructions, consult the official documentation here.
Wagmi hooks do not yet support Paymasters and native Account Abstraction; development is in progress.
Project Setup
Installing Dependencies
npm install @wagmi/core @wagmi/connectors viem@2.x
For React projects, you can also make use of the wagmi
package:
npm install wagmi viem@2.x @tanstack/react-query
Configuration
Make a file called wagmi-config.ts
and configure your preferred ZKsync network.
wagmi-config.ts
import { http, createConfig } from '@wagmi/core';
import { zksync, zksyncSepoliaTestnet, zksyncInMemoryNode, zksyncLocalNode } from '@wagmi/core/chains';
import { walletConnect } from '@wagmi/connectors';
export const config = createConfig({
chains: [zksync, zksyncSepoliaTestnet, zksyncInMemoryNode, zksyncLocalNode],
connectors: [
walletConnect({
projectId: 'd4a7167a6eed6a53c8364631aaeca861',
}),
],
transports: {
[zksync.id]: http(),
[zksyncSepoliaTestnet.id]: http(),
[zksyncInMemoryNode.id]: http(),
[zksyncLocalNode.id]: http(),
},
});
Here are some common actions:
Connect Wallet
connect
Method
connect.ts
import { connect } from '@wagmi/core';
import { injected } from '@wagmi/connectors';
import { config } from '../../wagmi-config';
export async function connectWallet() {
try {
await connect(config, { connector: injected() });
} catch (error) {
console.error('ERROR CONNECTING:', error);
}
}
useConnect
Hook
ConnectWallet.tsx
import { injected } from '@wagmi/connectors';
import { useConnect } from 'wagmi';
export function ConnectWallet() {
const { connect } = useConnect();
return (
<div>
<button onClick={() => connect({ connector: injected() })}>Connect Wallet</button>
</div>
);
}
Display Wallet Options
getConnectors
Method
connectors.ts
import { getConnectors } from '@wagmi/core';
import { config } from '../../wagmi-config';
export function fetchConnectors() {
const connectors = getConnectors(config);
return connectors;
}
useConnectors
Hook
ShowConnectors.tsx
import { connectWallet } from '@/utils/connect';
import { useConnectors } from 'wagmi';
export function ShowConnectors() {
const connectors = useConnectors();
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
{connectors.map((connector) => (
<button
key={connector.id}
onClick={connectWallet}
>
{connector.name}
</button>
))}
</div>
);
}
Fetch Account
getAccount
Method
account.ts
import { getAccount } from '@wagmi/core';
import { config } from '../../wagmi-config';
export function fetchAccount() {
const { address } = getAccount(config);
return address;
}
useAccount
Hook
Account.tsx
import { useAccount } from 'wagmi';
export function Account() {
const { address } = useAccount();
return <div>{address}</div>;
}
Fetch Balance
getBalance
Method
balance.ts
import { getBalance } from '@wagmi/core';
import { config } from '../../wagmi-config';
export async function fetchBalance() {
const address = '0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A';
const balance = await getBalance(config, { address });
return balance.value;
}
useBalance
Hook
Balance.tsx
import { useBalance } from 'wagmi';
export function Balance() {
const balance = useBalance({
address: '0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A',
});
return <div>{balance && <p>Balance: {balance.data?.value.toString()}</p>}</div>;
}
Fetch Block Number
getBlockNumber
Method
block.ts
import { getBlockNumber } from '@wagmi/core';
import { config } from '../../wagmi-config';
export async function fetchLatestBlockNumber() {
const blockNumber = await getBlockNumber(config);
return blockNumber;
}
useBlockNumber
Hook
Block.tsx
import { useBlockNumber } from 'wagmi';
export function Block() {
const block = useBlockNumber();
return <div>{block && <p>Block: {block.data?.toString()}</p>}</div>;
}
Send Transaction
sendTransaction
Method
sendTx.ts
import { sendTransaction } from '@wagmi/core';
import { parseEther } from 'viem';
import { config } from '../../wagmi-config';
export async function sendTx(address: `0x${string}`, value: `${number}`) {
const result = await sendTransaction(config, {
to: address,
value: parseEther(value),
});
return result;
}
useSendTransaction
Hook
SendTx.tsx
import { parseEther } from 'viem';
import { useSendTransaction } from 'wagmi';
export function SendTx() {
const { sendTransaction, isError, isPending, isSuccess, data, error } = useSendTransaction();
return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const address = formData.get('address') as `0x${string}`;
const value = formData.get('value') as `${number}`;
sendTransaction({
to: address,
value: parseEther(value),
});
}}
>
<input
name="address"
placeholder="address"
/>
<input
name="value"
placeholder="value (ether)"
/>
<button type="submit">Send</button>
</form>
{isPending && <div>Transaction pending...</div>}
{isSuccess && <div>Transaction Hash: {data}</div>}
{isError && <div>Error: {error?.message}</div>}
</>
);
}
Send Transaction (Prepared)
prepareTransactionRequest
Method
prepareTx.ts
import { prepareTransactionRequest } from '@wagmi/core';
import { parseEther } from 'viem';
import { config } from '../../wagmi-config';
export async function prepareTx(to: `0x${string}`, value: `${number}`) {
const tx = await prepareTransactionRequest(config, {
to,
value: parseEther(value),
});
return tx;
}
usePrepareTransactionRequest
Hook
SendTxPrepared.tsx
import { useState } from 'react';
import { parseEther } from 'viem';
import { usePrepareTransactionRequest, useSendTransaction } from 'wagmi';
export function SendTxPrepared() {
const [to, setTo] = useState<`0x${string}`>();
const [value, setValue] = useState<string>('0.0');
const { data: txRequest } = usePrepareTransactionRequest({
to,
value: parseEther(value as `${number}`),
});
const { sendTransaction, isError, isPending, isSuccess, data, error } = useSendTransaction();
return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
if (!txRequest) return;
sendTransaction(txRequest);
}}
>
<input
placeholder="address"
onChange={(e) => setTo(e.target.value as `0x${string}`)}
value={to}
/>
<input
id="value"
placeholder="value (ether)"
onChange={(e) => setValue(e.target.value)}
value={value?.toString()}
/>
<button
disabled={!txRequest}
type="submit"
>
Send
</button>
</form>
{isPending && <div>Transaction pending...</div>}
{isSuccess && <div>Transaction Hash: {data}</div>}
{isError && <div>Error: {error?.message}</div>}
</>
);
}
Sign Message
signMessage
Method
message.ts
import { signMessage } from '@wagmi/core';
import { config } from '../../wagmi-config';
export async function getSignedMessage(message: string) {
const signature = await signMessage(config, { message });
return signature;
}
useSignMessage
Hook
SignMessage.tsx
import { useEffect, useState } from 'react';
import { type Address, recoverMessageAddress } from 'viem';
import { useSignMessage } from 'wagmi';
export function SignMessage() {
const [recoveredAddress, setRecoveredAddress] = useState<Address>();
const { data: signature, variables, error, isPending, signMessage } = useSignMessage();
useEffect(() => {
(async () => {
if (variables?.message && signature) {
const recoveredAddress = await recoverMessageAddress({
message: variables?.message,
signature,
});
setRecoveredAddress(recoveredAddress);
}
})();
}, [signature, variables?.message]);
return (
<>
<form
onSubmit={(event) => {
event.preventDefault();
const element = event.target as HTMLFormElement;
const formData = new FormData(element);
const message = formData.get('message') as string;
signMessage({ message });
}}
>
<input
name="message"
type="text"
required
/>
<button
disabled={isPending}
type="submit"
>
{isPending ? '...Pending' : 'Sign Message'}
</button>
</form>
{signature && (
<div>
<div>Signature: {signature}</div>
<div>Recovered address: {recoveredAddress}</div>
</div>
)}
{error && <div>Error: {error?.message}</div>}
</>
);
}
Sign Typed Data
signTypedData
Method
signTyped.ts
import { signTypedData, type SignTypedDataParameters } from '@wagmi/core';
import { config } from '../../wagmi-config';
export async function getSignedTypedData(data: SignTypedDataParameters) {
const signature = await signTypedData(config, data);
return signature;
}
useSignTypedData
Hook
SignTypedData.tsx
import { useSignTypedData } from 'wagmi';
export function SignTypedData() {
const { signTypedData, data } = useSignTypedData();
return (
<>
<button
onClick={() =>
signTypedData({
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
},
primaryType: 'Mail',
message: {
from: {
name: 'Cow',
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
contents: 'Hello, Bob!',
},
})
}
>
Sign message
</button>
<div>{data && <p>Signature: {data}</p>}</div>
</>
);
}
Read Contract
readContract
Method
signTyped.ts
import { readContract } from '@wagmi/core';
import { config } from '../../wagmi-config';
import * as erc20TokenABI from '../../../frontend-paymaster/contracts/artifacts-zk/contracts/erc20/MyERC20Token.sol/MyERC20Token.json';
const CONTRACT_ADDRESS = '0x9c1a3d7C98dBF89c7f5d167F2219C29c2fe775A7';
export async function readERC20Contract(functionName: string) {
const data = await readContract(config, {
abi: erc20TokenABI.abi,
address: CONTRACT_ADDRESS,
functionName: functionName,
});
return data;
}
useReadContract
Hook
ReadContract.tsx
import { useState } from 'react';
import type { Address, BaseError } from 'viem';
import { useReadContract } from 'wagmi';
import * as erc20TokenABI from '../../../frontend-paymaster/contracts/artifacts-zk/contracts/erc20/MyERC20Token.sol/MyERC20Token.json';
const CONTRACT_ADDRESS = '0x9c1a3d7C98dBF89c7f5d167F2219C29c2fe775A7';
export function ReadContract() {
return (
<div>
<div>
<BalanceOf />
<br />
<TotalSupply />
</div>
</div>
);
}
function TotalSupply() {
const { data, isRefetching, refetch } = useReadContract({
abi: erc20TokenABI.abi,
address: CONTRACT_ADDRESS,
functionName: 'totalSupply',
});
console.log('data', data);
return (
<div>
Total Supply: {data?.toString()}
<button
disabled={isRefetching}
onClick={() => refetch()}
style={{ marginLeft: 4 }}
>
{isRefetching ? 'loading...' : 'refetch'}
</button>
</div>
);
}
function BalanceOf() {
const [address, setAddress] = useState<Address>('0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A');
const { data, error, isLoading, isSuccess } = useReadContract({
abi: erc20TokenABI.abi,
address: CONTRACT_ADDRESS,
functionName: 'balanceOf',
args: [address],
});
const [value, setValue] = useState<string>(address);
return (
<div>
Token balance: {isSuccess && data?.toString()}
<input
onChange={(e) => setValue(e.target.value)}
placeholder="wallet address"
style={{ marginLeft: 4 }}
value={value}
/>
<button onClick={() => setAddress(value as Address)}>{isLoading ? 'fetching...' : 'fetch'}</button>
{error && <div>{(error as BaseError).shortMessage}</div>}
</div>
);
}
Write Contract
writeContract
Method
write.ts
import { readContract } from '@wagmi/core';
import { config } from '../../wagmi-config';
import * as greeterABI from '../../../frontend-paymaster/contracts/artifacts-zk/contracts/Greeter.sol/Greeter.json';
const CONTRACT_ADDRESS = '0xCeAB1fc2693930bbad33024D270598c620D7A52B';
export async function writeToGreeterContract(greeting: string) {
const data = await readContract(config, {
abi: greeterABI.abi,
address: CONTRACT_ADDRESS,
functionName: 'setGreeting',
args: [greeting],
});
return data;
}
useWriteContract
Hook
WriteContract.tsx
import { useState } from 'react';
import { type BaseError, useWriteContract } from 'wagmi';
import * as greeterABI from '../../../frontend-paymaster/contracts/artifacts-zk/contracts/Greeter.sol/Greeter.json';
const CONTRACT_ADDRESS = '0xCeAB1fc2693930bbad33024D270598c620D7A52B';
export function WriteContract() {
const [greeting, setGreeting] = useState<string>();
const { writeContract, isError, isPending, isSuccess, data, error } = useWriteContract();
async function updateGreeting() {
if (!greeting) return;
writeContract({
abi: greeterABI.abi,
address: CONTRACT_ADDRESS,
functionName: 'setGreeting',
args: [greeting],
});
}
return (
<div>
<input
onChange={(e) => setGreeting(e.target.value)}
placeholder="Hello, Zeek!"
style={{ marginLeft: 4 }}
value={greeting}
/>
<button onClick={updateGreeting}>{isPending ? '...Pending' : 'Update Greeting'}</button>
{isError && <div>{(error as BaseError).shortMessage}</div>}
{isSuccess && <div>Transaction hash: {data}</div>}
</div>
);
}