Skip to content

Commit 5dba3c1

Browse files
ci: add viem test (#135)
Fixes the viem tutorial and adds a test
1 parent e6931e6 commit 5dba3c1

24 files changed

Lines changed: 590 additions & 96 deletions

.github/workflows/playwright.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ jobs:
6060
folder-name: 'walletconnect'
6161
mm-wallet: 'false'
6262

63+
- test-file-name: "viem"
64+
tutorial-paths: '["tutorials/guide-viem"]'
65+
config-path: 'tests/configs/viem.ts'
66+
folder-name: 'viem'
67+
mm-wallet: 'false'
68+
6369
steps:
6470
- uses: actions/checkout@v4
6571
- uses: oven-sh/setup-bun@v1
@@ -71,7 +77,7 @@ jobs:
7177

7278
# Setup Anvil for local testing
7379
- name: Run anvil-zksync
74-
if: matrix.tutorial.test-file-name != 'how-to-test-contracts' && matrix.tutorial.test-file-name != 'walletconnect'
80+
if: matrix.tutorial.test-file-name != 'how-to-test-contracts' && matrix.tutorial.test-file-name != 'walletconnect' && matrix.tutorial.test-file-name != 'viem'
7581
uses: dutterbutter/anvil-zksync-action@v1.1.0
7682

7783
# ZK Game Dependencies

code/viem/.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
bun.lock
15+
16+
# Editor directories and files
17+
.vscode/*
18+
!.vscode/extensions.json
19+
.idea
20+
.DS_Store
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?

code/viem/eslint.config.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import js from '@eslint/js';
2+
import globals from 'globals';
3+
import reactHooks from 'eslint-plugin-react-hooks';
4+
import reactRefresh from 'eslint-plugin-react-refresh';
5+
import tseslint from 'typescript-eslint';
6+
7+
export default tseslint.config(
8+
{ ignores: ['dist'] },
9+
{
10+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11+
files: ['**/*.{ts,tsx}'],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: globals.browser,
15+
},
16+
plugins: {
17+
'react-hooks': reactHooks,
18+
'react-refresh': reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
23+
},
24+
}
25+
);

code/viem/index.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1.0"
8+
/>
9+
<title>ZKsync Viem Demo</title>
10+
</head>
11+
<body>
12+
<div id="root"></div>
13+
<script
14+
type="module"
15+
src="/src/main.tsx"
16+
></script>
17+
</body>
18+
</html>

code/viem/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "viem",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"lint": "eslint .",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"react": "^19.1.0",
14+
"react-dom": "^19.1.0",
15+
"viem": "^2.31.0"
16+
},
17+
"devDependencies": {
18+
"@eslint/js": "^9.25.0",
19+
"@types/react": "^19.1.2",
20+
"@types/react-dom": "^19.1.2",
21+
"@vitejs/plugin-react": "^4.4.1",
22+
"eslint": "^9.25.0",
23+
"eslint-plugin-react-hooks": "^5.2.0",
24+
"eslint-plugin-react-refresh": "^0.4.19",
25+
"globals": "^16.0.0",
26+
"typescript": "~5.8.3",
27+
"typescript-eslint": "^8.30.1",
28+
"vite": "^6.3.5"
29+
}
30+
}

code/viem/src/App.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useState } from 'react';
2+
import Read from './components/Read';
3+
import Write from './components/Write';
4+
import ReadContract from './components/ReadContract';
5+
import Paymaster from './components/Paymaster';
6+
7+
const App = () => {
8+
const [update, setUpdate] = useState(0);
9+
10+
return (
11+
<>
12+
<Read update={update} />
13+
<Write update={setUpdate} />
14+
<Paymaster update={setUpdate} />
15+
<ReadContract update={update} />
16+
</>
17+
);
18+
};
19+
20+
export default App;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { Dispatch, SetStateAction } from 'react';
2+
// ANCHOR: setup-imports
3+
import { createWalletClient, custom } from 'viem';
4+
import {
5+
// zksync,
6+
zksyncSepoliaTestnet,
7+
// zksyncInMemoryNode,
8+
// zksyncLocalNode
9+
} from 'viem/chains';
10+
import { eip712WalletActions, getGeneralPaymasterInput } from 'viem/zksync';
11+
// ANCHOR_END: setup-imports
12+
import greeterAbi from '../utils/Greeter.json';
13+
import { publicClient } from '../utils/client';
14+
15+
const Paymaster = ({ update }: { update: Dispatch<SetStateAction<number>> }) => {
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17+
const window: any = globalThis.window;
18+
19+
async function writeToContract() {
20+
try {
21+
// ANCHOR: client
22+
// Initialize and extend the wallet client
23+
const client = createWalletClient({
24+
chain: zksyncSepoliaTestnet,
25+
transport: custom(window.ethereum!),
26+
}).extend(eip712WalletActions());
27+
// ANCHOR_END: client
28+
29+
const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
30+
if (!account) {
31+
console.error('User account is not set. Please connect your wallet.');
32+
return;
33+
}
34+
35+
// ANCHOR: use-paymaster
36+
// Replace with your paymaster address
37+
const paymasterAddress = '0xdeAD0bD94D5975538B7678C0EaAd28d20FbFF8A7';
38+
39+
// Call the contract function
40+
const hash = await client.writeContract({
41+
account,
42+
address: '0xe2e810a56672336C0Cc303E5f8156A5a56DBE150',
43+
abi: greeterAbi,
44+
functionName: 'sendMessage',
45+
args: ['ZKsync Paymaster'],
46+
paymaster: paymasterAddress,
47+
paymasterInput: getGeneralPaymasterInput({ innerInput: new Uint8Array() }),
48+
});
49+
50+
await publicClient.waitForTransactionReceipt({ hash });
51+
// ANCHOR_END: use-paymaster
52+
update((prev) => prev + 1);
53+
} catch (error) {
54+
console.error('Error writing to contract:', error);
55+
}
56+
}
57+
58+
return (
59+
<>
60+
<button onClick={writeToContract}>Write with Paymaster</button>
61+
</>
62+
);
63+
};
64+
65+
export default Paymaster;

