Prerequisites & Project Setup
This guide targets the Tiramisu testnet. Check Networks for current testnets and faucets.
Before you write code, install the tools in this section and set up the project structure.
1. Prerequisites
Section titled “1. Prerequisites”Make sure that you have these tools on your system:
- Node.js: version v20.6.0 or higher.
- A text editor: a code editor like VS Code works well.
- Test GLM: test GLM pays for gas on the Tiramisu testnet, the native gas token of Arkiv. The app prompts MetaMask to switch to Tiramisu, or to add it, automatically.
2. Project Setup
Section titled “2. Project Setup”Use Vite with a vanilla TypeScript template to scaffold the project.
Open your terminal and run these commands:
-
Create and enter the project:
Terminal window npm create vite@latest arkiv-sketch-app -- --template vanilla-tscd arkiv-sketch-app -
Install dependencies. Install the Arkiv SDK for blockchain interaction and
p5for the drawing canvas.Terminal window npm install @arkiv-network/sdk viem p5 @types/p5 -
Clean up default files and create your project files:
Terminal window rm -rf public src/counter.ts src/typescript.svg src/vite-env.d.tstouch src/wallet.ts src/sketch.ts
Open PowerShell and run these commands:
-
Create and enter the project:
Terminal window npm create vite@latest arkiv-sketch-app -- --template vanilla-tscd arkiv-sketch-app -
Install dependencies. Install the Arkiv SDK for blockchain interaction and
p5for the drawing canvas.Terminal window npm install @arkiv-network/sdk viem p5 @types/p5 -
Clean up default files and create your project files:
Terminal window Remove-Item -Recurse public, src\counter.ts, src\typescript.svg, src\vite-env.d.tsNew-Item src\wallet.ts, src\sketch.ts
Your project structure now looks like this:
Directoryarkiv-sketch-app/
Directorysrc/
- main.ts
- style.css
- wallet.ts
- sketch.ts
- index.html
- vite.config.ts
- package.json
Directorynode_modules/
- …
3. Configure Vite for WASM
Section titled “3. Configure Vite for WASM”The Arkiv SDK uses WASM internally. Tell Vite to exclude the WASM package from dependency optimization. Create or replace vite.config.ts:
Directoryarkiv-sketch-app/
Directorysrc/
- …
- index.html
- vite.config.ts
- package.json
import { defineConfig } from "vite";
export default defineConfig({optimizeDeps: { exclude: ["brotli-wasm", "brotli-wasm/pkg.bundler/brotli_wasm_bg.wasm"],},});4. HTML Structure
Section titled “4. HTML Structure”Replace the contents of index.html with the skeleton of the app. The app needs a header with a connect button, a left panel for the gallery, and a right panel for the drawing canvas.
Directoryarkiv-sketch-app/
Directorysrc/
- …
- index.html
- vite.config.ts
- package.json
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Arkiv Sketch App</title> </head> <body> <div id="app"> <header> <h1>Arkiv Sketch App</h1> <button id="connect-btn">Connect MetaMask</button> <div id="account"></div> </header> <div class="container"> <div class="left-panel"> <h2>Recent Sketches</h2> <div id="sketch-list"> <p>Connect your wallet to see sketches</p> </div> </div> <div class="right-panel"> <h2>Draw Something</h2> <div id="canvas-container"></div> <div class="controls"> <button id="reset-btn">Reset</button> <button id="save-btn">Save</button> </div> </div> </div> </div> <script type="module" src="/src/main.ts"></script> </body></html>5. MetaMask Wallet Integration
Section titled “5. MetaMask Wallet Integration”Now build the most important part of this tutorial: the connection to MetaMask. Open src/wallet.ts and build it step by step.
Directoryarkiv-sketch-app/
Directorysrc/
- wallet.ts
- sketch.ts
- main.ts
- style.css
- index.html
Step 1: Imports and Chain Switching
Section titled “Step 1: Imports and Chain Switching”First, import the SDK. Then write a helper function that switches MetaMask to the Tiramisu testnet. If MetaMask has not added the chain yet, the function adds it.
import { createPublicClient, createWalletClient } from "@arkiv-network/sdk";import { tiramisu } from "@arkiv-network/sdk/chains";import { custom, http } from "viem";import "viem/window";
async function switchToTiramisuChain() {if (!window.ethereum) { throw new Error("MetaMask not installed");}
const chainIdHex = `0x${tiramisu.id.toString(16)}`;
try { await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: chainIdHex }], });} catch (error: unknown) { if ( error && typeof error === "object" && "code" in error && error.code === 4902 ) { // Chain not added yet: add it await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: chainIdHex, chainName: tiramisu.name, nativeCurrency: tiramisu.nativeCurrency, rpcUrls: tiramisu.rpcUrls.default.http, blockExplorerUrls: ["https://tiramisu.explorer.arkiv.network"], // MetaMask makes this field optional, so leave it out. }, ], }); } else { throw error; }}}The switchToTiramisuChain function first tries wallet_switchEthereumChain. If MetaMask returns error code 4902, the chain does not exist yet. In that case, the function calls wallet_addEthereumChain. This call uses the chain details from the tiramisu object of the SDK.
Step 2: Connect Wallet
Section titled “Step 2: Connect Wallet”Next, add the connectWallet function. This function switches to the correct chain, then requests account access from MetaMask.
import { createPublicClient, createWalletClient } from "@arkiv-network/sdk";import { tiramisu } from "@arkiv-network/sdk/chains";import { custom, http } from "viem";import "viem/window";
async function switchToTiramisuChain() {if (!window.ethereum) { throw new Error("MetaMask not installed");}
const chainIdHex = `0x${tiramisu.id.toString(16)}`;
try { await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: chainIdHex }], });} catch (error: unknown) { if ( error && typeof error === "object" && "code" in error && error.code === 4902 ) { // Chain not added yet: add it await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: chainIdHex, chainName: tiramisu.name, nativeCurrency: tiramisu.nativeCurrency, rpcUrls: tiramisu.rpcUrls.default.http, blockExplorerUrls: ["https://tiramisu.explorer.arkiv.network"], }, ], }); } else { throw error; }}}
export async function connectWallet() {if (!window.ethereum) { throw new Error("MetaMask not installed");}
await switchToTiramisuChain();
const accounts = await window.ethereum.request({ method: "eth_requestAccounts",});
return accounts[0];}Step 3: Create Arkiv Clients
Section titled “Step 3: Create Arkiv Clients”Finally, add the factory function that creates two clients: a public client to read data, and a wallet client to write data. Export this function so other modules can use it.
import { createPublicClient, createWalletClient } from "@arkiv-network/sdk";import { tiramisu } from "@arkiv-network/sdk/chains";import { custom, http } from "viem";import "viem/window";
async function switchToTiramisuChain() {if (!window.ethereum) { throw new Error("MetaMask not installed");}
const chainIdHex = `0x${tiramisu.id.toString(16)}`;
try { await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: chainIdHex }], });} catch (error: unknown) { if ( error && typeof error === "object" && "code" in error && error.code === 4902 ) { // Chain not added yet: add it await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: chainIdHex, chainName: tiramisu.name, nativeCurrency: tiramisu.nativeCurrency, rpcUrls: tiramisu.rpcUrls.default.http, blockExplorerUrls: ["https://tiramisu.explorer.arkiv.network"], }, ], }); } else { throw error; }}}
export async function connectWallet() {if (!window.ethereum) { throw new Error("MetaMask not installed");}
await switchToTiramisuChain();
const accounts = await window.ethereum.request({ method: "eth_requestAccounts",});
return accounts[0];}
export function createArkivClients(account?: `0x${string}`) {if (!window.ethereum) { throw new Error("MetaMask not installed");}
const publicClient = createPublicClient({ chain: tiramisu, transport: http(),});
const walletClient = createWalletClient({ chain: tiramisu, transport: custom(window.ethereum), account,});
return { publicClient, walletClient };}The wallet integration is complete. In the next section, you build the data layer that saves and loads sketches.