diff --git a/code/wagmi/.gitignore b/code/wagmi/.gitignore new file mode 100644 index 00000000..d32cc78b --- /dev/null +++ b/code/wagmi/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/code/wagmi/bun.lockb b/code/wagmi/bun.lockb new file mode 100755 index 00000000..ffc50515 Binary files /dev/null and b/code/wagmi/bun.lockb differ diff --git a/code/wagmi/next.config.ts b/code/wagmi/next.config.ts new file mode 100644 index 00000000..0748d34f --- /dev/null +++ b/code/wagmi/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + /* config options here */ + reactStrictMode: true, +}; + +export default nextConfig; diff --git a/code/wagmi/package.json b/code/wagmi/package.json new file mode 100644 index 00000000..072c91b3 --- /dev/null +++ b/code/wagmi/package.json @@ -0,0 +1,27 @@ +{ + "name": "wagmi", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@tanstack/react-query": "^5.60.4", + "@wagmi/connectors": "^5.3.10", + "@wagmi/core": "^2.14.6", + "next": "15.0.3", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106", + "viem": "^2.21.45", + "wagmi": "^2.12.32" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } +} diff --git a/code/wagmi/public/favicon.ico b/code/wagmi/public/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/code/wagmi/public/favicon.ico differ diff --git a/code/wagmi/src/components/Account.tsx b/code/wagmi/src/components/Account.tsx new file mode 100644 index 00000000..9d92f643 --- /dev/null +++ b/code/wagmi/src/components/Account.tsx @@ -0,0 +1,7 @@ +import { useAccount } from 'wagmi'; + +export function Account() { + const { address } = useAccount(); + + return
{address}
; +} diff --git a/code/wagmi/src/components/AccountTS.tsx b/code/wagmi/src/components/AccountTS.tsx new file mode 100644 index 00000000..f6a82484 --- /dev/null +++ b/code/wagmi/src/components/AccountTS.tsx @@ -0,0 +1,7 @@ +import { fetchAccount } from '@/utils/account'; + +export function AccountTS() { + const address = fetchAccount(); + + return
{address}
; +} diff --git a/code/wagmi/src/components/Balance.tsx b/code/wagmi/src/components/Balance.tsx new file mode 100644 index 00000000..a07afca1 --- /dev/null +++ b/code/wagmi/src/components/Balance.tsx @@ -0,0 +1,9 @@ +import { useBalance } from 'wagmi'; + +export function Balance() { + const balance = useBalance({ + address: '0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A', + }); + + return
{balance &&

Balance: {balance.data?.value.toString()}

}
; +} diff --git a/code/wagmi/src/components/BalanceTS.tsx b/code/wagmi/src/components/BalanceTS.tsx new file mode 100644 index 00000000..ea0da6f1 --- /dev/null +++ b/code/wagmi/src/components/BalanceTS.tsx @@ -0,0 +1,18 @@ +import { fetchBalance } from '@/utils/balance'; +import { useState } from 'react'; + +export function BalanceTS() { + const [balance, setBalance] = useState(); + + const updateBalance = async () => { + const newBalance = await fetchBalance(); + setBalance(newBalance); + }; + + return ( +
+ + {balance &&

Balance: {balance.toString()}

} +
+ ); +} diff --git a/code/wagmi/src/components/Block.tsx b/code/wagmi/src/components/Block.tsx new file mode 100644 index 00000000..66c5e918 --- /dev/null +++ b/code/wagmi/src/components/Block.tsx @@ -0,0 +1,7 @@ +import { useBlockNumber } from 'wagmi'; + +export function Block() { + const block = useBlockNumber(); + + return
{block &&

Block: {block.data?.toString()}

}
; +} diff --git a/code/wagmi/src/components/BlockTS.tsx b/code/wagmi/src/components/BlockTS.tsx new file mode 100644 index 00000000..1ebefbfc --- /dev/null +++ b/code/wagmi/src/components/BlockTS.tsx @@ -0,0 +1,18 @@ +import { fetchLatestBlockNumber } from '@/utils/block'; +import { useState } from 'react'; + +export function BlockTS() { + const [block, setBlock] = useState(); + + const updateBlock = async () => { + const newBlock = await fetchLatestBlockNumber(); + setBlock(newBlock); + }; + + return ( +
+ + {block &&

Block: {block.toString()}

} +
+ ); +} diff --git a/code/wagmi/src/components/ConnectWallet.tsx b/code/wagmi/src/components/ConnectWallet.tsx new file mode 100644 index 00000000..12a5e452 --- /dev/null +++ b/code/wagmi/src/components/ConnectWallet.tsx @@ -0,0 +1,12 @@ +import { injected } from '@wagmi/connectors'; +import { useConnect } from 'wagmi'; + +export function ConnectWallet() { + const { connect } = useConnect(); + + return ( +
+ +
+ ); +} diff --git a/code/wagmi/src/components/ConnectWalletTS.tsx b/code/wagmi/src/components/ConnectWalletTS.tsx new file mode 100644 index 00000000..54cf2c53 --- /dev/null +++ b/code/wagmi/src/components/ConnectWalletTS.tsx @@ -0,0 +1,9 @@ +import { connectWallet } from '@/utils/connect'; + +export function ConnectWalletTS() { + return ( +
+ +
+ ); +} diff --git a/code/wagmi/src/components/ReadContract.tsx b/code/wagmi/src/components/ReadContract.tsx new file mode 100644 index 00000000..3e785d42 --- /dev/null +++ b/code/wagmi/src/components/ReadContract.tsx @@ -0,0 +1,67 @@ +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 ( +
+
+ +
+ +
+
+ ); +} + +function TotalSupply() { + const { data, isRefetching, refetch } = useReadContract({ + abi: erc20TokenABI.abi, + address: CONTRACT_ADDRESS, + functionName: 'totalSupply', + }); + + console.log('data', data); + + return ( +
+ Total Supply: {data?.toString()} + +
+ ); +} + +function BalanceOf() { + const [address, setAddress] = useState
('0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A'); + const { data, error, isLoading, isSuccess } = useReadContract({ + abi: erc20TokenABI.abi, + address: CONTRACT_ADDRESS, + functionName: 'balanceOf', + args: [address], + }); + + const [value, setValue] = useState(address); + + return ( +
+ Token balance: {isSuccess && data?.toString()} + setValue(e.target.value)} + placeholder="wallet address" + style={{ marginLeft: 4 }} + value={value} + /> + + {error &&
{(error as BaseError).shortMessage}
} +
+ ); +} diff --git a/code/wagmi/src/components/ReadContractTS.tsx b/code/wagmi/src/components/ReadContractTS.tsx new file mode 100644 index 00000000..fb2a75c3 --- /dev/null +++ b/code/wagmi/src/components/ReadContractTS.tsx @@ -0,0 +1,24 @@ +import { readERC20Contract } from '@/utils/read'; +import { useState } from 'react'; + +export function ReadContractTS() { + const [data, setData] = useState(); + + async function updateTotalSupply() { + const supply = await readERC20Contract('totalSupply'); + const newData = supply as bigint; + setData(newData.toString()); + } + + return ( +
+ {data &&
Total Supply: {data?.toString()}
} + +
+ ); +} diff --git a/code/wagmi/src/components/SendTx.tsx b/code/wagmi/src/components/SendTx.tsx new file mode 100644 index 00000000..be46fff2 --- /dev/null +++ b/code/wagmi/src/components/SendTx.tsx @@ -0,0 +1,37 @@ +import { parseEther } from 'viem'; +import { useSendTransaction } from 'wagmi'; + +export function SendTx() { + const { sendTransaction, isError, isPending, isSuccess, data, error } = useSendTransaction(); + + return ( + <> +
{ + 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), + }); + }} + > + + + +
+ + {isPending &&
Transaction pending...
} + {isSuccess &&
Transaction Hash: {data}
} + {isError &&
Error: {error?.message}
} + + ); +} diff --git a/code/wagmi/src/components/SendTxPrepared.tsx b/code/wagmi/src/components/SendTxPrepared.tsx new file mode 100644 index 00000000..1cb458fb --- /dev/null +++ b/code/wagmi/src/components/SendTxPrepared.tsx @@ -0,0 +1,49 @@ +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('0.0'); + + const { data: txRequest } = usePrepareTransactionRequest({ + to, + value: parseEther(value as `${number}`), + }); + + const { sendTransaction, isError, isPending, isSuccess, data, error } = useSendTransaction(); + + return ( + <> +
{ + e.preventDefault(); + if (!txRequest) return; + sendTransaction(txRequest); + }} + > + setTo(e.target.value as `0x${string}`)} + value={to} + /> + setValue(e.target.value)} + value={value?.toString()} + /> + +
+ + {isPending &&
Transaction pending...
} + {isSuccess &&
Transaction Hash: {data}
} + {isError &&
Error: {error?.message}
} + + ); +} diff --git a/code/wagmi/src/components/SendTxPreparedTS.tsx b/code/wagmi/src/components/SendTxPreparedTS.tsx new file mode 100644 index 00000000..5efe18b5 --- /dev/null +++ b/code/wagmi/src/components/SendTxPreparedTS.tsx @@ -0,0 +1,44 @@ +import { prepareTx } from '@/utils/prepareTx'; +import { useState } from 'react'; +import { config } from '../../wagmi-config'; +import { sendTransaction } from '@wagmi/core'; + +export function SendTxPreparedTS() { + const [data, setData] = useState(); + const [to, setTo] = useState<`0x${string}`>(); + const [value, setValue] = useState('0.0'); + + return ( + <> +
{ + e.preventDefault(); + if (!to) return; + const tx = await prepareTx(to, value as `${number}`); + const txData = await sendTransaction(config, tx); + setData(txData); + }} + > + setTo(e.target.value as `0x${string}`)} + value={to} + /> + setValue(e.target.value)} + value={value?.toString()} + /> + +
+ + {data &&
Transaction Hash: {data}
} + + ); +} diff --git a/code/wagmi/src/components/SendTxTS.tsx b/code/wagmi/src/components/SendTxTS.tsx new file mode 100644 index 00000000..17d64b94 --- /dev/null +++ b/code/wagmi/src/components/SendTxTS.tsx @@ -0,0 +1,32 @@ +import { sendTx } from '@/utils/sendTx'; +import { useState } from 'react'; + +export function SendTxTS() { + const [data, setData] = useState(); + return ( + <> +
{ + 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}`; + const txData = await sendTx(address, value); + setData(txData); + }} + > + + + +
+ + {data &&
Transaction Hash: {data}
} + + ); +} diff --git a/code/wagmi/src/components/ShowConnectors.tsx b/code/wagmi/src/components/ShowConnectors.tsx new file mode 100644 index 00000000..8a9a2ac7 --- /dev/null +++ b/code/wagmi/src/components/ShowConnectors.tsx @@ -0,0 +1,19 @@ +import { connectWallet } from '@/utils/connect'; +import { useConnectors } from 'wagmi'; + +export function ShowConnectors() { + const connectors = useConnectors(); + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ); +} diff --git a/code/wagmi/src/components/ShowConnectorsTS.tsx b/code/wagmi/src/components/ShowConnectorsTS.tsx new file mode 100644 index 00000000..ca74978d --- /dev/null +++ b/code/wagmi/src/components/ShowConnectorsTS.tsx @@ -0,0 +1,19 @@ +import { connectWallet } from '@/utils/connect'; +import { fetchConnectors } from '@/utils/connectors'; + +export function ShowConnectorsTS() { + const connectors = fetchConnectors(); + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ); +} diff --git a/code/wagmi/src/components/SignMessage.tsx b/code/wagmi/src/components/SignMessage.tsx new file mode 100644 index 00000000..09d181f7 --- /dev/null +++ b/code/wagmi/src/components/SignMessage.tsx @@ -0,0 +1,54 @@ +import { useEffect, useState } from 'react'; +import { type Address, recoverMessageAddress } from 'viem'; +import { useSignMessage } from 'wagmi'; + +export function SignMessage() { + const [recoveredAddress, setRecoveredAddress] = useState
(); + 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 ( + <> +
{ + event.preventDefault(); + const element = event.target as HTMLFormElement; + const formData = new FormData(element); + const message = formData.get('message') as string; + signMessage({ message }); + }} + > + + +
+ + {signature && ( +
+
Signature: {signature}
+
Recovered address: {recoveredAddress}
+
+ )} + {error &&
Error: {error?.message}
} + + ); +} diff --git a/code/wagmi/src/components/SignMessageTS.tsx b/code/wagmi/src/components/SignMessageTS.tsx new file mode 100644 index 00000000..22851d33 --- /dev/null +++ b/code/wagmi/src/components/SignMessageTS.tsx @@ -0,0 +1,30 @@ +import { getSignedMessage } from '@/utils/message'; +import { useState } from 'react'; + +export function SignMessageTS() { + const [data, setData] = useState(); + + return ( + <> +
{ + event.preventDefault(); + const element = event.target as HTMLFormElement; + const formData = new FormData(element); + const message = formData.get('message') as string; + const signature = await getSignedMessage(message); + setData(signature); + }} + > + + +
+ + {data &&
Signature: {data}
} + + ); +} diff --git a/code/wagmi/src/components/SignTypedData.tsx b/code/wagmi/src/components/SignTypedData.tsx new file mode 100644 index 00000000..e19deb85 --- /dev/null +++ b/code/wagmi/src/components/SignTypedData.tsx @@ -0,0 +1,42 @@ +import { useSignTypedData } from 'wagmi'; + +export function SignTypedData() { + const { signTypedData, data } = useSignTypedData(); + + return ( + <> + +
{data &&

Signature: {data}

}
+ + ); +} diff --git a/code/wagmi/src/components/SignTypedDataTS.tsx b/code/wagmi/src/components/SignTypedDataTS.tsx new file mode 100644 index 00000000..5335848a --- /dev/null +++ b/code/wagmi/src/components/SignTypedDataTS.tsx @@ -0,0 +1,44 @@ +import { getSignedTypedData } from '@/utils/signTyped'; +import { useState } from 'react'; + +export function SignTypedDataTS() { + const [signature, setSignature] = useState(); + + return ( + <> + +
{signature &&

Signature: {signature}

}
+ + ); +} diff --git a/code/wagmi/src/components/WriteContract.tsx b/code/wagmi/src/components/WriteContract.tsx new file mode 100644 index 00000000..e5dcd1b1 --- /dev/null +++ b/code/wagmi/src/components/WriteContract.tsx @@ -0,0 +1,34 @@ +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(); + 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 ( +
+ setGreeting(e.target.value)} + placeholder="Hello, Zeek!" + style={{ marginLeft: 4 }} + value={greeting} + /> + + {isError &&
{(error as BaseError).shortMessage}
} + {isSuccess &&
Transaction hash: {data}
} +
+ ); +} diff --git a/code/wagmi/src/components/WriteContractTS.tsx b/code/wagmi/src/components/WriteContractTS.tsx new file mode 100644 index 00000000..cae8ab14 --- /dev/null +++ b/code/wagmi/src/components/WriteContractTS.tsx @@ -0,0 +1,26 @@ +import { useState } from 'react'; +import { writeToGreeterContract } from '../utils/write'; + +export function WriteContractTS() { + const [greeting, setGreeting] = useState(); + const [data, setData] = useState(); + + async function updateGreeting() { + if (!greeting) return; + const txData = await writeToGreeterContract(greeting); + setData(txData as string); + } + + return ( +
+ setGreeting(e.target.value)} + placeholder="Hello, Zeek!" + style={{ marginLeft: 4 }} + value={greeting} + /> + + {data &&
Transaction hash: {data}
} +
+ ); +} diff --git a/code/wagmi/src/pages/_app.tsx b/code/wagmi/src/pages/_app.tsx new file mode 100644 index 00000000..0b5cff4f --- /dev/null +++ b/code/wagmi/src/pages/_app.tsx @@ -0,0 +1,16 @@ +import type { AppProps } from 'next/app'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { WagmiProvider } from 'wagmi'; +import { config } from '../../wagmi-config'; + +const queryClient = new QueryClient(); + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + ; + + + ); +} diff --git a/code/wagmi/src/pages/_document.tsx b/code/wagmi/src/pages/_document.tsx new file mode 100644 index 00000000..e1e9cbbb --- /dev/null +++ b/code/wagmi/src/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document'; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/code/wagmi/src/pages/index.tsx b/code/wagmi/src/pages/index.tsx new file mode 100644 index 00000000..6ebd9cd2 --- /dev/null +++ b/code/wagmi/src/pages/index.tsx @@ -0,0 +1,82 @@ +import Head from 'next/head'; +import { useEffect, useState } from 'react'; +import { ConnectWalletTS } from '@/components/ConnectWalletTS'; +import { ConnectWallet } from '@/components/ConnectWallet'; +import { ShowConnectors } from '@/components/ShowConnectors'; +import { ShowConnectorsTS } from '@/components/ShowConnectorsTS'; +import { Account } from '@/components/Account'; +import { AccountTS } from '@/components/AccountTS'; +import { Balance } from '@/components/Balance'; +import { BalanceTS } from '@/components/BalanceTS'; +import { Block } from '@/components/Block'; +import { BlockTS } from '@/components/BlockTS'; +import { SendTx } from '@/components/SendTx'; +import { SendTxTS } from '@/components/SendTxTS'; +import { SendTxPrepared } from '@/components/SendTxPrepared'; +import { SendTxPreparedTS } from '@/components/SendTxPreparedTS'; +import { SignMessage } from '@/components/SignMessage'; +import { SignMessageTS } from '@/components/SignMessageTS'; +import { SignTypedData } from '@/components/SignTypedData'; +import { SignTypedDataTS } from '@/components/SignTypedDataTS'; +import { ReadContract } from '@/components/ReadContract'; +import { ReadContractTS } from '@/components/ReadContractTS'; +import { WriteContract } from '@/components/WriteContract'; +import { WriteContractTS } from '@/components/WriteContractTS'; + +export default function Home() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + return ( + <> + + Wagmi ZKsync Demo + + + + +
+
+ {mounted && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + )} +
+
+ + ); +} diff --git a/code/wagmi/src/utils/account.ts b/code/wagmi/src/utils/account.ts new file mode 100644 index 00000000..dd711bc2 --- /dev/null +++ b/code/wagmi/src/utils/account.ts @@ -0,0 +1,7 @@ +import { getAccount } from '@wagmi/core'; +import { config } from '../../wagmi-config'; + +export function fetchAccount() { + const { address } = getAccount(config); + return address; +} diff --git a/code/wagmi/src/utils/balance.ts b/code/wagmi/src/utils/balance.ts new file mode 100644 index 00000000..ce0ac113 --- /dev/null +++ b/code/wagmi/src/utils/balance.ts @@ -0,0 +1,8 @@ +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; +} diff --git a/code/wagmi/src/utils/block.ts b/code/wagmi/src/utils/block.ts new file mode 100644 index 00000000..1357ffd5 --- /dev/null +++ b/code/wagmi/src/utils/block.ts @@ -0,0 +1,7 @@ +import { getBlockNumber } from '@wagmi/core'; +import { config } from '../../wagmi-config'; + +export async function fetchLatestBlockNumber() { + const blockNumber = await getBlockNumber(config); + return blockNumber; +} diff --git a/code/wagmi/src/utils/connect.ts b/code/wagmi/src/utils/connect.ts new file mode 100644 index 00000000..7dc8adfc --- /dev/null +++ b/code/wagmi/src/utils/connect.ts @@ -0,0 +1,11 @@ +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); + } +} diff --git a/code/wagmi/src/utils/connectors.ts b/code/wagmi/src/utils/connectors.ts new file mode 100644 index 00000000..1a923e38 --- /dev/null +++ b/code/wagmi/src/utils/connectors.ts @@ -0,0 +1,7 @@ +import { getConnectors } from '@wagmi/core'; +import { config } from '../../wagmi-config'; + +export function fetchConnectors() { + const connectors = getConnectors(config); + return connectors; +} diff --git a/code/wagmi/src/utils/message.ts b/code/wagmi/src/utils/message.ts new file mode 100644 index 00000000..d97a716a --- /dev/null +++ b/code/wagmi/src/utils/message.ts @@ -0,0 +1,7 @@ +import { signMessage } from '@wagmi/core'; +import { config } from '../../wagmi-config'; + +export async function getSignedMessage(message: string) { + const signature = await signMessage(config, { message }); + return signature; +} diff --git a/code/wagmi/src/utils/prepareTx.ts b/code/wagmi/src/utils/prepareTx.ts new file mode 100644 index 00000000..5854fd87 --- /dev/null +++ b/code/wagmi/src/utils/prepareTx.ts @@ -0,0 +1,12 @@ +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; +} diff --git a/code/wagmi/src/utils/read.ts b/code/wagmi/src/utils/read.ts new file mode 100644 index 00000000..1327207c --- /dev/null +++ b/code/wagmi/src/utils/read.ts @@ -0,0 +1,14 @@ +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; +} diff --git a/code/wagmi/src/utils/sendTx.ts b/code/wagmi/src/utils/sendTx.ts new file mode 100644 index 00000000..dd588e82 --- /dev/null +++ b/code/wagmi/src/utils/sendTx.ts @@ -0,0 +1,12 @@ +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; +} diff --git a/code/wagmi/src/utils/signTyped.ts b/code/wagmi/src/utils/signTyped.ts new file mode 100644 index 00000000..d085471d --- /dev/null +++ b/code/wagmi/src/utils/signTyped.ts @@ -0,0 +1,7 @@ +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; +} diff --git a/code/wagmi/src/utils/write.ts b/code/wagmi/src/utils/write.ts new file mode 100644 index 00000000..335c7481 --- /dev/null +++ b/code/wagmi/src/utils/write.ts @@ -0,0 +1,15 @@ +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; +} diff --git a/code/wagmi/tsconfig.json b/code/wagmi/tsconfig.json new file mode 100644 index 00000000..572b7ad3 --- /dev/null +++ b/code/wagmi/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/code/wagmi/wagmi-config.ts b/code/wagmi/wagmi-config.ts new file mode 100644 index 00000000..fd6717ab --- /dev/null +++ b/code/wagmi/wagmi-config.ts @@ -0,0 +1,18 @@ +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(), + }, +}); diff --git a/content/tutorials/guide-viem/_dir.yml b/content/tutorials/guide-viem/_dir.yml index e2412e89..035f39a0 100644 --- a/content/tutorials/guide-viem/_dir.yml +++ b/content/tutorials/guide-viem/_dir.yml @@ -8,6 +8,7 @@ github_repo: https://github.com/matter-labs tags: - guide - viem + - frontend summary: This page will explain how to use the viem/zksync plugin to interact with ZKsync. description: This guide outlines how to use the ZKsync viem plugin to interact with ZKsync and use specific actions like sending diff --git a/content/tutorials/guide-wagmi/10.index.md b/content/tutorials/guide-wagmi/10.index.md index 4da524ce..c2197a85 100644 --- a/content/tutorials/guide-wagmi/10.index.md +++ b/content/tutorials/guide-wagmi/10.index.md @@ -11,507 +11,212 @@ For setup instructions, consult the official documentation [here](https://wagmi. Wagmi hooks do not yet support Paymasters and native Account Abstraction; development is in progress. :: +## Project Setup + +### Installing Dependencies + +::code-group + +```bash [npm] +npm install @wagmi/core @wagmi/connectors viem@2.x +``` + +```bash [yarn] +yarn add @wagmi/core @wagmi/connectors viem@2.x +``` + +```bash [pnpm] +pnpm add @wagmi/core @wagmi/connectors viem@2.x +``` + +```bash [bun] +bun add @wagmi/core @wagmi/connectors viem@2.x +``` + +:: + +For React projects, you can also make use of the `wagmi` package: + +::code-group + +```bash [npm] +npm install wagmi viem@2.x @tanstack/react-query +``` + +```bash [yarn] +yarn add wagmi viem@2.x @tanstack/react-query +``` + +```bash [pnpm] +pnpm add wagmi viem@2.x @tanstack/react-query +``` + +```bash [bun] +bun add wagmi viem@2.x @tanstack/react-query +``` + +:: + +### Configuration + +Make a file called `wagmi-config.ts` and configure your preferred ZKsync network. + +```ts [wagmi-config.ts] +:code-import{filePath="wagmi/wagmi-config.ts"} +``` + Here are some common actions: ## Connect Wallet - -```typescript -import { w3mConnectors, w3mProvider } from "@web3modal/ethereum"; -import { configureChains, createConfig } from "wagmi"; -import { zkSync, zkSyncTestnet } from "wagmi/chains"; - -export const walletConnectProjectId = "d4a7167a6eed6a53c8364631aaeca861"; - -const { chains, publicClient, webSocketPublicClient } = configureChains( - [zkSync, ...(import.meta.env?.MODE === "development" ? [zkSyncTestnet] : [])], - [w3mProvider({ projectId: walletConnectProjectId })] -); - -export const config = createConfig({ - autoConnect: true, - connectors: w3mConnectors({ - chains, - projectId: walletConnectProjectId, - version: 2, - }), - publicClient, - webSocketPublicClient, -}); - -export { chains }; + +### `connect` Method + +```jsx [connect.ts] +:code-import{filePath="wagmi/src/utils/connect.ts"} ``` - + +### `useConnect` Hook + +```jsx [ConnectWallet.tsx] +:code-import{filePath="wagmi/src/components/ConnectWallet.tsx"} +``` + ## Display Wallet Options -```typescript -import { useConnect } from "wagmi"; - -export function WalletOptions() { - const { connect, connectors, error, isLoading, pendingConnector } = useConnect(); - console.log(connectors); - - return ( -
- {connectors.map((connector) => ( - - ))} - - {error &&
{error.message}
} -
- ); -} +### `getConnectors` Method + +```ts [connectors.ts] +:code-import{filePath="wagmi/src/utils/connectors.ts"} +``` + +### `useConnectors` Hook + +```jsx [ShowConnectors.tsx] +:code-import{filePath="wagmi/src/components/ShowConnectors.tsx"} ``` ## Fetch Account -```typescript -import { useAccount } from "wagmi"; +### `getAccount` Method -export function Account() { - const { address } = useAccount(); +```ts [account.ts] +:code-import{filePath="wagmi/src/utils/account.ts"} +``` - return
{address}
; -} +### `useAccount` Hook + +```jsx [Account.tsx] +:code-import{filePath="wagmi/src/components/Account.tsx"} ``` ## Fetch Balance -```typescript -import { useState } from "react"; -import type { Address } from "wagmi"; -import { useAccount, useBalance } from "wagmi"; - -export function Balance() { - return ( - <> -
- -
-
-
- -
- - ); -} - -export function AccountBalance() { - const { address } = useAccount(); - const { data, refetch } = useBalance({ - address, - watch: true, - }); - - return ( -
- {data?.formatted} - -
- ); -} - -export function FindBalance() { - const [address, setAddress] = useState(""); - const { data, isLoading, refetch } = useBalance({ - address: address as Address, - }); - - const [value, setValue] = useState(""); - - return ( -
- Find balance: setValue(e.target.value)} placeholder="wallet address" value={value} /> - -
{data?.formatted}
-
- ); -} +### `getBalance` Method + +```ts [balance.ts] +:code-import{filePath="wagmi/src/utils/balance.ts"} +``` + +### `useBalance` Hook + +```jsx [Balance.tsx] +:code-import{filePath="wagmi/src/components/Balance.tsx"} ``` ## Fetch Block Number -```typescript -import { useBlockNumber } from "wagmi"; +### `getBlockNumber` Method -export function BlockNumber() { - const { data } = useBlockNumber({ watch: true }); - return
{data?.toString()}
; -} +```ts [block.ts] +:code-import{filePath="wagmi/src/utils/block.ts"} +``` + +### `useBlockNumber` Hook + +```jsx [Block.tsx] +:code-import{filePath="wagmi/src/components/Block.tsx"} ``` ## Send Transaction -```typescript -import { parseEther } from "viem"; -import { useSendTransaction, useWaitForTransaction } from "wagmi"; - -import { stringify } from "../utils/stringify"; - -export function SendTransaction() { - const { data, error, isLoading, isError, sendTransaction } = useSendTransaction(); - const { data: receipt, isLoading: isPending, isSuccess } = useWaitForTransaction({ hash: data?.hash }); - - return ( - <> -
{ - e.preventDefault(); - const formData = new FormData(e.target as HTMLFormElement); - const address = formData.get("address") as string; - const value = formData.get("value") as `${number}`; - sendTransaction({ - to: address, - value: parseEther(value), - }); - }} - > - - - -
- - {isLoading &&
Check wallet...
} - {isPending &&
Transaction pending...
} - {isSuccess && ( - <> -
Transaction Hash: {data?.hash}
-
- Transaction Receipt:
{stringify(receipt, null, 2)}
-
- - )} - {isError &&
Error: {error?.message}
} - - ); -} +### `sendTransaction` Method + +```ts [sendTx.ts] +:code-import{filePath="wagmi/src/utils/sendTx.ts"} +``` + +### `useSendTransaction` Hook + +```jsx [SendTx.tsx] +:code-import{filePath="wagmi/src/components/SendTx.tsx"} ``` ## Send Transaction (Prepared) -```typescript -import { useState } from "react"; -import { parseEther, stringify } from "viem"; -import { usePrepareSendTransaction, useSendTransaction, useWaitForTransaction } from "wagmi"; - -import { useDebounce } from "../hooks/useDebounce"; - -export function SendTransactionPrepared() { - const [to, setTo] = useState(""); - const debouncedTo = useDebounce(to); - - const [value, setValue] = useState(""); - const debouncedValue = useDebounce(value); - - const { config } = usePrepareSendTransaction({ - to: debouncedTo, - value: debouncedValue ? parseEther(value as `${number}`) : undefined, - enabled: Boolean(debouncedTo && debouncedValue), - }); - const { data, error, isLoading, isError, sendTransaction } = useSendTransaction(config); - const { data: receipt, isLoading: isPending, isSuccess } = useWaitForTransaction({ hash: data?.hash }); - - return ( - <> -
{ - e.preventDefault(); - sendTransaction?.(); - }} - > - setTo(e.target.value)} value={to} /> - setValue(e.target.value)} value={value} /> - -
- - {isLoading &&
Check wallet...
} - {isPending &&
Transaction pending...
} - {isSuccess && ( - <> -
Transaction Hash: {data?.hash}
-
- Transaction Receipt:
{stringify(receipt, null, 2)}
-
- - )} - {isError &&
Error: {error?.message}
} - - ); -} +### `prepareTransactionRequest` Method + +```ts [prepareTx.ts] +:code-import{filePath="wagmi/src/utils/prepareTx.ts"} +``` + +### `usePrepareTransactionRequest` Hook + +```jsx [SendTxPrepared.tsx] +:code-import{filePath="wagmi/src/components/SendTxPrepared.tsx"} ``` ## Sign Message -```typescript -import { useEffect, useState } from "react"; -import { recoverMessageAddress } from "viem"; -import { type Address, useSignMessage } from "wagmi"; - -export function SignMessage() { - const [recoveredAddress, setRecoveredAddress] = useState
(); - const { data: signature, variables, error, isLoading, signMessage } = useSignMessage(); - - useEffect(() => { - (async () => { - if (variables?.message && signature) { - const recoveredAddress = await recoverMessageAddress({ - message: variables?.message, - signature, - }); - setRecoveredAddress(recoveredAddress); - } - })(); - }, [signature, variables?.message]); - - return ( - <> -
{ - event.preventDefault(); - const element = event.target as HTMLFormElement; - const formData = new FormData(element); - const message = formData.get("message") as string; - signMessage({ message }); - }} - > - - -
- - {signature && ( -
-
Signature: {signature}
-
Recovered address: {recoveredAddress}
-
- )} - {error &&
Error: {error?.message}
} - - ); -} +### `signMessage` Method + +```ts [message.ts] +:code-import{filePath="wagmi/src/utils/message.ts"} +``` + +### `useSignMessage` Hook + +```jsx [SignMessage.tsx] +:code-import{filePath="wagmi/src/components/SignMessage.tsx"} ``` ## Sign Typed Data -```typescript -import { useEffect, useState } from "react"; -import { recoverTypedDataAddress } from "viem"; -import { type Address, useSignTypedData } from "wagmi"; - -const domain = { - name: "Ether Mail", - version: "1", - chainId: 280 || 324, - verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", -} as const; - -const types = { - Person: [ - { name: "name", type: "string" }, - { name: "wallet", type: "address" }, - ], - Mail: [ - { name: "from", type: "Person" }, - { name: "to", type: "Person" }, - { name: "contents", type: "string" }, - ], -} as const; - -const message = { - from: { - name: "Cow", - wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - }, - to: { - name: "Bob", - wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - }, - contents: "Hello, Bob!", -} as const; - -export function SignTypedData() { - const { data, error, isLoading, signTypedData } = useSignTypedData({ - domain, - types, - message, - primaryType: "Mail", - }); - - const [recoveredAddress, setRecoveredAddress] = useState
(); - useEffect(() => { - if (!data) return; - (async () => { - setRecoveredAddress( - await recoverTypedDataAddress({ - domain, - types, - message, - primaryType: "Mail", - signature: data, - }) - ); - })(); - }, [data]); - - return ( - <> - - - {data && ( -
-
Signature: {data}
-
Recovered address {recoveredAddress}
-
- )} - {error &&
Error: {error?.message}
} - - ); -} +### `signTypedData` Method + +```ts [signTyped.ts] +:code-import{filePath="wagmi/src/utils/signTyped.ts"} +``` + +### `useSignTypedData` Hook + +```jsx [SignTypedData.tsx] +:code-import{filePath="wagmi/src/components/SignTypedData.tsx"} ``` ## Read Contract -```typescript -import { useState } from "react"; -import { BaseError } from "viem"; -import { type Address, useContractRead } from "wagmi"; - -import { erc20TokenABI } from "./contracts"; - -export function ReadContract() { - return ( -
-
- -
- -
-
- ); -} - -function TotalSupply() { - const { data, isRefetching, refetch } = useContractRead({ - ...erc20TokenABI, - functionName: "totalSupply", - }); - - return ( -
- Total Supply: {data?.toString()} - -
- ); -} - -function BalanceOf() { - const [address, setAddress] = useState
("0x3e7676937A7E96CFB7616f255b9AD9FF47363D4b"); - const { data, error, isLoading, isSuccess } = useContractRead({ - ...erc20TokenABI, - functionName: "balanceOf", - args: [address], - enabled: Boolean(address), - }); - - const [value, setValue] = useState(address); - - return ( -
- Token balance: {isSuccess && data?.toString()} - setValue(e.target.value)} placeholder="wallet address" style={{ marginLeft: 4 }} value={value} /> - - {error &&
{(error as BaseError).shortMessage}
} -
- ); -} +### `readContract` Method + +```ts [signTyped.ts] +:code-import{filePath="wagmi/src/utils/read.ts"} ``` -## Token - -```typescript -import { useState } from "react"; -import { type Address, useToken } from "wagmi"; - -export function Token() { - const [address, setAddress] = useState
("0x3e7676937A7E96CFB7616f255b9AD9FF47363D4b"); - const { data, error, isError, isLoading, refetch } = useToken({ address }); - - return ( - <> -
- setAddress(e.target.value as Address)} placeholder="token address" value={address} /> - -
- - {data && ( -
- {data.totalSupply?.formatted} {data.symbol} -
- )} - {isLoading &&
Fetching token...
} - {isError &&
Error: {error?.message}
} - - ); -} +### `useReadContract` Hook + +```jsx [ReadContract.tsx] +:code-import{filePath="wagmi/src/components/ReadContract.tsx"} ``` ## Write Contract -```typescript -import { BaseError } from "viem"; -import { useContractWrite, useWaitForTransaction } from "wagmi"; - -import { erc721Contract } from "./contracts"; -import { stringify } from "../utils/stringify"; - -export function WriteContract() { - const { write, data, error, isLoading, isError } = useContractWrite({ - ...erc721Contract, - functionName: "mint", - }); - const { data: receipt, isLoading: isPending, isSuccess } = useWaitForTransaction({ hash: data?.hash }); - - return ( - <> -