code/viem/src/components/Read.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useEffect, useState } from 'react';
2+
import greeterAbi from '../utils/Greeter.json';
3+
// ANCHOR: client
4+
import { createPublicClient, http, type Chain, type PublicClient } from 'viem';
5+
import {
6+
// zksync,
7+
zksyncSepoliaTestnet,
8+
// zksyncInMemoryNode,
9+
// zksyncLocalNode
10+
} from 'viem/chains';
11+
12+
// Initialize the Viem client
13+
const client: PublicClient = createPublicClient({
14+
chain: zksyncSepoliaTestnet as Chain, // Specify the ZKsync network
15+
transport: http(), // Define the transport method
16+
});
17+
// ANCHOR_END: client
18+
19+
const Read = ({ update }: { update: number }) => {
20+
const [blockNum, setBlockNum] = useState<bigint>();
21+
const [message, setMessage] = useState<string>();
22+
23+
useEffect(() => {
24+
async function fetchData() {
25+
// ANCHOR: block-number
26+
const blockNumber = await client.getBlockNumber();
27+
console.log(`Current block number: ${blockNumber}`);
28+
// ANCHOR_END: block-number
29+
setBlockNum(blockNumber);
30+
31+
// ANCHOR: read-contract
32+
const messageResponse = await client.readContract({
33+
address: '0xe2e810a56672336C0Cc303E5f8156A5a56DBE150',
34+
abi: greeterAbi,
35+
functionName: 'getLastMessage',
36+
});
37+
// ANCHOR_END: read-contract
38+
if (typeof messageResponse === 'string') {
39+
setMessage(messageResponse);
40+
}
41+
}
42+
fetchData();
43+
}, [update]);
44+
45+
return (
46+
<>
47+
{blockNum && (
48+
<div>
49+
<h2>Current Block Number</h2>
50+
<p>{blockNum}</p>
51+
</div>
52+
)}
53+
{message && (
54+
<div>
55+
<h2>Current Message 1:</h2>
56+
<p>{message}</p>
57+
</div>
58+
)}
59+
</>
60+
);
61+
};
62+
63+
export default Read;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// ANCHOR: imports
2+
import { getContract } from 'viem';
3+
import greeterAbi from '../utils/Greeter.json'; // Your contract's ABI
4+
import { useEffect, useState } from 'react';
5+
import { publicClient } from '../utils/client'; // Your initialized Viem client
6+
// ANCHOR_END: imports
7+
8+
const ReadContract = ({ update }: { update: number }) => {
9+
const [message, setMessage] = useState<string>();
10+
11+
useEffect(() => {
12+
async function getLastMessage() {
13+
// ANCHOR: read-message
14+
// Initialize the contract instance
15+
const contract = getContract({
16+
address: '0xe2e810a56672336C0Cc303E5f8156A5a56DBE150',
17+
abi: greeterAbi,
18+
client: publicClient,
19+
});
20+
21+
// Interact with your contract
22+
const result = await contract.read.getLastMessage();
23+
console.log(`Last Message: ${result}`);
24+
// ANCHOR_END: read-message
25+
if (typeof result === 'string') {
26+
setMessage(result);
27+
}
28+
}
29+
getLastMessage();
30+
}, [update]);
31+
32+
return (
33+
<>
34+
{message && (
35+
<div>
36+
<h2>Current Message 2:</h2>
37+
<p>{message}</p>
38+
</div>
39+
)}
40+
</>
41+
);
42+
};
43+
44+
export default ReadContract;

code/viem/src/components/Write.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// ANCHOR: write
2+
import { createWalletClient, custom, encodeFunctionData } from 'viem';
3+
import {
4+
// zksync,
5+
zksyncSepoliaTestnet,
6+
// zksyncInMemoryNode,
7+
// zksyncLocalNode
8+
} from 'viem/chains';
9+
import greeterAbi from '../utils/Greeter.json';
10+
import type { Dispatch, SetStateAction } from 'react';
11+
import { publicClient } from '../utils/client';
12+
13+
const Write = ({ update }: { update: Dispatch<SetStateAction<number>> }) => {
14+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15+
const window: any = globalThis.window;
16+
17+
async function writeToContract() {
18+
try {
19+
// Request account access from the Ethereum provider
20+
const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
21+
if (!account) return;
22+
// Configure the wallet client
23+
const client = createWalletClient({
24+
account,
25+
chain: zksyncSepoliaTestnet,
26+
transport: custom(window.ethereum),
27+
});
28+
29+
const data = encodeFunctionData({
30+
abi: greeterAbi,
31+
functionName: 'sendMessage',
32+
args: ['ZK is the endgame!'],
33+
});
34+
35+
// Example transaction
36+
const hash = await client.sendTransaction({
37+
account,
38+
to: '0xe2e810a56672336C0Cc303E5f8156A5a56DBE150',
39+
data,
40+
chain: client.chain,
41+
});
42+
43+
await publicClient.waitForTransactionReceipt({ hash });
44+
update((prev) => prev + 1);
45+
} catch (error) {
46+
console.error('Error writing to contract:', error);
47+
}
48+
}
49+
// ANCHOR_END: write
50+
51+
return (
52+
<>
53+
<button onClick={writeToContract}>Write To Greeter Contract</button>
54+
</>
55+
);
56+
};
57+
58+
export default Write;

0 commit comments

Comments
 (0)