Mint an NFT

-
{ - e.preventDefault(); - const formData = new FormData(e.target as HTMLFormElement); - const tokenId = formData.get("tokenId") as string; - write({ - args: [BigInt(tokenId)], - }); - }} - > - - -
- - {isLoading &&
Check wallet...
} - {isPending &&
Transaction pending...
} - {isSuccess && ( - <> -
Transaction Hash: {data?.hash}
-
- Transaction Receipt:
{stringify(receipt, null, 2)}
-
- - )} - {isError &&
{(error as BaseError)?.shortMessage}
} - - ); -} +### `writeContract` Method + +```ts [write.ts] +:code-import{filePath="wagmi/src/utils/write.ts"} +``` + +### `useWriteContract` Hook + +```jsx [WriteContract.tsx] +:code-import{filePath="wagmi/src/components/WriteContract.tsx"} ``` diff --git a/content/tutorials/guide-wagmi/_dir.yml b/content/tutorials/guide-wagmi/_dir.yml index a4036575..4de6d2c1 100644 --- a/content/tutorials/guide-wagmi/_dir.yml +++ b/content/tutorials/guide-wagmi/_dir.yml @@ -7,6 +7,7 @@ github_repo: https://github.com/matter-labs tags: - guide - wagmi + - frontend summary: Learn how to use the Wagmi toolkit to create websites that interact with contracts on ZKsync description: This guide outlines how to use the wagmi library to create websites that interact with ZKsync contracts. We provide diff --git a/content/tutorials/guide-walletconnect/_dir.yml b/content/tutorials/guide-walletconnect/_dir.yml index 38d29c8f..86946ab4 100644 --- a/content/tutorials/guide-walletconnect/_dir.yml +++ b/content/tutorials/guide-walletconnect/_dir.yml @@ -7,6 +7,7 @@ github_repo: https://github.com/matter-labs tags: - guide - walletconnect + - frontend summary: Learn how to use WalletConnect to create React apps that interact with contracts on ZKsync. description: This guide outlines how to use WalletConnect and Web3Modal to create React apps that interact with contracts on ZKsync diff --git a/content/tutorials/guide-web3js/_dir.yml b/content/tutorials/guide-web3js/_dir.yml index a380720e..86329fe0 100644 --- a/content/tutorials/guide-web3js/_dir.yml +++ b/content/tutorials/guide-web3js/_dir.yml @@ -8,6 +8,7 @@ github_repo: https://github.com/ChainSafe tags: - guide - web3.js + - frontend summary: This guide will teach you how to set up and use Web3.js to interact with ZKsync, leveraging the ZKsync Web3.js plugin. description: diff --git a/pages/tutorials/index.vue b/pages/tutorials/index.vue index 65d63a9a..feef75b2 100644 --- a/pages/tutorials/index.vue +++ b/pages/tutorials/index.vue @@ -24,7 +24,7 @@ const allTags = computed(() => { const uniqueTags = computed(() => [...new Set(allTags.value)]); -const activeTag = ref(uniqueTags.value[0] || ''); +const activeTag = ref(''); const isExpanded = ref(false); const defaultCount = 6;