diff --git a/.ai/categories/tooling.md b/.ai/categories/tooling.md
index 88d0f63bc..41ae91e34 100644
--- a/.ai/categories/tooling.md
+++ b/.ai/categories/tooling.md
@@ -25695,3 +25695,934 @@ Key features include:
- **Comprehensive documentation**: Includes usage guides and API references for both packages.
For detailed usage examples and API documentation, visit the [official Moonbeam XCM SDK documentation](https://moonbeam-foundation.github.io/xcm-sdk/latest/){target=\_blank}.
+
+
+---
+
+Page Title: Zero to Hero Smart Contract DApp
+
+- Source (raw): https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md
+- Canonical (HTML): https://docs.polkadot.com/smart-contracts/cookbook/dapps/zero-to-hero/
+- Summary: Learn how to build a decentralized application on Polkadot Hub using Viem and Next.js by creating a simple dApp that interacts with a smart contract.
+
+# Zero to Hero Smart Contract DApp
+
+Decentralized applications (dApps) are a key component of the Web3 ecosystem, enabling developers to build applications that communicate directly with blockchain networks. Polkadot Hub, a blockchain with smart contract support, serves as a robust platform for deploying and interacting with dApps.
+
+This tutorial will guide you through building a fully functional dApp that interacts with a smart contract on Polkadot Hub. You'll create and deploy a smart contract with Hardhat, and then use [Viem](https://viem.sh/){target=\_blank} for blockchain interactions and [Next.js](https://nextjs.org/){target=\_blank} for the frontend. By the end, you'll have a dApp that lets users connect their wallets, retrieve on-chain data, and execute transactions.
+
+## Prerequisites
+
+Before getting started, ensure you have the following:
+
+- [Node.js](https://nodejs.org/en){target=\_blank} v22.10.0 or later installed on your system.
+- A crypto wallet (such as MetaMask) funded with test tokens. Refer to the [Connect to Polkadot](/smart-contracts/connect){target=\_blank} guide for more details.
+- A basic understanding of React and JavaScript.
+- Some familiarity with blockchain fundamentals and Solidity (helpful but not required).
+
+## Project Overview
+
+This dApp will interact with a basic Storage contract that you will create and deploy with Hardhat. The contract will allow you to:
+
+- Store a number on the blockchain.
+- Retrieve the stored number from the blockchain.
+- Update the stored number with a new value.
+
+Your project directory will be organized as follows:
+
+```bash
+polkadot-hub-tutorial/
+├── storage-contract/ # Hardhat project for smart contract
+│ ├── contracts/
+│ │ └── Storage.sol
+│ ├── scripts/
+│ │ └── deploy.ts
+│ ├── artifacts/
+│ │ └── contracts/
+│ │ └── Storage.sol/
+│ │ └── Storage.json
+│ ├── hardhat.config.ts
+│ ├── .env
+│ └── package.json
+│
+└── dapp/ # Next.js dApp project
+ ├── abis/
+ │ └── Storage.json
+ └── app/
+ ├── components/
+ │ ├── ReadContract.tsx
+ │ ├── WalletConnect.tsx
+ │ └── WriteContract.tsx
+ ├── utils/
+ │ ├── contract.ts
+ │ └── viem.ts
+ ├── favicon.ico
+ ├── globals.css
+ ├── layout.tsx
+ └── page.tsx
+```
+
+Create the main folder for the project:
+
+```bash
+mkdir polkadot-hub-tutorial
+cd polkadot-hub-tutorial
+```
+
+## Create and Deploy the Storage Contract
+
+Before building the dApp, you'll need to create and deploy the Storage smart contract. This section will guide you through using Hardhat to write, compile, and deploy the contract to Polkadot Hub TestNet.
+
+### Set Up Hardhat Project
+
+First, create a new directory for your Hardhat project and initialize it:
+
+```bash
+mkdir storage-contract
+cd storage-contract
+npm init -y
+```
+
+Install Hardhat and its dependencies:
+
+```bash
+npm install --save-dev hardhat@3.0.9
+```
+
+Initialize a new Hardhat project:
+
+```bash
+npx hardhat --init
+```
+
+Select **Create a TypeScript project** and accept the default options.
+
+### Create the Storage Contract
+
+In the `contracts` directory, create a new file called `Storage.sol` and add the following code:
+
+```solidity title="Storage.sol"
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+contract Storage {
+ uint256 private storedNumber;
+
+ event NumberStored(uint256 newNumber);
+
+ function setNumber(uint256 _number) public {
+ storedNumber = _number;
+ emit NumberStored(_number);
+ }
+}
+```
+
+This simple contract stores a single number and provides functions to read and update it.
+
+### Configure Hardhat for Polkadot Hub
+
+Update your `hardhat.config.ts` file to include the Polkadot Hub TestNet configuration:
+
+```typescript title="hardhat.config.ts" hl_lines="39-44"
+import type { HardhatUserConfig } from "hardhat/config";
+
+import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
+import { configVariable } from "hardhat/config";
+
+const config: HardhatUserConfig = {
+ plugins: [hardhatToolboxViemPlugin],
+ solidity: {
+ profiles: {
+ default: {
+ version: "0.8.28",
+ },
+ production: {
+ version: "0.8.28",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ },
+ },
+ networks: {
+ hardhatMainnet: {
+ type: "edr-simulated",
+ chainType: "l1",
+ },
+ hardhatOp: {
+ type: "edr-simulated",
+ chainType: "op",
+ },
+ sepolia: {
+ type: "http",
+ chainType: "l1",
+ url: configVariable("SEPOLIA_RPC_URL"),
+ accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
+ },
+ polkadotTestNet: {
+ type: "http",
+ chainType: "l1",
+ url: 'http://127.0.0.1:8545',
+ accounts: [process.env.PRIVATE_KEY || ''],
+ },
+ },
+};
+
+export default config;
+```
+
+Create a `.env` file in the root of your Hardhat project:
+
+```text title=".env"
+PRIVATE_KEY=INSERT_PRIVATE_KEY_HERE
+```
+
+Replace `INSERT_PRIVATE_KEY_HERE` with your actual private key. You can get this by exporting the private key from your wallet (e.g., MetaMask).
+
+!!! warning
+ Never commit your private key to version control. Use environment variables or a `.env` file (and add it to `.gitignore`) to manage sensitive information. Keep your private key safe, and never share it with anyone. If it is compromised, your funds can be stolen.
+
+
+### Compile the Contract
+
+Compile your Storage contract:
+
+```bash
+npx hardhat compile
+```
+
+You should see output indicating successful compilation.
+
+### Deploy the Contract
+
+Create a deployment script in the `ignition/modules` directory called `Storage.ts`:
+
+```typescript title="Storage.ts"
+import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
+
+export default buildModule("StorageModule", (m) => {
+ const storage = m.contract("Storage");
+
+ return { storage };
+});
+```
+
+Deploy the contract to Polkadot Hub TestNet:
+
+```bash
+npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotHub
+```
+
+You should see output similar to:
+
+
+ npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotTestNet
+ WARNING: You are using Node.js 23.11.0 which is not supported by Hardhat.
+ Please upgrade to 22.10.0 or a later LTS version (even major version number)
+ ✔ Confirm deploy to network polkadotTestNet (420420420)? … yes
+ Hardhat Ignition 🚀
+ Deploying [ StorageModule ]
+ Batch #1
+ Executed StorageModule#Storage
+ [ StorageModule ] successfully deployed 🚀
+ Deployed Addresses
+ StorageModule#Storage - 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3
+
+
+!!! note
+ Save the deployed contract address - you'll need it when building your dApp. In the following sections, we'll reference a pre-deployed contract at `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`, but you can use your own deployed contract address instead.
+
+### Export the Contract ABI
+
+After deployment, you'll need the contract's Application Binary Interface (ABI) for your dApp. You can find it in the `artifacts/contracts/Storage.sol/Storage.json` file generated by Hardhat. You'll use this in the next section when setting up your dApp.
+
+Now that you have your contract deployed, you're ready to build the dApp that will interact with it!
+
+## Set Up the dApp Project
+
+Navigate to the root of the project, and create a new Next.js project called `dapp`:
+
+```bash
+npx create-next-app dapp --ts --eslint --tailwind --app --yes
+cd dapp
+```
+
+## Install Dependencies
+
+Install viem and related packages:
+
+```bash
+npm install viem@2.38.5
+npm install --save-dev typescript@5.9.3 @types/node@22.19.24
+```
+
+## Connect to Polkadot Hub
+
+To interact with Polkadot Hub, you need to set up a [Public Client](https://viem.sh/docs/clients/public#public-client){target=\_blank} that connects to the blockchain. In this example, you will interact with the Polkadot Hub TestNet, to experiment safely. Start by creating a new file called `utils/viem.ts` and add the following code:
+
+```typescript title="viem.ts"
+import { createPublicClient, http, createWalletClient, custom } from 'viem'
+import 'viem/window';
+
+const transport = http('http://127.0.0.1:8545') // TODO: change to the paseo asset hub RPC URL when it's available
+
+// Configure the Polkadot Testnet Hub chain
+export const polkadotTestnet = {
+ id: 420420420,
+ name: 'Polkadot Testnet',
+ network: 'polkadot-testnet',
+ nativeCurrency: {
+ decimals: 18,
+ name: 'PAS',
+ symbol: 'PAS',
+ },
+ rpcUrls: {
+ default: {
+ http: ['http://127.0.0.1:8545'], // TODO: change to the paseo asset hub RPC URL
+ },
+ },
+} as const
+
+// Create a public client for reading data
+export const publicClient = createPublicClient({
+ chain: polkadotTestnet,
+ transport
+})
+
+// Create a wallet client for signing transactions
+export const getWalletClient = async () => {
+ if (typeof window !== 'undefined' && window.ethereum) {
+ const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
+ return createWalletClient({
+ chain: polkadotTestnet,
+ transport: custom(window.ethereum),
+ account,
+ });
+ }
+ throw new Error('No Ethereum browser provider detected');
+};
+```
+
+This file initializes a viem client, providing helper functions for obtaining a Public Client and a [Wallet Client](https://viem.sh/docs/clients/wallet#wallet-client){target=\_blank}. The Public Client enables reading blockchain data, while the Wallet Client allows users to sign and send transactions. Also, note that by importing `viem/window` the global `window.ethereum` will be typed as an `EIP1193Provider`, check the [`window` Polyfill](https://viem.sh/docs/typescript#window-polyfill){target=\_blank} reference for more information.
+
+## Set Up the Smart Contract Interface
+
+For this dApp, you'll use a simple [Storage contract](/tutorials/smart-contracts/launch-your-first-project/create-contracts){target=\_blank} that's already deployed in the Polkadot Hub TestNet: `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`. To interact with it, you need to define the contract interface.
+
+Create a folder called `abis` at the root of your project, then create a file named `Storage.json` and paste the corresponding ABI of the Storage contract. You can copy and paste the following:
+
+```bash
+cp ./storage-contract/artifacts/contracts/Storage.sol/Storage.json ./dapp/abis/Storage.json
+```
+
+Next, create a file called `utils/contract.ts`:
+
+```typescript title="contract.ts"
+import { getContract } from 'viem';
+import { publicClient, getWalletClient } from './viem';
+import StorageABI from '../abis/Storage.json';
+
+export const CONTRACT_ADDRESS = '0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3'; // TODO: change when the paseo asset hub RPC URL is available, and the contract is redeployed
+export const CONTRACT_ABI = StorageABI.abi;
+
+// Create a function to get a contract instance for reading
+export const getContractInstance = () => {
+ return getContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ client: publicClient,
+ });
+};
+
+// Create a function to get a contract instance with a signer for writing
+export const getSignedContract = async () => {
+ const walletClient = await getWalletClient();
+ return getContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ client: walletClient,
+ });
+};
+```
+
+This file defines the contract address, ABI, and functions to create a viem [contract instance](https://viem.sh/docs/contract/getContract#contract-instances){target=\_blank} for reading and writing operations. viem's contract utilities enable more efficient, type-safe interaction with smart contracts.
+
+## Create the Wallet Connection Component
+
+Now, let's create a component to handle wallet connections. Create a new file called `components/WalletConnect.tsx`:
+
+```typescript title="WalletConnect.tsx"
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { polkadotTestnet } from "../utils/viem";
+
+interface WalletConnectProps {
+ onConnect: (account: string) => void;
+}
+
+const WalletConnect: React.FC = ({ onConnect }) => {
+ const [account, setAccount] = useState(null);
+ const [chainId, setChainId] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ // Check if user already has an authorized wallet connection
+ const checkConnection = async () => {
+ if (typeof window !== 'undefined' && window.ethereum) {
+ try {
+ // eth_accounts doesn't trigger the wallet popup
+ const accounts = await window.ethereum.request({
+ method: 'eth_accounts',
+ }) as string[];
+
+ if (accounts.length > 0) {
+ setAccount(accounts[0]);
+ const chainIdHex = await window.ethereum.request({
+ method: 'eth_chainId',
+ }) as string;
+ setChainId(parseInt(chainIdHex, 16));
+ onConnect(accounts[0]);
+ }
+ } catch (err) {
+ console.error('Error checking connection:', err);
+ setError('Failed to check wallet connection');
+ }
+ }
+ };
+
+ checkConnection();
+
+ if (typeof window !== 'undefined' && window.ethereum) {
+ // Setup wallet event listeners
+ window.ethereum.on('accountsChanged', (accounts: string[]) => {
+ setAccount(accounts[0] || null);
+ if (accounts[0]) onConnect(accounts[0]);
+ });
+
+ window.ethereum.on('chainChanged', (chainIdHex: string) => {
+ setChainId(parseInt(chainIdHex, 16));
+ });
+ }
+
+ return () => {
+ // Cleanup event listeners
+ if (typeof window !== 'undefined' && window.ethereum) {
+ window.ethereum.removeListener('accountsChanged', () => {});
+ window.ethereum.removeListener('chainChanged', () => {});
+ }
+ };
+ }, [onConnect]);
+
+ const connectWallet = async () => {
+ if (typeof window === 'undefined' || !window.ethereum) {
+ setError(
+ 'MetaMask not detected! Please install MetaMask to use this dApp.'
+ );
+ return;
+ }
+
+ try {
+ // eth_requestAccounts triggers the wallet popup
+ const accounts = await window.ethereum.request({
+ method: 'eth_requestAccounts',
+ }) as string[];
+
+ setAccount(accounts[0]);
+
+ const chainIdHex = await window.ethereum.request({
+ method: 'eth_chainId',
+ }) as string;
+
+ const currentChainId = parseInt(chainIdHex, 16);
+ setChainId(currentChainId);
+
+ // Prompt user to switch networks if needed
+ if (currentChainId !== polkadotTestnet.id) {
+ await switchNetwork();
+ }
+
+ onConnect(accounts[0]);
+ } catch (err) {
+ console.error('Error connecting to wallet:', err);
+ setError('Failed to connect wallet');
+ }
+ };
+
+ const switchNetwork = async () => {
+ console.log('Switch network')
+ try {
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: `0x${polkadotTestnet.id.toString(16)}` }],
+ });
+ } catch (switchError: any) {
+ // Error 4902 means the chain hasn't been added to MetaMask
+ if (switchError.code === 4902) {
+ try {
+ await window.ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [
+ {
+ chainId: `0x${polkadotTestnet.id.toString(16)}`,
+ chainName: polkadotTestnet.name,
+ rpcUrls: [polkadotTestnet.rpcUrls.default.http[0]],
+ nativeCurrency: {
+ name: polkadotTestnet.nativeCurrency.name,
+ symbol: polkadotTestnet.nativeCurrency.symbol,
+ decimals: polkadotTestnet.nativeCurrency.decimals,
+ },
+ },
+ ],
+ });
+ } catch (addError) {
+ setError('Failed to add network to wallet');
+ }
+ } else {
+ setError('Failed to switch network');
+ }
+ }
+ };
+
+ // UI-only disconnection - MetaMask doesn't support programmatic disconnection
+ const disconnectWallet = () => {
+ setAccount(null);
+ };
+
+ return (
+
+ );
+};
+
+export default WalletConnect;
+```
+
+This component handles connecting to the wallet, switching networks if necessary, and keeping track of the connected account. It provides a button for users to connect their wallet and displays the connected account address once connected.
+
+## Create the Read Contract Component
+
+Now, let's create a component to read data from the contract. Create a file called `components/ReadContract.tsx`:
+
+```typescript title="ReadContract.tsx"
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import { publicClient } from '../utils/viem';
+import { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';
+
+const ReadContract: React.FC = () => {
+ const [storedNumber, setStoredNumber] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ // Function to read data from the blockchain
+ const fetchData = async () => {
+ try {
+ setLoading(true);
+ // Call the smart contract's storedNumber function
+ const number = await publicClient.readContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ functionName: 'storedNumber',
+ args: [],
+ }) as bigint;
+
+ setStoredNumber(number.toString());
+ setError(null);
+ } catch (err) {
+ console.error('Error fetching stored number:', err);
+ setError('Failed to fetch data from the contract');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+
+ // Poll for updates every 10 seconds to keep UI in sync with blockchain
+ const interval = setInterval(fetchData, 10000);
+
+ // Clean up interval on component unmount
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+
Contract Data
+ {loading ? (
+
+
+
+ ) : error ? (
+
{error}
+ ) : (
+
+
+ Stored Number: {storedNumber}
+
+
+ )}
+
+ );
+};
+
+export default ReadContract;
+```
+
+This component reads the `storedNumber` value from the contract and displays it to the user. It also sets up a polling interval to refresh the data periodically, ensuring that the UI stays in sync with the blockchain state.
+
+## Create the Write Contract Component
+
+Finally, let's create a component that allows users to update the stored number. Create a file called `components/WriteContract.tsx`:
+
+```typescript title="WriteContract.tsx"
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { publicClient, getWalletClient } from '../utils/viem';
+import { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';
+
+interface WriteContractProps {
+ account: string | null;
+}
+
+const WriteContract: React.FC = ({ account }) => {
+ const [newNumber, setNewNumber] = useState("");
+ const [status, setStatus] = useState<{
+ type: string | null;
+ message: string;
+ }>({
+ type: null,
+ message: "",
+ });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [isCorrectNetwork, setIsCorrectNetwork] = useState(true);
+
+ // Check if the account is on the correct network
+ useEffect(() => {
+ const checkNetwork = async () => {
+ if (!account) return;
+
+ try {
+ // Get the chainId from the public client
+ const chainId = await publicClient.getChainId();
+
+ // Get the user's current chainId from their wallet
+ const walletClient = await getWalletClient();
+ if (!walletClient) return;
+
+ const walletChainId = await walletClient.getChainId();
+
+ // Check if they match
+ setIsCorrectNetwork(chainId === walletChainId);
+ } catch (err) {
+ console.error("Error checking network:", err);
+ setIsCorrectNetwork(false);
+ }
+ };
+
+ checkNetwork();
+ }, [account]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ // Validation checks
+ if (!account) {
+ setStatus({ type: "error", message: "Please connect your wallet first" });
+ return;
+ }
+
+ if (!isCorrectNetwork) {
+ setStatus({
+ type: "error",
+ message: "Please switch to the correct network in your wallet",
+ });
+ return;
+ }
+
+ if (!newNumber || isNaN(Number(newNumber))) {
+ setStatus({ type: "error", message: "Please enter a valid number" });
+ return;
+ }
+
+ try {
+ setIsSubmitting(true);
+ setStatus({ type: "info", message: "Initiating transaction..." });
+
+ // Get wallet client for transaction signing
+ const walletClient = await getWalletClient();
+
+ if (!walletClient) {
+ setStatus({ type: "error", message: "Wallet client not available" });
+ return;
+ }
+
+ // Check if account matches
+ if (
+ walletClient.account?.address.toLowerCase() !== account.toLowerCase()
+ ) {
+ setStatus({
+ type: "error",
+ message:
+ "Connected wallet account doesn't match the selected account",
+ });
+ return;
+ }
+
+ // Prepare transaction and wait for user confirmation in wallet
+ setStatus({
+ type: "info",
+ message: "Please confirm the transaction in your wallet...",
+ });
+
+ // Simulate the contract call first
+ console.log('newNumber', newNumber);
+ const { request } = await publicClient.simulateContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ functionName: "setNumber",
+ args: [BigInt(newNumber)],
+ account: walletClient.account,
+ });
+
+ // Send the transaction with wallet client
+ const hash = await walletClient.writeContract(request);
+
+ // Wait for transaction to be mined
+ setStatus({
+ type: "info",
+ message: "Transaction submitted. Waiting for confirmation...",
+ });
+
+ const receipt = await publicClient.waitForTransactionReceipt({
+ hash,
+ });
+
+ setStatus({
+ type: "success",
+ message: `Transaction confirmed! Transaction hash: ${receipt.transactionHash}`,
+ });
+
+ setNewNumber("");
+ } catch (err: any) {
+ console.error("Error updating number:", err);
+
+ // Handle specific errors
+ if (err.code === 4001) {
+ // User rejected transaction
+ setStatus({ type: "error", message: "Transaction rejected by user." });
+ } else if (err.message?.includes("Account not found")) {
+ // Account not found on the network
+ setStatus({
+ type: "error",
+ message:
+ "Account not found on current network. Please check your wallet is connected to the correct network.",
+ });
+ } else if (err.message?.includes("JSON is not a valid request object")) {
+ // JSON error - specific to your current issue
+ setStatus({
+ type: "error",
+ message:
+ "Invalid request format. Please try again or contact support.",
+ });
+ } else {
+ // Other errors
+ setStatus({
+ type: "error",
+ message: `Error: ${err.message || "Failed to send transaction"}`,
+ });
+ }
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
Update Stored Number
+
+ {!isCorrectNetwork && account && (
+
+ ⚠️ You are not connected to the correct network. Please switch
+ networks in your wallet.
+
+ )}
+
+ {status.message && (
+
+ {status.message}
+
+ )}
+
+
+
+ {!account && (
+
+ Connect your wallet to update the stored number.
+
+ )}
+
+ );
+};
+
+export default WriteContract;
+```
+
+This component allows users to input a new number and send a transaction to update the value stored in the contract. It provides appropriate feedback during each step of the transaction process and handles error scenarios.
+
+Update the `app/page.tsx` file to integrate all components:
+
+```typescript title="page.tsx"
+"use client";
+
+import { useState } from "react";
+import WalletConnect from "./components/WalletConnect";
+import ReadContract from "./components/ReadContract";
+import WriteContract from "./components/WriteContract";
+
+export default function Home() {
+ const [account, setAccount] = useState(null);
+
+ const handleConnect = (connectedAccount: string) => {
+ setAccount(connectedAccount);
+ };
+
+ return (
+
+
+ Polkadot Hub - Zero To Hero DApp
+
+
+
+
+
+ );
+}
+```
+
+Run the dApp:
+
+```bash
+npm run dev
+```
+
+Navigate to `http://localhost:3000` in your browser, and you should see your dApp with the wallet connection button, the stored number displayed, and the form to update the number. You should see something like this:
+
+
+
+## How It Works
+
+This dApp uses components to interact with the blockchain in several ways.
+
+### Wallet Connection
+
+The `WalletConnect` component uses the browser's Ethereum provider (MetaMask) to connect to the user's wallet and handles network switching to ensure the user is connected to the Polkadot Hub TestNet. Once connected, it provides the user's account address to the parent component.
+
+### Data Reads
+
+The `ReadContract` component uses viem's `readContract` function to call the `storedNumber` view function and periodically poll for updates to keep the UI in sync with the blockchain state. The component also displays a loading indicator while fetching data and handles error states.
+
+### Data Writes
+
+The `WriteContract` component uses viem's `writeContract` function to send a transaction to the `setNumber` function and ensures the wallet is connected before allowing a transaction. The component shows detailed feedback during transaction submission and confirmation. After a successful transaction, the value displayed in the `ReadContract` component will update on the next poll.
+
+## Conclusion
+
+Congratulations! You've successfully built a fully functional dApp that interacts with a smart contract on Polkadot Hub using viem and Next.js. Your application can now:
+
+- Create a smart contract with Hardhat and deploy it to Polkadot Hub TestNet.
+- Connect to a user's wallet and handle network switching.
+- Read data from a smart contract and keep it updated.
+- Write data to the blockchain through transactions.
+
+These fundamental skills provide the foundation for building more complex dApps on Polkadot Hub. With this knowledge, you can extend your application to interact with more sophisticated smart contracts and create advanced user interfaces.
+
+To get started right away with a working example, you can clone the repository and navigate to the implementation:
+
+```bash
+git clone https://github.com/polkadot-developers/revm-hardhat-examples.git
+cd zero-to-hero-dapp
+```
+
+## Where to Go Next
+
+
+
+- Guide __Port Ethereum Projects to Polkadot Hub__
+
+ ---
+
+ Learn how to port an Ethereum project to Polkadot Hub using Hardhat and Viem.
+
+ [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/eth-dapps/)
+
+- Guide __Dive Deeper into Polkadot Precompiles__
+
+ ---
+
+ Learn how to use the Polkadot precompiles to interact with the blockchain.
+
+ [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/polkadot-precompiles/)
+
diff --git a/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md b/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md
index cfeb31dfe..d2c8e9ade 100644
--- a/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md
+++ b/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md
@@ -1,5 +1,928 @@
---
+title: Zero to Hero Smart Contract DApp
+description: Learn how to build a decentralized application on Polkadot Hub using Viem and Next.js by creating a simple dApp that interacts with a smart contract.
+categories: dApp, Tooling
url: https://docs.polkadot.com/smart-contracts/cookbook/dapps/zero-to-hero/
---
-TODO
+# Zero to Hero Smart Contract DApp
+
+Decentralized applications (dApps) are a key component of the Web3 ecosystem, enabling developers to build applications that communicate directly with blockchain networks. Polkadot Hub, a blockchain with smart contract support, serves as a robust platform for deploying and interacting with dApps.
+
+This tutorial will guide you through building a fully functional dApp that interacts with a smart contract on Polkadot Hub. You'll create and deploy a smart contract with Hardhat, and then use [Viem](https://viem.sh/){target=\_blank} for blockchain interactions and [Next.js](https://nextjs.org/){target=\_blank} for the frontend. By the end, you'll have a dApp that lets users connect their wallets, retrieve on-chain data, and execute transactions.
+
+## Prerequisites
+
+Before getting started, ensure you have the following:
+
+- [Node.js](https://nodejs.org/en){target=\_blank} v22.10.0 or later installed on your system.
+- A crypto wallet (such as MetaMask) funded with test tokens. Refer to the [Connect to Polkadot](/smart-contracts/connect){target=\_blank} guide for more details.
+- A basic understanding of React and JavaScript.
+- Some familiarity with blockchain fundamentals and Solidity (helpful but not required).
+
+## Project Overview
+
+This dApp will interact with a basic Storage contract that you will create and deploy with Hardhat. The contract will allow you to:
+
+- Store a number on the blockchain.
+- Retrieve the stored number from the blockchain.
+- Update the stored number with a new value.
+
+Your project directory will be organized as follows:
+
+```bash
+polkadot-hub-tutorial/
+├── storage-contract/ # Hardhat project for smart contract
+│ ├── contracts/
+│ │ └── Storage.sol
+│ ├── scripts/
+│ │ └── deploy.ts
+│ ├── artifacts/
+│ │ └── contracts/
+│ │ └── Storage.sol/
+│ │ └── Storage.json
+│ ├── hardhat.config.ts
+│ ├── .env
+│ └── package.json
+│
+└── dapp/ # Next.js dApp project
+ ├── abis/
+ │ └── Storage.json
+ └── app/
+ ├── components/
+ │ ├── ReadContract.tsx
+ │ ├── WalletConnect.tsx
+ │ └── WriteContract.tsx
+ ├── utils/
+ │ ├── contract.ts
+ │ └── viem.ts
+ ├── favicon.ico
+ ├── globals.css
+ ├── layout.tsx
+ └── page.tsx
+```
+
+Create the main folder for the project:
+
+```bash
+mkdir polkadot-hub-tutorial
+cd polkadot-hub-tutorial
+```
+
+## Create and Deploy the Storage Contract
+
+Before building the dApp, you'll need to create and deploy the Storage smart contract. This section will guide you through using Hardhat to write, compile, and deploy the contract to Polkadot Hub TestNet.
+
+### Set Up Hardhat Project
+
+First, create a new directory for your Hardhat project and initialize it:
+
+```bash
+mkdir storage-contract
+cd storage-contract
+npm init -y
+```
+
+Install Hardhat and its dependencies:
+
+```bash
+npm install --save-dev hardhat@3.0.9
+```
+
+Initialize a new Hardhat project:
+
+```bash
+npx hardhat --init
+```
+
+Select **Create a TypeScript project** and accept the default options.
+
+### Create the Storage Contract
+
+In the `contracts` directory, create a new file called `Storage.sol` and add the following code:
+
+```solidity title="Storage.sol"
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+contract Storage {
+ uint256 private storedNumber;
+
+ event NumberStored(uint256 newNumber);
+
+ function setNumber(uint256 _number) public {
+ storedNumber = _number;
+ emit NumberStored(_number);
+ }
+}
+```
+
+This simple contract stores a single number and provides functions to read and update it.
+
+### Configure Hardhat for Polkadot Hub
+
+Update your `hardhat.config.ts` file to include the Polkadot Hub TestNet configuration:
+
+```typescript title="hardhat.config.ts" hl_lines="39-44"
+import type { HardhatUserConfig } from "hardhat/config";
+
+import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
+import { configVariable } from "hardhat/config";
+
+const config: HardhatUserConfig = {
+ plugins: [hardhatToolboxViemPlugin],
+ solidity: {
+ profiles: {
+ default: {
+ version: "0.8.28",
+ },
+ production: {
+ version: "0.8.28",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ },
+ },
+ networks: {
+ hardhatMainnet: {
+ type: "edr-simulated",
+ chainType: "l1",
+ },
+ hardhatOp: {
+ type: "edr-simulated",
+ chainType: "op",
+ },
+ sepolia: {
+ type: "http",
+ chainType: "l1",
+ url: configVariable("SEPOLIA_RPC_URL"),
+ accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
+ },
+ polkadotTestNet: {
+ type: "http",
+ chainType: "l1",
+ url: 'http://127.0.0.1:8545',
+ accounts: [process.env.PRIVATE_KEY || ''],
+ },
+ },
+};
+
+export default config;
+```
+
+Create a `.env` file in the root of your Hardhat project:
+
+```text title=".env"
+PRIVATE_KEY=INSERT_PRIVATE_KEY_HERE
+```
+
+Replace `INSERT_PRIVATE_KEY_HERE` with your actual private key. You can get this by exporting the private key from your wallet (e.g., MetaMask).
+
+!!! warning
+ Never commit your private key to version control. Use environment variables or a `.env` file (and add it to `.gitignore`) to manage sensitive information. Keep your private key safe, and never share it with anyone. If it is compromised, your funds can be stolen.
+
+
+### Compile the Contract
+
+Compile your Storage contract:
+
+```bash
+npx hardhat compile
+```
+
+You should see output indicating successful compilation.
+
+### Deploy the Contract
+
+Create a deployment script in the `ignition/modules` directory called `Storage.ts`:
+
+```typescript title="Storage.ts"
+import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
+
+export default buildModule("StorageModule", (m) => {
+ const storage = m.contract("Storage");
+
+ return { storage };
+});
+```
+
+Deploy the contract to Polkadot Hub TestNet:
+
+```bash
+npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotHub
+```
+
+You should see output similar to:
+
+
+ npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotTestNet
+ WARNING: You are using Node.js 23.11.0 which is not supported by Hardhat.
+ Please upgrade to 22.10.0 or a later LTS version (even major version number)
+ ✔ Confirm deploy to network polkadotTestNet (420420420)? … yes
+ Hardhat Ignition 🚀
+ Deploying [ StorageModule ]
+ Batch #1
+ Executed StorageModule#Storage
+ [ StorageModule ] successfully deployed 🚀
+ Deployed Addresses
+ StorageModule#Storage - 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3
+
+
+!!! note
+ Save the deployed contract address - you'll need it when building your dApp. In the following sections, we'll reference a pre-deployed contract at `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`, but you can use your own deployed contract address instead.
+
+### Export the Contract ABI
+
+After deployment, you'll need the contract's Application Binary Interface (ABI) for your dApp. You can find it in the `artifacts/contracts/Storage.sol/Storage.json` file generated by Hardhat. You'll use this in the next section when setting up your dApp.
+
+Now that you have your contract deployed, you're ready to build the dApp that will interact with it!
+
+## Set Up the dApp Project
+
+Navigate to the root of the project, and create a new Next.js project called `dapp`:
+
+```bash
+npx create-next-app dapp --ts --eslint --tailwind --app --yes
+cd dapp
+```
+
+## Install Dependencies
+
+Install viem and related packages:
+
+```bash
+npm install viem@2.38.5
+npm install --save-dev typescript@5.9.3 @types/node@22.19.24
+```
+
+## Connect to Polkadot Hub
+
+To interact with Polkadot Hub, you need to set up a [Public Client](https://viem.sh/docs/clients/public#public-client){target=\_blank} that connects to the blockchain. In this example, you will interact with the Polkadot Hub TestNet, to experiment safely. Start by creating a new file called `utils/viem.ts` and add the following code:
+
+```typescript title="viem.ts"
+import { createPublicClient, http, createWalletClient, custom } from 'viem'
+import 'viem/window';
+
+const transport = http('http://127.0.0.1:8545') // TODO: change to the paseo asset hub RPC URL when it's available
+
+// Configure the Polkadot Testnet Hub chain
+export const polkadotTestnet = {
+ id: 420420420,
+ name: 'Polkadot Testnet',
+ network: 'polkadot-testnet',
+ nativeCurrency: {
+ decimals: 18,
+ name: 'PAS',
+ symbol: 'PAS',
+ },
+ rpcUrls: {
+ default: {
+ http: ['http://127.0.0.1:8545'], // TODO: change to the paseo asset hub RPC URL
+ },
+ },
+} as const
+
+// Create a public client for reading data
+export const publicClient = createPublicClient({
+ chain: polkadotTestnet,
+ transport
+})
+
+// Create a wallet client for signing transactions
+export const getWalletClient = async () => {
+ if (typeof window !== 'undefined' && window.ethereum) {
+ const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
+ return createWalletClient({
+ chain: polkadotTestnet,
+ transport: custom(window.ethereum),
+ account,
+ });
+ }
+ throw new Error('No Ethereum browser provider detected');
+};
+```
+
+This file initializes a viem client, providing helper functions for obtaining a Public Client and a [Wallet Client](https://viem.sh/docs/clients/wallet#wallet-client){target=\_blank}. The Public Client enables reading blockchain data, while the Wallet Client allows users to sign and send transactions. Also, note that by importing `viem/window` the global `window.ethereum` will be typed as an `EIP1193Provider`, check the [`window` Polyfill](https://viem.sh/docs/typescript#window-polyfill){target=\_blank} reference for more information.
+
+## Set Up the Smart Contract Interface
+
+For this dApp, you'll use a simple [Storage contract](/tutorials/smart-contracts/launch-your-first-project/create-contracts){target=\_blank} that's already deployed in the Polkadot Hub TestNet: `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`. To interact with it, you need to define the contract interface.
+
+Create a folder called `abis` at the root of your project, then create a file named `Storage.json` and paste the corresponding ABI of the Storage contract. You can copy and paste the following:
+
+```bash
+cp ./storage-contract/artifacts/contracts/Storage.sol/Storage.json ./dapp/abis/Storage.json
+```
+
+Next, create a file called `utils/contract.ts`:
+
+```typescript title="contract.ts"
+import { getContract } from 'viem';
+import { publicClient, getWalletClient } from './viem';
+import StorageABI from '../abis/Storage.json';
+
+export const CONTRACT_ADDRESS = '0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3'; // TODO: change when the paseo asset hub RPC URL is available, and the contract is redeployed
+export const CONTRACT_ABI = StorageABI.abi;
+
+// Create a function to get a contract instance for reading
+export const getContractInstance = () => {
+ return getContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ client: publicClient,
+ });
+};
+
+// Create a function to get a contract instance with a signer for writing
+export const getSignedContract = async () => {
+ const walletClient = await getWalletClient();
+ return getContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ client: walletClient,
+ });
+};
+```
+
+This file defines the contract address, ABI, and functions to create a viem [contract instance](https://viem.sh/docs/contract/getContract#contract-instances){target=\_blank} for reading and writing operations. viem's contract utilities enable more efficient, type-safe interaction with smart contracts.
+
+## Create the Wallet Connection Component
+
+Now, let's create a component to handle wallet connections. Create a new file called `components/WalletConnect.tsx`:
+
+```typescript title="WalletConnect.tsx"
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { polkadotTestnet } from "../utils/viem";
+
+interface WalletConnectProps {
+ onConnect: (account: string) => void;
+}
+
+const WalletConnect: React.FC = ({ onConnect }) => {
+ const [account, setAccount] = useState(null);
+ const [chainId, setChainId] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ // Check if user already has an authorized wallet connection
+ const checkConnection = async () => {
+ if (typeof window !== 'undefined' && window.ethereum) {
+ try {
+ // eth_accounts doesn't trigger the wallet popup
+ const accounts = await window.ethereum.request({
+ method: 'eth_accounts',
+ }) as string[];
+
+ if (accounts.length > 0) {
+ setAccount(accounts[0]);
+ const chainIdHex = await window.ethereum.request({
+ method: 'eth_chainId',
+ }) as string;
+ setChainId(parseInt(chainIdHex, 16));
+ onConnect(accounts[0]);
+ }
+ } catch (err) {
+ console.error('Error checking connection:', err);
+ setError('Failed to check wallet connection');
+ }
+ }
+ };
+
+ checkConnection();
+
+ if (typeof window !== 'undefined' && window.ethereum) {
+ // Setup wallet event listeners
+ window.ethereum.on('accountsChanged', (accounts: string[]) => {
+ setAccount(accounts[0] || null);
+ if (accounts[0]) onConnect(accounts[0]);
+ });
+
+ window.ethereum.on('chainChanged', (chainIdHex: string) => {
+ setChainId(parseInt(chainIdHex, 16));
+ });
+ }
+
+ return () => {
+ // Cleanup event listeners
+ if (typeof window !== 'undefined' && window.ethereum) {
+ window.ethereum.removeListener('accountsChanged', () => {});
+ window.ethereum.removeListener('chainChanged', () => {});
+ }
+ };
+ }, [onConnect]);
+
+ const connectWallet = async () => {
+ if (typeof window === 'undefined' || !window.ethereum) {
+ setError(
+ 'MetaMask not detected! Please install MetaMask to use this dApp.'
+ );
+ return;
+ }
+
+ try {
+ // eth_requestAccounts triggers the wallet popup
+ const accounts = await window.ethereum.request({
+ method: 'eth_requestAccounts',
+ }) as string[];
+
+ setAccount(accounts[0]);
+
+ const chainIdHex = await window.ethereum.request({
+ method: 'eth_chainId',
+ }) as string;
+
+ const currentChainId = parseInt(chainIdHex, 16);
+ setChainId(currentChainId);
+
+ // Prompt user to switch networks if needed
+ if (currentChainId !== polkadotTestnet.id) {
+ await switchNetwork();
+ }
+
+ onConnect(accounts[0]);
+ } catch (err) {
+ console.error('Error connecting to wallet:', err);
+ setError('Failed to connect wallet');
+ }
+ };
+
+ const switchNetwork = async () => {
+ console.log('Switch network')
+ try {
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: `0x${polkadotTestnet.id.toString(16)}` }],
+ });
+ } catch (switchError: any) {
+ // Error 4902 means the chain hasn't been added to MetaMask
+ if (switchError.code === 4902) {
+ try {
+ await window.ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [
+ {
+ chainId: `0x${polkadotTestnet.id.toString(16)}`,
+ chainName: polkadotTestnet.name,
+ rpcUrls: [polkadotTestnet.rpcUrls.default.http[0]],
+ nativeCurrency: {
+ name: polkadotTestnet.nativeCurrency.name,
+ symbol: polkadotTestnet.nativeCurrency.symbol,
+ decimals: polkadotTestnet.nativeCurrency.decimals,
+ },
+ },
+ ],
+ });
+ } catch (addError) {
+ setError('Failed to add network to wallet');
+ }
+ } else {
+ setError('Failed to switch network');
+ }
+ }
+ };
+
+ // UI-only disconnection - MetaMask doesn't support programmatic disconnection
+ const disconnectWallet = () => {
+ setAccount(null);
+ };
+
+ return (
+
+ );
+};
+
+export default WalletConnect;
+```
+
+This component handles connecting to the wallet, switching networks if necessary, and keeping track of the connected account. It provides a button for users to connect their wallet and displays the connected account address once connected.
+
+## Create the Read Contract Component
+
+Now, let's create a component to read data from the contract. Create a file called `components/ReadContract.tsx`:
+
+```typescript title="ReadContract.tsx"
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import { publicClient } from '../utils/viem';
+import { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';
+
+const ReadContract: React.FC = () => {
+ const [storedNumber, setStoredNumber] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ // Function to read data from the blockchain
+ const fetchData = async () => {
+ try {
+ setLoading(true);
+ // Call the smart contract's storedNumber function
+ const number = await publicClient.readContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ functionName: 'storedNumber',
+ args: [],
+ }) as bigint;
+
+ setStoredNumber(number.toString());
+ setError(null);
+ } catch (err) {
+ console.error('Error fetching stored number:', err);
+ setError('Failed to fetch data from the contract');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+
+ // Poll for updates every 10 seconds to keep UI in sync with blockchain
+ const interval = setInterval(fetchData, 10000);
+
+ // Clean up interval on component unmount
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+
Contract Data
+ {loading ? (
+
+
+
+ ) : error ? (
+
{error}
+ ) : (
+
+
+ Stored Number: {storedNumber}
+
+
+ )}
+
+ );
+};
+
+export default ReadContract;
+```
+
+This component reads the `storedNumber` value from the contract and displays it to the user. It also sets up a polling interval to refresh the data periodically, ensuring that the UI stays in sync with the blockchain state.
+
+## Create the Write Contract Component
+
+Finally, let's create a component that allows users to update the stored number. Create a file called `components/WriteContract.tsx`:
+
+```typescript title="WriteContract.tsx"
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { publicClient, getWalletClient } from '../utils/viem';
+import { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';
+
+interface WriteContractProps {
+ account: string | null;
+}
+
+const WriteContract: React.FC = ({ account }) => {
+ const [newNumber, setNewNumber] = useState("");
+ const [status, setStatus] = useState<{
+ type: string | null;
+ message: string;
+ }>({
+ type: null,
+ message: "",
+ });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [isCorrectNetwork, setIsCorrectNetwork] = useState(true);
+
+ // Check if the account is on the correct network
+ useEffect(() => {
+ const checkNetwork = async () => {
+ if (!account) return;
+
+ try {
+ // Get the chainId from the public client
+ const chainId = await publicClient.getChainId();
+
+ // Get the user's current chainId from their wallet
+ const walletClient = await getWalletClient();
+ if (!walletClient) return;
+
+ const walletChainId = await walletClient.getChainId();
+
+ // Check if they match
+ setIsCorrectNetwork(chainId === walletChainId);
+ } catch (err) {
+ console.error("Error checking network:", err);
+ setIsCorrectNetwork(false);
+ }
+ };
+
+ checkNetwork();
+ }, [account]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ // Validation checks
+ if (!account) {
+ setStatus({ type: "error", message: "Please connect your wallet first" });
+ return;
+ }
+
+ if (!isCorrectNetwork) {
+ setStatus({
+ type: "error",
+ message: "Please switch to the correct network in your wallet",
+ });
+ return;
+ }
+
+ if (!newNumber || isNaN(Number(newNumber))) {
+ setStatus({ type: "error", message: "Please enter a valid number" });
+ return;
+ }
+
+ try {
+ setIsSubmitting(true);
+ setStatus({ type: "info", message: "Initiating transaction..." });
+
+ // Get wallet client for transaction signing
+ const walletClient = await getWalletClient();
+
+ if (!walletClient) {
+ setStatus({ type: "error", message: "Wallet client not available" });
+ return;
+ }
+
+ // Check if account matches
+ if (
+ walletClient.account?.address.toLowerCase() !== account.toLowerCase()
+ ) {
+ setStatus({
+ type: "error",
+ message:
+ "Connected wallet account doesn't match the selected account",
+ });
+ return;
+ }
+
+ // Prepare transaction and wait for user confirmation in wallet
+ setStatus({
+ type: "info",
+ message: "Please confirm the transaction in your wallet...",
+ });
+
+ // Simulate the contract call first
+ console.log('newNumber', newNumber);
+ const { request } = await publicClient.simulateContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ functionName: "setNumber",
+ args: [BigInt(newNumber)],
+ account: walletClient.account,
+ });
+
+ // Send the transaction with wallet client
+ const hash = await walletClient.writeContract(request);
+
+ // Wait for transaction to be mined
+ setStatus({
+ type: "info",
+ message: "Transaction submitted. Waiting for confirmation...",
+ });
+
+ const receipt = await publicClient.waitForTransactionReceipt({
+ hash,
+ });
+
+ setStatus({
+ type: "success",
+ message: `Transaction confirmed! Transaction hash: ${receipt.transactionHash}`,
+ });
+
+ setNewNumber("");
+ } catch (err: any) {
+ console.error("Error updating number:", err);
+
+ // Handle specific errors
+ if (err.code === 4001) {
+ // User rejected transaction
+ setStatus({ type: "error", message: "Transaction rejected by user." });
+ } else if (err.message?.includes("Account not found")) {
+ // Account not found on the network
+ setStatus({
+ type: "error",
+ message:
+ "Account not found on current network. Please check your wallet is connected to the correct network.",
+ });
+ } else if (err.message?.includes("JSON is not a valid request object")) {
+ // JSON error - specific to your current issue
+ setStatus({
+ type: "error",
+ message:
+ "Invalid request format. Please try again or contact support.",
+ });
+ } else {
+ // Other errors
+ setStatus({
+ type: "error",
+ message: `Error: ${err.message || "Failed to send transaction"}`,
+ });
+ }
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
Update Stored Number
+
+ {!isCorrectNetwork && account && (
+
+ ⚠️ You are not connected to the correct network. Please switch
+ networks in your wallet.
+
+ )}
+
+ {status.message && (
+
+ {status.message}
+
+ )}
+
+
+
+ {!account && (
+
+ Connect your wallet to update the stored number.
+
+ )}
+
+ );
+};
+
+export default WriteContract;
+```
+
+This component allows users to input a new number and send a transaction to update the value stored in the contract. It provides appropriate feedback during each step of the transaction process and handles error scenarios.
+
+Update the `app/page.tsx` file to integrate all components:
+
+```typescript title="page.tsx"
+"use client";
+
+import { useState } from "react";
+import WalletConnect from "./components/WalletConnect";
+import ReadContract from "./components/ReadContract";
+import WriteContract from "./components/WriteContract";
+
+export default function Home() {
+ const [account, setAccount] = useState(null);
+
+ const handleConnect = (connectedAccount: string) => {
+ setAccount(connectedAccount);
+ };
+
+ return (
+
+
+ Polkadot Hub - Zero To Hero DApp
+
+
+
+
+
+ );
+}
+```
+
+Run the dApp:
+
+```bash
+npm run dev
+```
+
+Navigate to `http://localhost:3000` in your browser, and you should see your dApp with the wallet connection button, the stored number displayed, and the form to update the number. You should see something like this:
+
+
+
+## How It Works
+
+This dApp uses components to interact with the blockchain in several ways.
+
+### Wallet Connection
+
+The `WalletConnect` component uses the browser's Ethereum provider (MetaMask) to connect to the user's wallet and handles network switching to ensure the user is connected to the Polkadot Hub TestNet. Once connected, it provides the user's account address to the parent component.
+
+### Data Reads
+
+The `ReadContract` component uses viem's `readContract` function to call the `storedNumber` view function and periodically poll for updates to keep the UI in sync with the blockchain state. The component also displays a loading indicator while fetching data and handles error states.
+
+### Data Writes
+
+The `WriteContract` component uses viem's `writeContract` function to send a transaction to the `setNumber` function and ensures the wallet is connected before allowing a transaction. The component shows detailed feedback during transaction submission and confirmation. After a successful transaction, the value displayed in the `ReadContract` component will update on the next poll.
+
+## Conclusion
+
+Congratulations! You've successfully built a fully functional dApp that interacts with a smart contract on Polkadot Hub using viem and Next.js. Your application can now:
+
+- Create a smart contract with Hardhat and deploy it to Polkadot Hub TestNet.
+- Connect to a user's wallet and handle network switching.
+- Read data from a smart contract and keep it updated.
+- Write data to the blockchain through transactions.
+
+These fundamental skills provide the foundation for building more complex dApps on Polkadot Hub. With this knowledge, you can extend your application to interact with more sophisticated smart contracts and create advanced user interfaces.
+
+To get started right away with a working example, you can clone the repository and navigate to the implementation:
+
+```bash
+git clone https://github.com/polkadot-developers/revm-hardhat-examples.git
+cd zero-to-hero-dapp
+```
+
+## Where to Go Next
+
+
+
+- Guide __Port Ethereum Projects to Polkadot Hub__
+
+ ---
+
+ Learn how to port an Ethereum project to Polkadot Hub using Hardhat and Viem.
+
+ [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/eth-dapps/)
+
+- Guide __Dive Deeper into Polkadot Precompiles__
+
+ ---
+
+ Learn how to use the Polkadot precompiles to interact with the blockchain.
+
+ [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/polkadot-precompiles/)
+
diff --git a/.ai/site-index.json b/.ai/site-index.json
index 814768041..64e35be44 100644
--- a/.ai/site-index.json
+++ b/.ai/site-index.json
@@ -17,6 +17,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -37,6 +38,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -57,6 +59,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -77,6 +80,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -97,6 +101,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -117,6 +122,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -228,6 +234,7 @@
"estimated_token_count_total": 4830
},
"hash": "sha256:a6bf7623a535e7a9162c0913b07bd59d43c8535025ad8225fb3e5adc83084c7a",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -299,6 +306,7 @@
"estimated_token_count_total": 7755
},
"hash": "sha256:086a87823ab67ceac102358030e316583cd733c0ec326316e7f29061fe7f6934",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -319,6 +327,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -380,6 +389,7 @@
"estimated_token_count_total": 5207
},
"hash": "sha256:91f59a76dd33641ca2b5bf6d58230f65034fa3cc5f8313525fb57e854a878a56",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -471,6 +481,7 @@
"estimated_token_count_total": 2132
},
"hash": "sha256:1b9efd2fe00b251d3b4054c9cfcb55f9b5a1384238eeaca81a6f1542fc36d75c",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -491,6 +502,7 @@
"estimated_token_count_total": 0
},
"hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -557,6 +569,7 @@
"estimated_token_count_total": 4063
},
"hash": "sha256:bd07cdae71bf63786994865d2f33fba5f7bf8855dce6399414ad44ab0ec6635c",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -613,6 +626,7 @@
"estimated_token_count_total": 2225
},
"hash": "sha256:e916033f54c2874eb5ce9a43d58af058eb935429f73b7b1acc7da1592218e0b8",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -660,6 +674,7 @@
"estimated_token_count_total": 1523
},
"hash": "sha256:d9d85827d2c14bff8dd6b3301617345430cf63db603e37859720713004ecafae",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -679,7 +694,9 @@
"headings": 0,
"estimated_token_count_total": 0
},
- "hash": "sha256:56ba36249ea8216ad513b13df3de6c0e490ba214897674d30331f1c7e7edbef3",
+ "hash": "sha256:2b017d8a89f8734b9cbb501f03612a22657d2f8d4d85c51e490e4c8ca4bf771b",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -737,6 +754,7 @@
"estimated_token_count_total": 1635
},
"hash": "sha256:46252e238b0b51105148dc622da6d8809c55ec11da7ec7b2953c35ca52f5f585",
+ "last_modified": "2025-10-28T14:42:10+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -779,6 +797,7 @@
"estimated_token_count_total": 1491
},
"hash": "sha256:db37b2f5888f283b5eb5bd84a5f8c81fc66b2313e3f94f510a73dfeb310ae3f0",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -845,6 +864,7 @@
"estimated_token_count_total": 955
},
"hash": "sha256:72ee7394fd1308c111a8d548cb4dc63c6b9bc5b6e2bb556dd1baacbaedb92286",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -896,6 +916,7 @@
"estimated_token_count_total": 876
},
"hash": "sha256:d6cb22337280a19bdf24981dcba98f337d48ee4f79ce7ac040466ef1cb4b330b",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -977,6 +998,7 @@
"estimated_token_count_total": 2744
},
"hash": "sha256:1a2d34ccab19bd71263763bbc294977acf34f5800398f51398753594cfc7d7a6",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1048,6 +1070,7 @@
"estimated_token_count_total": 608
},
"hash": "sha256:7bba6105d99721373aa6f494627d20af97b1851c19703f26be26c32f0c83524b",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1114,6 +1137,7 @@
"estimated_token_count_total": 558
},
"hash": "sha256:b79fe56c9604712825bdf30d17667fd8f237fce9691be0d8d042d38691dbba7a",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1165,58 +1189,7 @@
"estimated_token_count_total": 348
},
"hash": "sha256:11cd8d428fa9c3e70490da5c63ce4597cd89ec46306d2bb49b016ced6aa68c3d",
- "token_estimator": "heuristic-v1"
- },
- {
- "id": "develop-interoperability-versions-v5",
- "title": "XCMv5",
- "slug": "develop-interoperability-versions-v5",
- "categories": [
- "Uncategorized"
- ],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-interoperability-versions-v5.md",
- "html_url": "https://docs.polkadot.com/develop/interoperability/versions/v5/",
- "preview": "The latest iteration of XCM is version 5. The main RFCs defining the changes in version 5 are the following:",
- "outline": [
- {
- "depth": 2,
- "title": "In This Section",
- "anchor": "in-this-section"
- }
- ],
- "stats": {
- "chars": 2970,
- "words": 438,
- "headings": 1,
- "estimated_token_count_total": 12
- },
- "hash": "sha256:3821c2ef97699091b76e1de58e6d95e866df69d39fca16f2a15c156b71da5b22",
- "token_estimator": "heuristic-v1"
- },
- {
- "id": "develop-interoperability-versions",
- "title": "XCM Versions",
- "slug": "develop-interoperability-versions",
- "categories": [
- "Uncategorized"
- ],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-interoperability-versions.md",
- "html_url": "https://docs.polkadot.com/develop/interoperability/versions/",
- "preview": "XCM is a versioned language that evolves to meet the growing needs of cross-chain communication in the Polkadot ecosystem. Understanding XCM versioning is essential for developers building interoperable applications to keep up with the latest improvements.",
- "outline": [
- {
- "depth": 2,
- "title": "In This Section",
- "anchor": "in-this-section"
- }
- ],
- "stats": {
- "chars": 835,
- "words": 114,
- "headings": 1,
- "estimated_token_count_total": 12
- },
- "hash": "sha256:634e299f347beb8ad690697943bb7f99915d62d40cda4227179619ed18abe2ff",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1264,6 +1237,7 @@
"estimated_token_count_total": 1365
},
"hash": "sha256:5f8fa89fc725c5c559975012fe2f9ae92c3b62f10024b5688dcd118331118f1a",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1316,6 +1290,7 @@
"estimated_token_count_total": 4979
},
"hash": "sha256:ed3b7c8101b69f9c907cca7c5edfef67fdb5e7bc3c8df8d9fbad297f9dd3c80a",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1372,6 +1347,7 @@
"estimated_token_count_total": 1781
},
"hash": "sha256:35c71a215558cd0642d363e4515ad240093995d42720e6495cd2994c859243e4",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1418,6 +1394,7 @@
"estimated_token_count_total": 1447
},
"hash": "sha256:0e39aee80fbcf3dfaa19133f31d664914ed45b42a1a929270f05d8ae876b89e2",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1464,6 +1441,7 @@
"estimated_token_count_total": 1082
},
"hash": "sha256:ec82957c768c2c07a272e7a28659c812b223df836e21372b1642f0bb249d7b39",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1505,63 +1483,7 @@
"estimated_token_count_total": 4178
},
"hash": "sha256:d480791a76082937b47c77f7cf3794e701f193452ed347fcb1c04c3c67577bf5",
- "token_estimator": "heuristic-v1"
- },
- {
- "id": "develop-interoperability-xcm-guides-from-apps",
- "title": "From Apps",
- "slug": "develop-interoperability-xcm-guides-from-apps",
- "categories": [
- "Uncategorized"
- ],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-interoperability-xcm-guides-from-apps.md",
- "html_url": "https://docs.polkadot.com/develop/interoperability/xcm-guides/from-apps/",
- "preview": "This section shows how to interact with XCM from applications, providing practical guidance for implementing cross-chain functionality in your dApps and services.",
- "outline": [
- {
- "depth": 2,
- "title": "In This Section",
- "anchor": "in-this-section"
- }
- ],
- "stats": {
- "chars": 511,
- "words": 70,
- "headings": 1,
- "estimated_token_count_total": 12
- },
- "hash": "sha256:63584f5b1dab7b67b18b35b47dfc19d00ad5c013804772f0d653a11ac3fca38d",
- "token_estimator": "heuristic-v1"
- },
- {
- "id": "develop-interoperability-xcm-guides",
- "title": "XCM Guides",
- "slug": "develop-interoperability-xcm-guides",
- "categories": [
- "Uncategorized"
- ],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-interoperability-xcm-guides.md",
- "html_url": "https://docs.polkadot.com/develop/interoperability/xcm-guides/",
- "preview": "This section provides comprehensive guides for implementing XCM functionality, including application development patterns, fee management, asset transfers, and cross-chain transaction handling.",
- "outline": [
- {
- "depth": 2,
- "title": "In This Section",
- "anchor": "in-this-section"
- },
- {
- "depth": 2,
- "title": "Additional Resources",
- "anchor": "additional-resources"
- }
- ],
- "stats": {
- "chars": 1663,
- "words": 215,
- "headings": 2,
- "estimated_token_count_total": 340
- },
- "hash": "sha256:d4c2d7fd46ddf60f638f948c88ba3940de6d69f140923ba8df52ed787b0afede",
+ "last_modified": "2025-10-28T14:42:11+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1629,37 +1551,7 @@
"estimated_token_count_total": 6510
},
"hash": "sha256:353ad782303ef79bce1262bfa945e6f11b3c3c9ca1edf5705b778c46bada6200",
- "token_estimator": "heuristic-v1"
- },
- {
- "id": "develop-interoperability",
- "title": "Interoperability",
- "slug": "develop-interoperability",
- "categories": [
- "Uncategorized"
- ],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-interoperability.md",
- "html_url": "https://docs.polkadot.com/develop/interoperability/",
- "preview": "This section covers everything you need to know about building and implementing [Cross-Consensus Messaging (XCM)](/parachains/interoperability/get-started/){target=\\_blank} solutions in the Polkadot ecosystem. Whether you're working on establishing cross-chain channels, sending and receiving XCM messages, or testing and debugging your cross-chain configurations, you'll find the essential resources and tools here to support your interoperability needs, regardless of your development focus.",
- "outline": [
- {
- "depth": 2,
- "title": "In This Section",
- "anchor": "in-this-section"
- },
- {
- "depth": 2,
- "title": "Additional Resources",
- "anchor": "additional-resources"
- }
- ],
- "stats": {
- "chars": 2363,
- "words": 323,
- "headings": 2,
- "estimated_token_count_total": 402
- },
- "hash": "sha256:5da6bdeec1deee5ef3d7ab1a43f546067bcef91acdc67df4ce114ee8f8669e82",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -1722,19 +1614,19 @@
"estimated_token_count_total": 1520
},
"hash": "sha256:ed09ef7a6abe21204006186fd5791ada7597688fad67e30244dc449c51330309",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
- "id": "develop-parachains-customize-parachain-overview",
- "title": "Overview of FRAME",
- "slug": "develop-parachains-customize-parachain-overview",
+ "id": "develop-parachains-customize-parachain-add-existing-pallets",
+ "title": "Add a Pallet to the Runtime",
+ "slug": "develop-parachains-customize-parachain-add-existing-pallets",
"categories": [
- "Basics",
"Parachains"
],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain-overview.md",
- "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/overview/",
- "preview": "The runtime is the heart of any Polkadot SDK-based blockchain, handling the essential logic that governs state changes and transaction processing. With Polkadot SDK’s [FRAME (Framework for Runtime Aggregation of Modularized Entities)](/reference/glossary/#frame-framework-for-runtime-aggregation-of-modularized-entities){target=\\_bank}, developers gain access to a powerful suite of tools for building custom blockchain runtimes. FRAME offers a modular architecture, featuring reusable pallets and su",
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain-add-existing-pallets.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/add-existing-pallets/",
+ "preview": "The [Polkadot SDK Solochain Template](https://github.com/paritytech/polkadot-sdk-solochain-template){target=\\_blank} provides a functional runtime that includes default FRAME development modules (pallets) to help you get started with building a custom blockchain.",
"outline": [
{
"depth": 2,
@@ -1743,38 +1635,91 @@
},
{
"depth": 2,
- "title": "FRAME Runtime Architecture",
- "anchor": "frame-runtime-architecture"
+ "title": "Configuring Runtime Dependencies",
+ "anchor": "configuring-runtime-dependencies"
},
{
- "depth": 3,
- "title": "Pallets",
- "anchor": "pallets"
+ "depth": 2,
+ "title": "Dependencies for a New Pallet",
+ "anchor": "dependencies-for-a-new-pallet"
+ },
+ {
+ "depth": 2,
+ "title": "Config Trait for Pallets",
+ "anchor": "config-trait-for-pallets"
},
{
"depth": 3,
- "title": "Support Libraries",
- "anchor": "support-libraries"
+ "title": "Utility Pallet Example",
+ "anchor": "utility-pallet-example"
},
{
"depth": 2,
- "title": "Compose a Runtime with Pallets",
- "anchor": "compose-a-runtime-with-pallets"
+ "title": "Parameter Configuration for Pallets",
+ "anchor": "parameter-configuration-for-pallets"
},
{
"depth": 2,
- "title": "Starting from Templates",
- "anchor": "starting-from-templates"
+ "title": "Pallet Config in the Runtime",
+ "anchor": "pallet-config-in-the-runtime"
+ },
+ {
+ "depth": 2,
+ "title": "Where to Go Next",
+ "anchor": "where-to-go-next"
+ }
+ ],
+ "stats": {
+ "chars": 11939,
+ "words": 1615,
+ "headings": 8,
+ "estimated_token_count_total": 2598
+ },
+ "hash": "sha256:b2b3d8c048863e7760f633b12ab2a0202c741be3050ea4beafb9a7265cfe96b5",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "token_estimator": "heuristic-v1"
+ },
+ {
+ "id": "develop-parachains-customize-parachain-add-pallet-instances",
+ "title": "Add Multiple Pallet Instances",
+ "slug": "develop-parachains-customize-parachain-add-pallet-instances",
+ "categories": [
+ "Parachains"
+ ],
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain-add-pallet-instances.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/add-pallet-instances/",
+ "preview": "Running multiple instances of the same pallet within a runtime is a powerful technique in Polkadot SDK development. This approach lets you reuse pallet functionality without reimplementing it, enabling diverse use cases with the same codebase. The Polkadot SDK provides developer-friendly traits for creating instantiable pallets and, in most cases, handles unique storage allocation for different instances automatically. This guide teaches you how to implement and configure multiple instances of a",
+ "outline": [
+ {
+ "depth": 2,
+ "title": "Introduction",
+ "anchor": "introduction"
+ },
+ {
+ "depth": 2,
+ "title": "Understanding Instantiable Pallets",
+ "anchor": "understanding-instantiable-pallets"
+ },
+ {
+ "depth": 2,
+ "title": "Adding Instantiable Pallets to Your Runtime",
+ "anchor": "adding-instantiable-pallets-to-your-runtime"
},
{
"depth": 3,
- "title": "Solochain Templates",
- "anchor": "solochain-templates"
+ "title": "Define Pallet Parameters",
+ "anchor": "define-pallet-parameters"
},
{
"depth": 3,
- "title": "Parachain Templates",
- "anchor": "parachain-templates"
+ "title": "Configure the Pallet Instances",
+ "anchor": "configure-the-pallet-instances"
+ },
+ {
+ "depth": 3,
+ "title": "Add Pallet Instances to the Runtime",
+ "anchor": "add-pallet-instances-to-the-runtime"
},
{
"depth": 2,
@@ -1783,55 +1728,69 @@
}
],
"stats": {
- "chars": 9427,
- "words": 1267,
- "headings": 9,
- "estimated_token_count_total": 2019
+ "chars": 6294,
+ "words": 729,
+ "headings": 7,
+ "estimated_token_count_total": 1219
},
- "hash": "sha256:d03ea172f2af9f4648e730d60033a80c2c1e64efa9241fed0c1ba40a5f846ae5",
+ "hash": "sha256:262e7a3ad3d0a0102897c52c7589e3f94c7827c441398b3b446b205f6c6753d3",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
- "id": "develop-parachains-customize-parachain",
- "title": "Customize Your Parachain",
- "slug": "develop-parachains-customize-parachain",
+ "id": "develop-parachains-customize-parachain-add-smart-contract-functionality",
+ "title": "Add Smart Contract Functionality",
+ "slug": "develop-parachains-customize-parachain-add-smart-contract-functionality",
"categories": [
- "Uncategorized"
+ "Parachains"
],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain.md",
- "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/",
- "preview": "Learn how to build a custom parachain with Polkadot SDK's FRAME framework, which includes pallet development, testing, smart contracts, and runtime customization. Pallets are modular components within the FRAME ecosystem that contain specific blockchain functionalities. This modularity grants developers increased flexibility and control around which behaviors to include in the core logic of their parachain.",
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain-add-smart-contract-functionality.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/add-smart-contract-functionality/",
+ "preview": "When building your custom blockchain with the Polkadot SDK, you have the flexibility to add smart contract capabilities through specialized pallets. These pallets allow blockchain users to deploy and execute smart contracts, enhancing your chain's functionality and programmability.",
"outline": [
{
"depth": 2,
- "title": "In This Section",
- "anchor": "in-this-section"
+ "title": "Introduction",
+ "anchor": "introduction"
},
{
"depth": 2,
- "title": "Additional Resources",
- "anchor": "additional-resources"
+ "title": "EVM Smart Contracts",
+ "anchor": "evm-smart-contracts"
+ },
+ {
+ "depth": 2,
+ "title": "Wasm Smart Contracts",
+ "anchor": "wasm-smart-contracts"
+ },
+ {
+ "depth": 2,
+ "title": "Where to Go Next",
+ "anchor": "where-to-go-next"
}
],
"stats": {
- "chars": 1899,
- "words": 259,
- "headings": 2,
- "estimated_token_count_total": 335
+ "chars": 3896,
+ "words": 523,
+ "headings": 4,
+ "estimated_token_count_total": 905
},
- "hash": "sha256:9a08b66442c564c7116c686d8914b74ad617326f450d0894b05e753462f69aac",
+ "hash": "sha256:ad8e6d9c77d5451c5f4d17f8e6311b21e6ad24eae8780fd4c3ae6013744822cf",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
- "id": "develop-parachains-deployment-build-deterministic-runtime",
- "title": "Build a deterministic runtime",
- "slug": "develop-parachains-deployment-build-deterministic-runtime",
+ "id": "develop-parachains-customize-parachain-make-custom-pallet",
+ "title": "Make a Custom Pallet",
+ "slug": "develop-parachains-customize-parachain-make-custom-pallet",
"categories": [
"Parachains"
],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-deployment-build-deterministic-runtime.md",
- "html_url": "https://docs.polkadot.com/develop/parachains/deployment/build-deterministic-runtime/",
- "preview": "By default, the Rust compiler produces optimized Wasm binaries. These binaries are suitable for working in an isolated environment, such as local development. However, the Wasm binaries the compiler builds by default aren't guaranteed to be deterministically reproducible. Each time the compiler generates the Wasm runtime, it might produce a slightly different Wasm byte code. This is problematic in a blockchain network where all nodes must use exactly the same raw chain specification file.",
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain-make-custom-pallet.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/make-custom-pallet/",
+ "preview": "FRAME provides a powerful set of tools for blockchain development, including a library of pre-built pallets. However, its true strength lies in the ability to create custom pallets tailored to your specific needs. This section will guide you through creating your own custom pallet, allowing you to extend your blockchain's functionality in unique ways.",
"outline": [
{
"depth": 2,
@@ -1840,64 +1799,67 @@
},
{
"depth": 2,
- "title": "Prerequisites",
- "anchor": "prerequisites"
+ "title": "Initial Setup",
+ "anchor": "initial-setup"
},
{
"depth": 2,
- "title": "Tooling for Wasm Runtime",
- "anchor": "tooling-for-wasm-runtime"
+ "title": "Pallet Configuration",
+ "anchor": "pallet-configuration"
},
{
"depth": 2,
- "title": "Working with the Docker Container",
- "anchor": "working-with-the-docker-container"
+ "title": "Pallet Events",
+ "anchor": "pallet-events"
},
{
"depth": 2,
- "title": "Prepare the Environment",
- "anchor": "prepare-the-environment"
+ "title": "Pallet Errors",
+ "anchor": "pallet-errors"
},
{
"depth": 2,
- "title": "Start a Deterministic Build",
- "anchor": "start-a-deterministic-build"
+ "title": "Pallet Storage",
+ "anchor": "pallet-storage"
},
{
"depth": 2,
- "title": "Use srtool in GitHub Actions",
- "anchor": "use-srtool-in-github-actions"
+ "title": "Pallet Dispatchable Extrinsics",
+ "anchor": "pallet-dispatchable-extrinsics"
},
{
"depth": 2,
- "title": "Use the srtool Image via Docker Hub",
- "anchor": "use-the-srtool-image-via-docker-hub"
+ "title": "Pallet Implementation Overview",
+ "anchor": "pallet-implementation-overview"
},
{
- "depth": 3,
- "title": "Naming Convention for Images",
- "anchor": "naming-convention-for-images"
+ "depth": 2,
+ "title": "Where to Go Next",
+ "anchor": "where-to-go-next"
}
],
"stats": {
- "chars": 8470,
- "words": 1227,
+ "chars": 17824,
+ "words": 2311,
"headings": 9,
- "estimated_token_count_total": 1944
+ "estimated_token_count_total": 3995
},
- "hash": "sha256:4fc8cab40e982e860b64d9aede1058fe7fa82ec321ac215b919db00c4df0a9c0",
+ "hash": "sha256:19997d390abf2847824024ba923f46a61106ef77544d256d50b371210816b309",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
- "id": "develop-parachains-deployment-coretime-renewal",
- "title": "Coretime Renewal",
- "slug": "develop-parachains-deployment-coretime-renewal",
+ "id": "develop-parachains-customize-parachain-overview",
+ "title": "Overview of FRAME",
+ "slug": "develop-parachains-customize-parachain-overview",
"categories": [
+ "Basics",
"Parachains"
],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-deployment-coretime-renewal.md",
- "html_url": "https://docs.polkadot.com/develop/parachains/deployment/coretime-renewal/",
- "preview": "Coretime can be purchased in bulk for a period of 28 days, providing access to Polkadot's shared security and interoperability for Polkadot parachains. The bulk purchase of coretime includes a rent-control mechanism that keeps future purchases within a predictable price range of the initial purchase. This allows cores to be renewed at a known price without competing against other participants in the open market.",
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-customize-parachain-overview.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/customize-parachain/overview/",
+ "preview": "The runtime is the heart of any Polkadot SDK-based blockchain, handling the essential logic that governs state changes and transaction processing. With Polkadot SDK’s [FRAME (Framework for Runtime Aggregation of Modularized Entities)](/reference/glossary/#frame-framework-for-runtime-aggregation-of-modularized-entities){target=\\_bank}, developers gain access to a powerful suite of tools for building custom blockchain runtimes. FRAME offers a modular architecture, featuring reusable pallets and su",
"outline": [
{
"depth": 2,
@@ -1906,23 +1868,157 @@
},
{
"depth": 2,
- "title": "Bulk Sale Phases",
- "anchor": "bulk-sale-phases"
+ "title": "FRAME Runtime Architecture",
+ "anchor": "frame-runtime-architecture"
},
{
- "depth": 2,
- "title": "Renewal Timing",
- "anchor": "renewal-timing"
+ "depth": 3,
+ "title": "Pallets",
+ "anchor": "pallets"
+ },
+ {
+ "depth": 3,
+ "title": "Support Libraries",
+ "anchor": "support-libraries"
},
{
"depth": 2,
- "title": "Manual Renewal",
- "anchor": "manual-renewal"
+ "title": "Compose a Runtime with Pallets",
+ "anchor": "compose-a-runtime-with-pallets"
},
{
"depth": 2,
- "title": "Auto-Renewal",
- "anchor": "auto-renewal"
+ "title": "Starting from Templates",
+ "anchor": "starting-from-templates"
+ },
+ {
+ "depth": 3,
+ "title": "Solochain Templates",
+ "anchor": "solochain-templates"
+ },
+ {
+ "depth": 3,
+ "title": "Parachain Templates",
+ "anchor": "parachain-templates"
+ },
+ {
+ "depth": 2,
+ "title": "Where to Go Next",
+ "anchor": "where-to-go-next"
+ }
+ ],
+ "stats": {
+ "chars": 9427,
+ "words": 1267,
+ "headings": 9,
+ "estimated_token_count_total": 2019
+ },
+ "hash": "sha256:0becb82886d34e2ed23d963efd2c14120112e6e080ea4072e864531299b59753",
+ "last_modified": "2025-10-28T14:42:12+00:00",
+ "token_estimator": "heuristic-v1"
+ },
+ {
+ "id": "develop-parachains-deployment-build-deterministic-runtime",
+ "title": "Build a deterministic runtime",
+ "slug": "develop-parachains-deployment-build-deterministic-runtime",
+ "categories": [
+ "Parachains"
+ ],
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-deployment-build-deterministic-runtime.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/deployment/build-deterministic-runtime/",
+ "preview": "By default, the Rust compiler produces optimized Wasm binaries. These binaries are suitable for working in an isolated environment, such as local development. However, the Wasm binaries the compiler builds by default aren't guaranteed to be deterministically reproducible. Each time the compiler generates the Wasm runtime, it might produce a slightly different Wasm byte code. This is problematic in a blockchain network where all nodes must use exactly the same raw chain specification file.",
+ "outline": [
+ {
+ "depth": 2,
+ "title": "Introduction",
+ "anchor": "introduction"
+ },
+ {
+ "depth": 2,
+ "title": "Prerequisites",
+ "anchor": "prerequisites"
+ },
+ {
+ "depth": 2,
+ "title": "Tooling for Wasm Runtime",
+ "anchor": "tooling-for-wasm-runtime"
+ },
+ {
+ "depth": 2,
+ "title": "Working with the Docker Container",
+ "anchor": "working-with-the-docker-container"
+ },
+ {
+ "depth": 2,
+ "title": "Prepare the Environment",
+ "anchor": "prepare-the-environment"
+ },
+ {
+ "depth": 2,
+ "title": "Start a Deterministic Build",
+ "anchor": "start-a-deterministic-build"
+ },
+ {
+ "depth": 2,
+ "title": "Use srtool in GitHub Actions",
+ "anchor": "use-srtool-in-github-actions"
+ },
+ {
+ "depth": 2,
+ "title": "Use the srtool Image via Docker Hub",
+ "anchor": "use-the-srtool-image-via-docker-hub"
+ },
+ {
+ "depth": 3,
+ "title": "Naming Convention for Images",
+ "anchor": "naming-convention-for-images"
+ }
+ ],
+ "stats": {
+ "chars": 8470,
+ "words": 1227,
+ "headings": 9,
+ "estimated_token_count_total": 1944
+ },
+ "hash": "sha256:4fc8cab40e982e860b64d9aede1058fe7fa82ec321ac215b919db00c4df0a9c0",
+ "last_modified": "2025-10-28T14:42:12+00:00",
+ "token_estimator": "heuristic-v1"
+ },
+ {
+ "id": "develop-parachains-deployment-coretime-renewal",
+ "title": "Coretime Renewal",
+ "slug": "develop-parachains-deployment-coretime-renewal",
+ "categories": [
+ "Parachains"
+ ],
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-deployment-coretime-renewal.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/deployment/coretime-renewal/",
+ "preview": "Coretime can be purchased in bulk for a period of 28 days, providing access to Polkadot's shared security and interoperability for Polkadot parachains. The bulk purchase of coretime includes a rent-control mechanism that keeps future purchases within a predictable price range of the initial purchase. This allows cores to be renewed at a known price without competing against other participants in the open market.",
+ "outline": [
+ {
+ "depth": 2,
+ "title": "Introduction",
+ "anchor": "introduction"
+ },
+ {
+ "depth": 2,
+ "title": "Bulk Sale Phases",
+ "anchor": "bulk-sale-phases"
+ },
+ {
+ "depth": 2,
+ "title": "Renewal Timing",
+ "anchor": "renewal-timing"
+ },
+ {
+ "depth": 2,
+ "title": "Manual Renewal",
+ "anchor": "manual-renewal"
+ },
+ {
+ "depth": 2,
+ "title": "Auto-Renewal",
+ "anchor": "auto-renewal"
},
{
"depth": 3,
@@ -1957,6 +2053,7 @@
"estimated_token_count_total": 3068
},
"hash": "sha256:9918593a46c12a1756552ddfaf7421ad6262600735b6f1fec030911420fe1736",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2038,6 +2135,7 @@
"estimated_token_count_total": 3025
},
"hash": "sha256:a60fe36a5ba6d1cafe12eab75300afd24a46d3ace1e791087adb7e3e538afcc3",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2089,6 +2187,7 @@
"estimated_token_count_total": 1289
},
"hash": "sha256:39c58dbe2ddcd542d7074d08d72f1811318dc8a3130419025480fd5cbe9fc3e7",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2124,7 +2223,152 @@
"headings": 3,
"estimated_token_count_total": 966
},
- "hash": "sha256:b25618dc598f4f946da06f854211645768214e0b51d06b684b0cab668b66124c",
+ "hash": "sha256:358ed14147b96b47deb61df9a1ea0e1103a139ea5edb78c5d50a48d5a779b80d",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "token_estimator": "heuristic-v1"
+ },
+ {
+ "id": "develop-parachains-install-polkadot-sdk",
+ "title": "Install Polkadot SDK Dependencies",
+ "slug": "develop-parachains-install-polkadot-sdk",
+ "categories": [
+ "Basics",
+ "Tooling"
+ ],
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-install-polkadot-sdk.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/install-polkadot-sdk/",
+ "preview": "This guide provides step-by-step instructions for installing the dependencies you need to work with the Polkadot SDK-based chains on macOS, Linux, and Windows. Follow the appropriate section for your operating system to ensure all necessary tools are installed and configured properly.",
+ "outline": [
+ {
+ "depth": 2,
+ "title": "macOS",
+ "anchor": "macos"
+ },
+ {
+ "depth": 3,
+ "title": "Before You Begin",
+ "anchor": "before-you-begin"
+ },
+ {
+ "depth": 3,
+ "title": "Install Required Packages and Rust",
+ "anchor": "install-required-packages-and-rust"
+ },
+ {
+ "depth": 2,
+ "title": "Linux",
+ "anchor": "linux"
+ },
+ {
+ "depth": 3,
+ "title": "Before You Begin {: #before-you-begin-linux }",
+ "anchor": "before-you-begin-before-you-begin-linux"
+ },
+ {
+ "depth": 3,
+ "title": "Install Required Packages and Rust {: #install-required-packages-and-rust-linux }",
+ "anchor": "install-required-packages-and-rust-install-required-packages-and-rust-linux"
+ },
+ {
+ "depth": 2,
+ "title": "Windows (WSL)",
+ "anchor": "windows-wsl"
+ },
+ {
+ "depth": 3,
+ "title": "Before You Begin {: #before-you-begin-windows }",
+ "anchor": "before-you-begin-before-you-begin-windows"
+ },
+ {
+ "depth": 3,
+ "title": "Set Up Windows Subsystem for Linux",
+ "anchor": "set-up-windows-subsystem-for-linux"
+ },
+ {
+ "depth": 3,
+ "title": "Install Required Packages and Rust {: #install-required-packages-and-rust-windows }",
+ "anchor": "install-required-packages-and-rust-install-required-packages-and-rust-windows"
+ },
+ {
+ "depth": 2,
+ "title": "Verifying Installation",
+ "anchor": "verifying-installation"
+ },
+ {
+ "depth": 2,
+ "title": "Where to Go Next",
+ "anchor": "where-to-go-next"
+ }
+ ],
+ "stats": {
+ "chars": 12756,
+ "words": 1840,
+ "headings": 12,
+ "estimated_token_count_total": 2709
+ },
+ "hash": "sha256:2ee5656f749b4bca445172f2bc66c7fc39af40ff173626662ae4c399f49cf909",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "token_estimator": "heuristic-v1"
+ },
+ {
+ "id": "develop-parachains-intro-polkadot-sdk",
+ "title": "Introduction to Polkadot SDK",
+ "slug": "develop-parachains-intro-polkadot-sdk",
+ "categories": [
+ "Basics",
+ "Tooling"
+ ],
+ "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-parachains-intro-polkadot-sdk.md",
+ "html_url": "https://docs.polkadot.com/develop/parachains/intro-polkadot-sdk/",
+ "preview": "The [Polkadot SDK](https://github.com/paritytech/polkadot-sdk/tree/polkadot-stable2506-2){target=\\_blank} is a powerful and versatile developer kit designed to facilitate building on the Polkadot network. It provides the necessary components for creating custom blockchains, parachains, generalized rollups, and more. Written in the Rust programming language, it puts security and robustness at the forefront of its design.",
+ "outline": [
+ {
+ "depth": 2,
+ "title": "Introduction",
+ "anchor": "introduction"
+ },
+ {
+ "depth": 2,
+ "title": "Polkadot SDK Overview",
+ "anchor": "polkadot-sdk-overview"
+ },
+ {
+ "depth": 3,
+ "title": "Substrate",
+ "anchor": "substrate"
+ },
+ {
+ "depth": 3,
+ "title": "FRAME",
+ "anchor": "frame"
+ },
+ {
+ "depth": 3,
+ "title": "Cumulus",
+ "anchor": "cumulus"
+ },
+ {
+ "depth": 2,
+ "title": "Why Use Polkadot SDK?",
+ "anchor": "why-use-polkadot-sdk"
+ },
+ {
+ "depth": 2,
+ "title": "Create a Custom Blockchain Using the SDK",
+ "anchor": "create-a-custom-blockchain-using-the-sdk"
+ }
+ ],
+ "stats": {
+ "chars": 8758,
+ "words": 1156,
+ "headings": 7,
+ "estimated_token_count_total": 1892
+ },
+ "hash": "sha256:74de798c287cae75729e7db54019507f03a361dbbd1f2bb58c4694605f83efab",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2176,6 +2420,7 @@
"estimated_token_count_total": 4719
},
"hash": "sha256:bfad885d8053d052c55dbffc3c09e6196586795c3a1d07ab6ad58f9006ec3345",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2237,6 +2482,7 @@
"estimated_token_count_total": 1819
},
"hash": "sha256:b0c1535fa8e969a9bdeee426a5a35a42b4649121fb8ce6fd2b15fdeba35b5d5f",
+ "last_modified": "2025-10-28T14:42:12+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2267,7 +2513,9 @@
"headings": 2,
"estimated_token_count_total": 236
},
- "hash": "sha256:3b0a9e8037c7634c33ac6674170bd763599fca914855d9d2fbf490d359140130",
+ "hash": "sha256:07e63e1e99b9acf1cc3b5ef8fa1f06ff22182b2a801582ce800eba37d7d39408",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2298,7 +2546,9 @@
"headings": 2,
"estimated_token_count_total": 211
},
- "hash": "sha256:0ce1fe38de00827a0735b9fa8076492205c2450c61da9fbd1937d9f38cfe7825",
+ "hash": "sha256:55dc252fdecf1590048ce8d009b822e90231442abe81e9593cf1635944a31336",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
@@ -2329,627 +2579,2627 @@
"headings": 2,
"estimated_token_count_total": 330
},
- "hash": "sha256:75a6fa2f21b67009be62e07bab01655a10b2c35a5292dc1f7ca57df846d709f3",
+ "hash": "sha256:f4964f894f7cd2fdfd699c017b4bd25cffc322b03a5a88a36c682cf952832ccc",
+ "last_modified": "2025-10-27T18:04:05+00:00",
+ "last_modified": "2025-10-27T18:04:05+00:00",
"token_estimator": "heuristic-v1"
},
{
- "id": "develop-smart-contracts-connect-to-kusama",
- "title": "Connect to Kusama",
- "slug": "develop-smart-contracts-connect-to-kusama",
+ "id": "develop-parachains-testing-benchmarking",
+ "title": "Benchmarking FRAME Pallets",
+ "slug": "develop-parachains-testing-benchmarking",
"categories": [
- "Uncategorized"
+ "Parachains"
],
- "raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/develop-smart-contracts-connect-to-kusama.md",
- "html_url": "https://docs.polkadot.com/develop/smart-contracts/connect-to-kusama/",
- "preview": "!!! smartcontract \"PolkaVM Preview Release\" PolkaVM smart contracts with Ethereum compatibility are in **early-stage development and may be unstable or incomplete**.
\n\n- Guide __Deploy your first contract with Remix__\n\n ---\n\n Explore the smart contract development and deployment process on Polkadot Hub using the Remix IDE.\n\n [:octicons-arrow-right-24: Build with Remix IDE](/smart-contracts/dev-environments/remix/get-started/)\n\n- Guide __Interact with the blockchain with viem__\n\n ---\n\n Use viem for interacting with Ethereum-compatible chains, to deploy and interact with smart contracts on Polkadot Hub.\n\n [:octicons-arrow-right-24: Build with viem](/smart-contracts/libraries/viem/)\n\n
"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 0, "depth": 2, "title": "Prerequisites", "anchor": "prerequisites", "start_char": 787, "end_char": 1258, "estimated_token_count": 113, "token_estimator": "heuristic-v1", "text": "## Prerequisites\n\nBefore getting started, ensure you have the following:\n\n- [Node.js](https://nodejs.org/en){target=\\_blank} v22.10.0 or later installed on your system.\n- A crypto wallet (such as MetaMask) funded with test tokens. Refer to the [Connect to Polkadot](/smart-contracts/connect){target=\\_blank} guide for more details.\n- A basic understanding of React and JavaScript.\n- Some familiarity with blockchain fundamentals and Solidity (helpful but not required)."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 1, "depth": 2, "title": "Project Overview", "anchor": "project-overview", "start_char": 1258, "end_char": 2446, "estimated_token_count": 296, "token_estimator": "heuristic-v1", "text": "## Project Overview\n\nThis dApp will interact with a basic Storage contract that you will create and deploy with Hardhat. The contract will allow you to:\n\n- Store a number on the blockchain.\n- Retrieve the stored number from the blockchain.\n- Update the stored number with a new value.\n\nYour project directory will be organized as follows:\n\n```bash\npolkadot-hub-tutorial/\n├── storage-contract/ # Hardhat project for smart contract\n│ ├── contracts/\n│ │ └── Storage.sol\n│ ├── scripts/\n│ │ └── deploy.ts\n│ ├── artifacts/\n│ │ └── contracts/\n│ │ └── Storage.sol/\n│ │ └── Storage.json\n│ ├── hardhat.config.ts\n│ ├── .env\n│ └── package.json\n│\n└── dapp/ # Next.js dApp project\n ├── abis/\n │ └── Storage.json\n └── app/\n ├── components/\n │ ├── ReadContract.tsx\n │ ├── WalletConnect.tsx\n │ └── WriteContract.tsx\n ├── utils/\n │ ├── contract.ts\n │ └── viem.ts\n ├── favicon.ico\n ├── globals.css\n ├── layout.tsx\n └── page.tsx\n```\n\nCreate the main folder for the project:\n\n```bash\nmkdir polkadot-hub-tutorial\ncd polkadot-hub-tutorial\n```"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 2, "depth": 2, "title": "Create and Deploy the Storage Contract", "anchor": "create-and-deploy-the-storage-contract", "start_char": 2446, "end_char": 2695, "estimated_token_count": 48, "token_estimator": "heuristic-v1", "text": "## Create and Deploy the Storage Contract\n\nBefore building the dApp, you'll need to create and deploy the Storage smart contract. This section will guide you through using Hardhat to write, compile, and deploy the contract to Polkadot Hub TestNet."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 3, "depth": 3, "title": "Set Up Hardhat Project", "anchor": "set-up-hardhat-project", "start_char": 2695, "end_char": 3094, "estimated_token_count": 100, "token_estimator": "heuristic-v1", "text": "### Set Up Hardhat Project\n\nFirst, create a new directory for your Hardhat project and initialize it:\n\n```bash\nmkdir storage-contract\ncd storage-contract\nnpm init -y\n```\n\nInstall Hardhat and its dependencies:\n\n```bash\nnpm install --save-dev hardhat@3.0.9\n```\n\nInitialize a new Hardhat project:\n\n```bash\nnpx hardhat --init\n```\n\nSelect **Create a TypeScript project** and accept the default options."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 4, "depth": 3, "title": "Create the Storage Contract", "anchor": "create-the-storage-contract", "start_char": 3094, "end_char": 3633, "estimated_token_count": 112, "token_estimator": "heuristic-v1", "text": "### Create the Storage Contract\n\nIn the `contracts` directory, create a new file called `Storage.sol` and add the following code:\n\n```solidity title=\"Storage.sol\"\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Storage {\n uint256 private storedNumber;\n\n event NumberStored(uint256 newNumber);\n\n function setNumber(uint256 _number) public {\n storedNumber = _number;\n emit NumberStored(_number);\n }\n}\n```\n\nThis simple contract stores a single number and provides functions to read and update it."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 5, "depth": 3, "title": "Configure Hardhat for Polkadot Hub", "anchor": "configure-hardhat-for-polkadot-hub", "start_char": 3633, "end_char": 5430, "estimated_token_count": 415, "token_estimator": "heuristic-v1", "text": "### Configure Hardhat for Polkadot Hub\n\nUpdate your `hardhat.config.ts` file to include the Polkadot Hub TestNet configuration:\n\n```typescript title=\"hardhat.config.ts\" hl_lines=\"39-44\"\nimport type { HardhatUserConfig } from \"hardhat/config\";\n\nimport hardhatToolboxViemPlugin from \"@nomicfoundation/hardhat-toolbox-viem\";\nimport { configVariable } from \"hardhat/config\";\n\nconst config: HardhatUserConfig = {\n plugins: [hardhatToolboxViemPlugin],\n solidity: {\n profiles: {\n default: {\n version: \"0.8.28\",\n },\n production: {\n version: \"0.8.28\",\n settings: {\n optimizer: {\n enabled: true,\n runs: 200,\n },\n },\n },\n },\n },\n networks: {\n hardhatMainnet: {\n type: \"edr-simulated\",\n chainType: \"l1\",\n },\n hardhatOp: {\n type: \"edr-simulated\",\n chainType: \"op\",\n },\n sepolia: {\n type: \"http\",\n chainType: \"l1\",\n url: configVariable(\"SEPOLIA_RPC_URL\"),\n accounts: [configVariable(\"SEPOLIA_PRIVATE_KEY\")],\n },\n polkadotTestNet: {\n type: \"http\",\n chainType: \"l1\",\n url: 'http://127.0.0.1:8545',\n accounts: [process.env.PRIVATE_KEY || ''],\n },\n },\n};\n\nexport default config;\n```\n\nCreate a `.env` file in the root of your Hardhat project:\n\n```text title=\".env\"\nPRIVATE_KEY=INSERT_PRIVATE_KEY_HERE\n```\n\nReplace `INSERT_PRIVATE_KEY_HERE` with your actual private key. You can get this by exporting the private key from your wallet (e.g., MetaMask).\n\n!!! warning\n Never commit your private key to version control. Use environment variables or a `.env` file (and add it to `.gitignore`) to manage sensitive information. Keep your private key safe, and never share it with anyone. If it is compromised, your funds can be stolen."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 6, "depth": 3, "title": "Compile the Contract", "anchor": "compile-the-contract", "start_char": 5430, "end_char": 5579, "estimated_token_count": 29, "token_estimator": "heuristic-v1", "text": "### Compile the Contract\n\nCompile your Storage contract:\n\n```bash\nnpx hardhat compile\n```\n\nYou should see output indicating successful compilation."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 7, "depth": 3, "title": "Deploy the Contract", "anchor": "deploy-the-contract", "start_char": 5579, "end_char": 7212, "estimated_token_count": 416, "token_estimator": "heuristic-v1", "text": "### Deploy the Contract\n\nCreate a deployment script in the `ignition/modules` directory called `Storage.ts`:\n\n```typescript title=\"Storage.ts\"\nimport { buildModule } from \"@nomicfoundation/hardhat-ignition/modules\";\n\nexport default buildModule(\"StorageModule\", (m) => {\n const storage = m.contract(\"Storage\");\n\n return { storage };\n});\n```\n\nDeploy the contract to Polkadot Hub TestNet:\n\n```bash\nnpx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotHub\n```\n\nYou should see output similar to:\n\n
\n npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotTestNet\n WARNING: You are using Node.js 23.11.0 which is not supported by Hardhat.\n Please upgrade to 22.10.0 or a later LTS version (even major version number)\n ✔ Confirm deploy to network polkadotTestNet (420420420)? … yes\n Hardhat Ignition 🚀\n Deploying [ StorageModule ]\n Batch #1\n Executed StorageModule#Storage\n [ StorageModule ] successfully deployed 🚀\n Deployed Addresses\n StorageModule#Storage - 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3\n
\n\n!!! note\n Save the deployed contract address - you'll need it when building your dApp. In the following sections, we'll reference a pre-deployed contract at `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`, but you can use your own deployed contract address instead."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 8, "depth": 3, "title": "Export the Contract ABI", "anchor": "export-the-contract-abi", "start_char": 7212, "end_char": 7599, "estimated_token_count": 89, "token_estimator": "heuristic-v1", "text": "### Export the Contract ABI\n\nAfter deployment, you'll need the contract's Application Binary Interface (ABI) for your dApp. You can find it in the `artifacts/contracts/Storage.sol/Storage.json` file generated by Hardhat. You'll use this in the next section when setting up your dApp.\n\nNow that you have your contract deployed, you're ready to build the dApp that will interact with it!"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 9, "depth": 2, "title": "Set Up the dApp Project", "anchor": "set-up-the-dapp-project", "start_char": 7599, "end_char": 7796, "estimated_token_count": 59, "token_estimator": "heuristic-v1", "text": "## Set Up the dApp Project\n\nNavigate to the root of the project, and create a new Next.js project called `dapp`:\n\n```bash\nnpx create-next-app dapp --ts --eslint --tailwind --app --yes\ncd dapp\n```"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 10, "depth": 2, "title": "Install Dependencies", "anchor": "install-dependencies", "start_char": 7796, "end_char": 7955, "estimated_token_count": 50, "token_estimator": "heuristic-v1", "text": "## Install Dependencies\n\nInstall viem and related packages:\n\n```bash\nnpm install viem@2.38.5\nnpm install --save-dev typescript@5.9.3 @types/node@22.19.24\n```"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 11, "depth": 2, "title": "Connect to Polkadot Hub", "anchor": "connect-to-polkadot-hub", "start_char": 7955, "end_char": 10052, "estimated_token_count": 509, "token_estimator": "heuristic-v1", "text": "## Connect to Polkadot Hub\n\nTo interact with Polkadot Hub, you need to set up a [Public Client](https://viem.sh/docs/clients/public#public-client){target=\\_blank} that connects to the blockchain. In this example, you will interact with the Polkadot Hub TestNet, to experiment safely. Start by creating a new file called `utils/viem.ts` and add the following code:\n\n```typescript title=\"viem.ts\"\nimport { createPublicClient, http, createWalletClient, custom } from 'viem'\nimport 'viem/window';\n\nconst transport = http('http://127.0.0.1:8545') // TODO: change to the paseo asset hub RPC URL when it's available\n\n// Configure the Polkadot Testnet Hub chain\nexport const polkadotTestnet = {\n id: 420420420,\n name: 'Polkadot Testnet',\n network: 'polkadot-testnet',\n nativeCurrency: {\n decimals: 18,\n name: 'PAS',\n symbol: 'PAS',\n },\n rpcUrls: {\n default: {\n http: ['http://127.0.0.1:8545'], // TODO: change to the paseo asset hub RPC URL\n },\n },\n} as const\n\n// Create a public client for reading data\nexport const publicClient = createPublicClient({\n chain: polkadotTestnet,\n transport\n})\n\n// Create a wallet client for signing transactions\nexport const getWalletClient = async () => {\n if (typeof window !== 'undefined' && window.ethereum) {\n const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });\n return createWalletClient({\n chain: polkadotTestnet,\n transport: custom(window.ethereum),\n account,\n });\n }\n throw new Error('No Ethereum browser provider detected');\n};\n```\n\nThis file initializes a viem client, providing helper functions for obtaining a Public Client and a [Wallet Client](https://viem.sh/docs/clients/wallet#wallet-client){target=\\_blank}. The Public Client enables reading blockchain data, while the Wallet Client allows users to sign and send transactions. Also, note that by importing `viem/window` the global `window.ethereum` will be typed as an `EIP1193Provider`, check the [`window` Polyfill](https://viem.sh/docs/typescript#window-polyfill){target=\\_blank} reference for more information."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 12, "depth": 2, "title": "Set Up the Smart Contract Interface", "anchor": "set-up-the-smart-contract-interface", "start_char": 10052, "end_char": 11943, "estimated_token_count": 415, "token_estimator": "heuristic-v1", "text": "## Set Up the Smart Contract Interface\n\nFor this dApp, you'll use a simple [Storage contract](/tutorials/smart-contracts/launch-your-first-project/create-contracts){target=\\_blank} that's already deployed in the Polkadot Hub TestNet: `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`. To interact with it, you need to define the contract interface.\n\nCreate a folder called `abis` at the root of your project, then create a file named `Storage.json` and paste the corresponding ABI of the Storage contract. You can copy and paste the following:\n\n```bash\ncp ./storage-contract/artifacts/contracts/Storage.sol/Storage.json ./dapp/abis/Storage.json\n```\n\nNext, create a file called `utils/contract.ts`:\n\n```typescript title=\"contract.ts\"\nimport { getContract } from 'viem';\nimport { publicClient, getWalletClient } from './viem';\nimport StorageABI from '../abis/Storage.json';\n\nexport const CONTRACT_ADDRESS = '0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3'; // TODO: change when the paseo asset hub RPC URL is available, and the contract is redeployed\nexport const CONTRACT_ABI = StorageABI.abi;\n\n// Create a function to get a contract instance for reading\nexport const getContractInstance = () => {\n return getContract({\n address: CONTRACT_ADDRESS,\n abi: CONTRACT_ABI,\n client: publicClient,\n });\n};\n\n// Create a function to get a contract instance with a signer for writing\nexport const getSignedContract = async () => {\n const walletClient = await getWalletClient();\n return getContract({\n address: CONTRACT_ADDRESS,\n abi: CONTRACT_ABI,\n client: walletClient,\n });\n};\n```\n\nThis file defines the contract address, ABI, and functions to create a viem [contract instance](https://viem.sh/docs/contract/getContract#contract-instances){target=\\_blank} for reading and writing operations. viem's contract utilities enable more efficient, type-safe interaction with smart contracts."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 13, "depth": 2, "title": "Create the Wallet Connection Component", "anchor": "create-the-wallet-connection-component", "start_char": 11943, "end_char": 17968, "estimated_token_count": 1343, "token_estimator": "heuristic-v1", "text": "## Create the Wallet Connection Component\n\nNow, let's create a component to handle wallet connections. Create a new file called `components/WalletConnect.tsx`:\n\n```typescript title=\"WalletConnect.tsx\"\n\"use client\";\n\nimport React, { useState, useEffect } from \"react\";\nimport { polkadotTestnet } from \"../utils/viem\";\n\ninterface WalletConnectProps {\n onConnect: (account: string) => void;\n}\n\nconst WalletConnect: React.FC = ({ onConnect }) => {\n const [account, setAccount] = useState(null);\n const [chainId, setChainId] = useState(null);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n // Check if user already has an authorized wallet connection\n const checkConnection = async () => {\n if (typeof window !== 'undefined' && window.ethereum) {\n try {\n // eth_accounts doesn't trigger the wallet popup\n const accounts = await window.ethereum.request({\n method: 'eth_accounts',\n }) as string[];\n \n if (accounts.length > 0) {\n setAccount(accounts[0]);\n const chainIdHex = await window.ethereum.request({\n method: 'eth_chainId',\n }) as string;\n setChainId(parseInt(chainIdHex, 16));\n onConnect(accounts[0]);\n }\n } catch (err) {\n console.error('Error checking connection:', err);\n setError('Failed to check wallet connection');\n }\n }\n };\n\n checkConnection();\n\n if (typeof window !== 'undefined' && window.ethereum) {\n // Setup wallet event listeners\n window.ethereum.on('accountsChanged', (accounts: string[]) => {\n setAccount(accounts[0] || null);\n if (accounts[0]) onConnect(accounts[0]);\n });\n\n window.ethereum.on('chainChanged', (chainIdHex: string) => {\n setChainId(parseInt(chainIdHex, 16));\n });\n }\n\n return () => {\n // Cleanup event listeners\n if (typeof window !== 'undefined' && window.ethereum) {\n window.ethereum.removeListener('accountsChanged', () => {});\n window.ethereum.removeListener('chainChanged', () => {});\n }\n };\n }, [onConnect]);\n\n const connectWallet = async () => {\n if (typeof window === 'undefined' || !window.ethereum) {\n setError(\n 'MetaMask not detected! Please install MetaMask to use this dApp.'\n );\n return;\n }\n\n try {\n // eth_requestAccounts triggers the wallet popup\n const accounts = await window.ethereum.request({\n method: 'eth_requestAccounts',\n }) as string[];\n \n setAccount(accounts[0]);\n\n const chainIdHex = await window.ethereum.request({\n method: 'eth_chainId',\n }) as string;\n \n const currentChainId = parseInt(chainIdHex, 16);\n setChainId(currentChainId);\n\n // Prompt user to switch networks if needed\n if (currentChainId !== polkadotTestnet.id) {\n await switchNetwork();\n }\n\n onConnect(accounts[0]);\n } catch (err) {\n console.error('Error connecting to wallet:', err);\n setError('Failed to connect wallet');\n }\n };\n\n const switchNetwork = async () => {\n console.log('Switch network')\n try {\n await window.ethereum.request({\n method: 'wallet_switchEthereumChain',\n params: [{ chainId: `0x${polkadotTestnet.id.toString(16)}` }],\n });\n } catch (switchError: any) {\n // Error 4902 means the chain hasn't been added to MetaMask\n if (switchError.code === 4902) {\n try {\n await window.ethereum.request({\n method: 'wallet_addEthereumChain',\n params: [\n {\n chainId: `0x${polkadotTestnet.id.toString(16)}`,\n chainName: polkadotTestnet.name,\n rpcUrls: [polkadotTestnet.rpcUrls.default.http[0]],\n nativeCurrency: {\n name: polkadotTestnet.nativeCurrency.name,\n symbol: polkadotTestnet.nativeCurrency.symbol,\n decimals: polkadotTestnet.nativeCurrency.decimals,\n },\n },\n ],\n });\n } catch (addError) {\n setError('Failed to add network to wallet');\n }\n } else {\n setError('Failed to switch network');\n }\n }\n };\n\n // UI-only disconnection - MetaMask doesn't support programmatic disconnection\n const disconnectWallet = () => {\n setAccount(null);\n };\n\n return (\n
\n );\n};\n\nexport default WalletConnect;\n```\n\nThis component handles connecting to the wallet, switching networks if necessary, and keeping track of the connected account. It provides a button for users to connect their wallet and displays the connected account address once connected."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 14, "depth": 2, "title": "Create the Read Contract Component", "anchor": "create-the-read-contract-component", "start_char": 17968, "end_char": 20502, "estimated_token_count": 617, "token_estimator": "heuristic-v1", "text": "## Create the Read Contract Component\n\nNow, let's create a component to read data from the contract. Create a file called `components/ReadContract.tsx`:\n\n```typescript title=\"ReadContract.tsx\"\n'use client';\n\nimport React, { useState, useEffect } from 'react';\nimport { publicClient } from '../utils/viem';\nimport { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';\n\nconst ReadContract: React.FC = () => {\n const [storedNumber, setStoredNumber] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n // Function to read data from the blockchain\n const fetchData = async () => {\n try {\n setLoading(true);\n // Call the smart contract's storedNumber function\n const number = await publicClient.readContract({\n address: CONTRACT_ADDRESS,\n abi: CONTRACT_ABI,\n functionName: 'storedNumber',\n args: [],\n }) as bigint;\n\n setStoredNumber(number.toString());\n setError(null);\n } catch (err) {\n console.error('Error fetching stored number:', err);\n setError('Failed to fetch data from the contract');\n } finally {\n setLoading(false);\n }\n };\n\n fetchData();\n\n // Poll for updates every 10 seconds to keep UI in sync with blockchain\n const interval = setInterval(fetchData, 10000);\n\n // Clean up interval on component unmount\n return () => clearInterval(interval);\n }, []);\n\n return (\n
\n
Contract Data
\n {loading ? (\n
\n \n
\n ) : error ? (\n
{error}
\n ) : (\n
\n
\n Stored Number: {storedNumber}\n
\n
\n )}\n
\n );\n};\n\nexport default ReadContract;\n```\n\nThis component reads the `storedNumber` value from the contract and displays it to the user. It also sets up a polling interval to refresh the data periodically, ensuring that the UI stays in sync with the blockchain state."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 15, "depth": 2, "title": "Create the Write Contract Component", "anchor": "create-the-write-contract-component", "start_char": 20502, "end_char": 28611, "estimated_token_count": 1825, "token_estimator": "heuristic-v1", "text": "## Create the Write Contract Component\n\nFinally, let's create a component that allows users to update the stored number. Create a file called `components/WriteContract.tsx`:\n\n```typescript title=\"WriteContract.tsx\"\n\"use client\";\n\nimport React, { useState, useEffect } from \"react\";\nimport { publicClient, getWalletClient } from '../utils/viem';\nimport { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';\n\ninterface WriteContractProps {\n account: string | null;\n}\n\nconst WriteContract: React.FC = ({ account }) => {\n const [newNumber, setNewNumber] = useState(\"\");\n const [status, setStatus] = useState<{\n type: string | null;\n message: string;\n }>({\n type: null,\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isCorrectNetwork, setIsCorrectNetwork] = useState(true);\n\n // Check if the account is on the correct network\n useEffect(() => {\n const checkNetwork = async () => {\n if (!account) return;\n\n try {\n // Get the chainId from the public client\n const chainId = await publicClient.getChainId();\n\n // Get the user's current chainId from their wallet\n const walletClient = await getWalletClient();\n if (!walletClient) return;\n\n const walletChainId = await walletClient.getChainId();\n\n // Check if they match\n setIsCorrectNetwork(chainId === walletChainId);\n } catch (err) {\n console.error(\"Error checking network:\", err);\n setIsCorrectNetwork(false);\n }\n };\n\n checkNetwork();\n }, [account]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n // Validation checks\n if (!account) {\n setStatus({ type: \"error\", message: \"Please connect your wallet first\" });\n return;\n }\n\n if (!isCorrectNetwork) {\n setStatus({\n type: \"error\",\n message: \"Please switch to the correct network in your wallet\",\n });\n return;\n }\n\n if (!newNumber || isNaN(Number(newNumber))) {\n setStatus({ type: \"error\", message: \"Please enter a valid number\" });\n return;\n }\n\n try {\n setIsSubmitting(true);\n setStatus({ type: \"info\", message: \"Initiating transaction...\" });\n\n // Get wallet client for transaction signing\n const walletClient = await getWalletClient();\n\n if (!walletClient) {\n setStatus({ type: \"error\", message: \"Wallet client not available\" });\n return;\n }\n\n // Check if account matches\n if (\n walletClient.account?.address.toLowerCase() !== account.toLowerCase()\n ) {\n setStatus({\n type: \"error\",\n message:\n \"Connected wallet account doesn't match the selected account\",\n });\n return;\n }\n\n // Prepare transaction and wait for user confirmation in wallet\n setStatus({\n type: \"info\",\n message: \"Please confirm the transaction in your wallet...\",\n });\n\n // Simulate the contract call first\n console.log('newNumber', newNumber);\n const { request } = await publicClient.simulateContract({\n address: CONTRACT_ADDRESS,\n abi: CONTRACT_ABI,\n functionName: \"setNumber\",\n args: [BigInt(newNumber)],\n account: walletClient.account,\n });\n\n // Send the transaction with wallet client\n const hash = await walletClient.writeContract(request);\n\n // Wait for transaction to be mined\n setStatus({\n type: \"info\",\n message: \"Transaction submitted. Waiting for confirmation...\",\n });\n\n const receipt = await publicClient.waitForTransactionReceipt({\n hash,\n });\n\n setStatus({\n type: \"success\",\n message: `Transaction confirmed! Transaction hash: ${receipt.transactionHash}`,\n });\n\n setNewNumber(\"\");\n } catch (err: any) {\n console.error(\"Error updating number:\", err);\n\n // Handle specific errors\n if (err.code === 4001) {\n // User rejected transaction\n setStatus({ type: \"error\", message: \"Transaction rejected by user.\" });\n } else if (err.message?.includes(\"Account not found\")) {\n // Account not found on the network\n setStatus({\n type: \"error\",\n message:\n \"Account not found on current network. Please check your wallet is connected to the correct network.\",\n });\n } else if (err.message?.includes(\"JSON is not a valid request object\")) {\n // JSON error - specific to your current issue\n setStatus({\n type: \"error\",\n message:\n \"Invalid request format. Please try again or contact support.\",\n });\n } else {\n // Other errors\n setStatus({\n type: \"error\",\n message: `Error: ${err.message || \"Failed to send transaction\"}`,\n });\n }\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n
\n
Update Stored Number
\n\n {!isCorrectNetwork && account && (\n
\n ⚠️ You are not connected to the correct network. Please switch\n networks in your wallet.\n
\n )}\n\n {status.message && (\n
\n {status.message}\n
\n )}\n\n \n\n {!account && (\n
\n Connect your wallet to update the stored number.\n
\n )}\n
\n );\n};\n\nexport default WriteContract;\n```\n\nThis component allows users to input a new number and send a transaction to update the value stored in the contract. It provides appropriate feedback during each step of the transaction process and handles error scenarios.\n\nUpdate the `app/page.tsx` file to integrate all components:\n\n```typescript title=\"page.tsx\"\n\"use client\";\n\nimport { useState } from \"react\";\nimport WalletConnect from \"./components/WalletConnect\";\nimport ReadContract from \"./components/ReadContract\";\nimport WriteContract from \"./components/WriteContract\";\n\nexport default function Home() {\n const [account, setAccount] = useState(null);\n\n const handleConnect = (connectedAccount: string) => {\n setAccount(connectedAccount);\n };\n\n return (\n \n
\n Polkadot Hub - Zero To Hero DApp\n
\n \n \n \n \n );\n}\n```\n\nRun the dApp:\n\n```bash\nnpm run dev\n```\n\nNavigate to `http://localhost:3000` in your browser, and you should see your dApp with the wallet connection button, the stored number displayed, and the form to update the number. You should see something like this:"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 16, "depth": 2, "title": "How It Works", "anchor": "how-it-works", "start_char": 28611, "end_char": 28704, "estimated_token_count": 18, "token_estimator": "heuristic-v1", "text": "## How It Works\n\nThis dApp uses components to interact with the blockchain in several ways."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 17, "depth": 3, "title": "Wallet Connection", "anchor": "wallet-connection", "start_char": 28704, "end_char": 29010, "estimated_token_count": 60, "token_estimator": "heuristic-v1", "text": "### Wallet Connection \n\nThe `WalletConnect` component uses the browser's Ethereum provider (MetaMask) to connect to the user's wallet and handles network switching to ensure the user is connected to the Polkadot Hub TestNet. Once connected, it provides the user's account address to the parent component."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 18, "depth": 3, "title": "Data Reads", "anchor": "data-reads", "start_char": 29010, "end_char": 29311, "estimated_token_count": 57, "token_estimator": "heuristic-v1", "text": "### Data Reads\n\nThe `ReadContract` component uses viem's `readContract` function to call the `storedNumber` view function and periodically poll for updates to keep the UI in sync with the blockchain state. The component also displays a loading indicator while fetching data and handles error states."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 19, "depth": 3, "title": "Data Writes", "anchor": "data-writes", "start_char": 29311, "end_char": 29713, "estimated_token_count": 71, "token_estimator": "heuristic-v1", "text": "### Data Writes\n\nThe `WriteContract` component uses viem's `writeContract` function to send a transaction to the `setNumber` function and ensures the wallet is connected before allowing a transaction. The component shows detailed feedback during transaction submission and confirmation. After a successful transaction, the value displayed in the `ReadContract` component will update on the next poll."}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 20, "depth": 2, "title": "Conclusion", "anchor": "conclusion", "start_char": 29713, "end_char": 30610, "estimated_token_count": 178, "token_estimator": "heuristic-v1", "text": "## Conclusion\n\nCongratulations! You've successfully built a fully functional dApp that interacts with a smart contract on Polkadot Hub using viem and Next.js. Your application can now:\n\n- Create a smart contract with Hardhat and deploy it to Polkadot Hub TestNet.\n- Connect to a user's wallet and handle network switching.\n- Read data from a smart contract and keep it updated.\n- Write data to the blockchain through transactions.\n\nThese fundamental skills provide the foundation for building more complex dApps on Polkadot Hub. With this knowledge, you can extend your application to interact with more sophisticated smart contracts and create advanced user interfaces.\n\nTo get started right away with a working example, you can clone the repository and navigate to the implementation:\n\n```bash\ngit clone https://github.com/polkadot-developers/revm-hardhat-examples.git\ncd zero-to-hero-dapp\n```"}
+{"page_id": "smart-contracts-cookbook-dapps-zero-to-hero", "page_title": "Zero to Hero Smart Contract DApp", "index": 21, "depth": 2, "title": "Where to Go Next", "anchor": "where-to-go-next", "start_char": 30610, "end_char": 31203, "estimated_token_count": 147, "token_estimator": "heuristic-v1", "text": "## Where to Go Next\n\n
\n\n- Guide __Port Ethereum Projects to Polkadot Hub__\n\n ---\n\n Learn how to port an Ethereum project to Polkadot Hub using Hardhat and Viem.\n\n [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/eth-dapps/)\n\n- Guide __Dive Deeper into Polkadot Precompiles__\n\n ---\n\n Learn how to use the Polkadot precompiles to interact with the blockchain.\n\n [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/polkadot-precompiles/)\n
"}
{"page_id": "smart-contracts-cookbook-eth-dapps-uniswap-v2", "page_title": "Deploying Uniswap V2 on Polkadot", "index": 0, "depth": 2, "title": "Introduction", "anchor": "introduction", "start_char": 191, "end_char": 857, "estimated_token_count": 131, "token_estimator": "heuristic-v1", "text": "## Introduction\n\nDecentralized exchanges (DEXs) are a cornerstone of the DeFi ecosystem, allowing for permissionless token swaps without intermediaries. [Uniswap V2](https://docs.uniswap.org/contracts/v2/overview){target=\\_blank}, with its Automated Market Maker (AMM) model, revolutionized DEXs by enabling liquidity provision for any ERC-20 token pair.\n\nThis tutorial will guide you through how Uniswap V2 works so you can take advantage of it in your projects deployed to Polkadot Hub. By understanding these contracts, you'll gain hands-on experience with one of the most influential DeFi protocols and understand how it functions across blockchain ecosystems."}
{"page_id": "smart-contracts-cookbook-eth-dapps-uniswap-v2", "page_title": "Deploying Uniswap V2 on Polkadot", "index": 1, "depth": 2, "title": "Prerequisites", "anchor": "prerequisites", "start_char": 857, "end_char": 1357, "estimated_token_count": 124, "token_estimator": "heuristic-v1", "text": "## Prerequisites\n\nBefore starting, make sure you have:\n\n- Node.js (v16.0.0 or later) and npm installed.\n- Basic understanding of Solidity and JavaScript.\n- Familiarity with [`hardhat-polkadot`](/smart-contracts/dev-environments/hardhat/get-started/){target=\\_blank} development environment.\n- Some PAS test tokens to cover transaction fees (obtained from the [Polkadot faucet](https://faucet.polkadot.io/?parachain=1111){target=\\_blank}).\n- Basic understanding of how AMMs and liquidity pools work."}
{"page_id": "smart-contracts-cookbook-eth-dapps-uniswap-v2", "page_title": "Deploying Uniswap V2 on Polkadot", "index": 2, "depth": 2, "title": "Set Up the Project", "anchor": "set-up-the-project", "start_char": 1357, "end_char": 3682, "estimated_token_count": 570, "token_estimator": "heuristic-v1", "text": "## Set Up the Project\n\nLet's start by cloning the Uniswap V2 project:\n\n1. Clone the Uniswap V2 repository:\n\n ```\n git clone https://github.com/polkadot-developers/polkavm-hardhat-examples.git -b v0.0.6\n cd polkavm-hardhat-examples/uniswap-v2-polkadot/\n ```\n\n2. Install the required dependencies:\n\n ```bash\n npm install\n ```\n\n3. Update the `hardhat.config.js` file so the paths for the Substrate node and the ETH-RPC adapter match with the paths on your machine. For more info, check the [Testing your Contract](/smart-contracts/dev-environments/hardhat/compile-and-test/){target=\\_blank} section in the Hardhat guide.\n\n ```js title=\"hardhat.config.js\"\n hardhat: {\n polkavm: true,\n nodeConfig: {\n nodeBinaryPath: '../bin/substrate-node',\n rpcPort: 8000,\n dev: true,\n },\n adapterConfig: {\n adapterBinaryPath: '../bin/eth-rpc',\n dev: true,\n },\n },\n ```\n\n4. Create a `.env` file in your project root to store your private keys (you can use as an example the `env.example` file):\n\n ```text title=\".env\"\n LOCAL_PRIV_KEY=\"INSERT_LOCAL_PRIVATE_KEY\"\n AH_PRIV_KEY=\"INSERT_AH_PRIVATE_KEY\"\n ```\n\n Ensure to replace `\"INSERT_LOCAL_PRIVATE_KEY\"` with a private key available in the local environment (you can get them from this [file](https://github.com/paritytech/hardhat-polkadot/blob/main/packages/hardhat-polkadot-node/src/constants.ts#L22){target=\\_blank}). And `\"INSERT_AH_PRIVATE_KEY\"` with the account's private key you want to use to deploy the contracts. You can get this by exporting the private key from your wallet (e.g., MetaMask).\n\n !!!warning\n Keep your private key safe, and never share it with anyone. If it is compromised, your funds can be stolen.\n\n5. Compile the contracts:\n\n ```bash\n npx hardhat compile\n ```\n\nIf the compilation is successful, you should see the following output:\n\n
\n\nAfter running the above command, you should see the compiled contracts in the `artifacts-pvm` directory. This directory contains the ABI and bytecode of your contracts."}
diff --git a/llms.txt b/llms.txt
index 0388f51af..002e3fd30 100644
--- a/llms.txt
+++ b/llms.txt
@@ -240,6 +240,7 @@ Docs: Tooling
- [Sidecar REST API](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/reference-tools-sidecar.md): Learn about Substrate API Sidecar, a REST service that provides endpoints for interacting with Polkadot SDK-based chains and simplifies blockchain interactions.
- [Subxt Rust API](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/reference-tools-subxt.md): Subxt is a Rust library for type-safe interaction with Polkadot SDK blockchains, enabling transactions, state queries, runtime API access, and more.
- [XCM Tools](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/reference-tools-xcm-tools.md): Explore essential XCM tools across Polkadot, crafted to enhance cross-chain functionality and integration within the ecosystem.
+- [Zero to Hero Smart Contract DApp](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md): Learn how to build a decentralized application on Polkadot Hub using Viem and Next.js by creating a simple dApp that interacts with a smart contract.
- [Deploying Uniswap V2 on Polkadot](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-cookbook-eth-dapps-uniswap-v2.md): Learn how to deploy and test Uniswap V2 on Polkadot Hub using Hardhat, bringing AMM-based token swaps to the Polkadot ecosystem.
- [Use Hardhat with Polkadot Hub](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-dev-environments-hardhat-get-started.md): Learn how to create, compile, test, and deploy smart contracts on Polkadot Hub using Hardhat, a powerful development environment for blockchain developers.
- [Use the Polkadot Remix IDE](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-dev-environments-remix-get-started.md): Explore the smart contract development and deployment process on Asset Hub using Remix IDE, a visual IDE for blockchain developers.
@@ -392,6 +393,7 @@ Docs: Uncategorized
- [Tutorials](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/tutorials.md): Explore step-by-step tutorials for building in Polkadot, from parachain deployment and testing to cross-chain asset creation and XCM channel management.
Docs: dApp
+- [Zero to Hero Smart Contract DApp](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-cookbook-dapps-zero-to-hero.md): Learn how to build a decentralized application on Polkadot Hub using Viem and Next.js by creating a simple dApp that interacts with a smart contract.
- [Create a dApp With Ethers.js](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/tutorials-smart-contracts-launch-your-first-project-create-dapp-ethers-js.md): Learn how to build a decentralized application on Polkadot Hub using Ethers.js and Next.js by creating a simple dApp that interacts with a smart contract.
- [Create a dApp With Viem](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/tutorials-smart-contracts-launch-your-first-project-create-dapp-viem.md): Learn how to build a decentralized application on Polkadot Hub using Viem and Next.js by creating a simple dApp that interacts with a smart contract.
- [Test and Deploy with Hardhat](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/tutorials-smart-contracts-launch-your-first-project-test-and-deploy-with-hardhat.md): Learn how to set up a Hardhat development environment, write comprehensive tests for Solidity smart contracts, and deploy to local and Polkadot Hub networks.
diff --git a/smart-contracts/cookbook/dapps/zero-to-hero.md b/smart-contracts/cookbook/dapps/zero-to-hero.md
index 30404ce4c..d45dc3a05 100644
--- a/smart-contracts/cookbook/dapps/zero-to-hero.md
+++ b/smart-contracts/cookbook/dapps/zero-to-hero.md
@@ -1 +1,928 @@
-TODO
\ No newline at end of file
+---
+title: Zero to Hero Smart Contract DApp
+description: Learn how to build a decentralized application on Polkadot Hub using Viem and Next.js by creating a simple dApp that interacts with a smart contract.
+tutorial_badge: Intermediate
+categories: dApp, Tooling
+---
+
+# Zero to Hero Smart Contract DApp
+
+Decentralized applications (dApps) are a key component of the Web3 ecosystem, enabling developers to build applications that communicate directly with blockchain networks. Polkadot Hub, a blockchain with smart contract support, serves as a robust platform for deploying and interacting with dApps.
+
+This tutorial will guide you through building a fully functional dApp that interacts with a smart contract on Polkadot Hub. You'll create and deploy a smart contract with Hardhat, and then use [Viem](https://viem.sh/){target=\_blank} for blockchain interactions and [Next.js](https://nextjs.org/){target=\_blank} for the frontend. By the end, you'll have a dApp that lets users connect their wallets, retrieve on-chain data, and execute transactions.
+
+## Prerequisites
+
+Before getting started, ensure you have the following:
+
+- [Node.js](https://nodejs.org/en){target=\_blank} v22.10.0 or later installed on your system.
+- A crypto wallet (such as MetaMask) funded with test tokens. Refer to the [Connect to Polkadot](/smart-contracts/connect){target=\_blank} guide for more details.
+- A basic understanding of React and JavaScript.
+- Some familiarity with blockchain fundamentals and Solidity (helpful but not required).
+
+## Project Overview
+
+This dApp will interact with a basic Storage contract that you will create and deploy with Hardhat. The contract will allow you to:
+
+- Store a number on the blockchain.
+- Retrieve the stored number from the blockchain.
+- Update the stored number with a new value.
+
+Your project directory will be organized as follows:
+
+```bash
+polkadot-hub-tutorial/
+├── storage-contract/ # Hardhat project for smart contract
+│ ├── contracts/
+│ │ └── Storage.sol
+│ ├── scripts/
+│ │ └── deploy.ts
+│ ├── artifacts/
+│ │ └── contracts/
+│ │ └── Storage.sol/
+│ │ └── Storage.json
+│ ├── hardhat.config.ts
+│ ├── .env
+│ └── package.json
+│
+└── dapp/ # Next.js dApp project
+ ├── abis/
+ │ └── Storage.json
+ └── app/
+ ├── components/
+ │ ├── ReadContract.tsx
+ │ ├── WalletConnect.tsx
+ │ └── WriteContract.tsx
+ ├── utils/
+ │ ├── contract.ts
+ │ └── viem.ts
+ ├── favicon.ico
+ ├── globals.css
+ ├── layout.tsx
+ └── page.tsx
+```
+
+Create the main folder for the project:
+
+```bash
+mkdir polkadot-hub-tutorial
+cd polkadot-hub-tutorial
+```
+
+## Create and Deploy the Storage Contract
+
+Before building the dApp, you'll need to create and deploy the Storage smart contract. This section will guide you through using Hardhat to write, compile, and deploy the contract to Polkadot Hub TestNet.
+
+### Set Up Hardhat Project
+
+First, create a new directory for your Hardhat project and initialize it:
+
+```bash
+mkdir storage-contract
+cd storage-contract
+npm init -y
+```
+
+Install Hardhat and its dependencies:
+
+```bash
+npm install --save-dev hardhat@3.0.9
+```
+
+Initialize a new Hardhat project:
+
+```bash
+npx hardhat --init
+```
+
+Select **Create a TypeScript project** and accept the default options.
+
+### Create the Storage Contract
+
+In the `contracts` directory, create a new file called `Storage.sol` and add the following code:
+
+```solidity title="Storage.sol"
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+contract Storage {
+ uint256 private storedNumber;
+
+ event NumberStored(uint256 newNumber);
+
+ function setNumber(uint256 _number) public {
+ storedNumber = _number;
+ emit NumberStored(_number);
+ }
+}
+```
+
+This simple contract stores a single number and provides functions to read and update it.
+
+### Configure Hardhat for Polkadot Hub
+
+Update your `hardhat.config.ts` file to include the Polkadot Hub TestNet configuration:
+
+```typescript title="hardhat.config.ts" hl_lines="39-44"
+import type { HardhatUserConfig } from "hardhat/config";
+
+import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
+import { configVariable } from "hardhat/config";
+
+const config: HardhatUserConfig = {
+ plugins: [hardhatToolboxViemPlugin],
+ solidity: {
+ profiles: {
+ default: {
+ version: "0.8.28",
+ },
+ production: {
+ version: "0.8.28",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ },
+ },
+ networks: {
+ hardhatMainnet: {
+ type: "edr-simulated",
+ chainType: "l1",
+ },
+ hardhatOp: {
+ type: "edr-simulated",
+ chainType: "op",
+ },
+ sepolia: {
+ type: "http",
+ chainType: "l1",
+ url: configVariable("SEPOLIA_RPC_URL"),
+ accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
+ },
+ polkadotTestNet: {
+ type: "http",
+ chainType: "l1",
+ url: 'http://127.0.0.1:8545',
+ accounts: [process.env.PRIVATE_KEY || ''],
+ },
+ },
+};
+
+export default config;
+```
+
+Create a `.env` file in the root of your Hardhat project:
+
+```text title=".env"
+PRIVATE_KEY=INSERT_PRIVATE_KEY_HERE
+```
+
+Replace `INSERT_PRIVATE_KEY_HERE` with your actual private key. You can get this by exporting the private key from your wallet (e.g., MetaMask).
+
+!!! warning
+ Never commit your private key to version control. Use environment variables or a `.env` file (and add it to `.gitignore`) to manage sensitive information. Keep your private key safe, and never share it with anyone. If it is compromised, your funds can be stolen.
+
+
+### Compile the Contract
+
+Compile your Storage contract:
+
+```bash
+npx hardhat compile
+```
+
+You should see output indicating successful compilation.
+
+### Deploy the Contract
+
+Create a deployment script in the `ignition/modules` directory called `Storage.ts`:
+
+```typescript title="Storage.ts"
+import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
+
+export default buildModule("StorageModule", (m) => {
+ const storage = m.contract("Storage");
+
+ return { storage };
+});
+```
+
+Deploy the contract to Polkadot Hub TestNet:
+
+```bash
+npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotTestNet
+```
+
+You should see output similar to:
+
+
+ npx hardhat ignition deploy ./ignition/modules/Storage.ts --network polkadotTestNet
+ WARNING: You are using Node.js 23.11.0 which is not supported by Hardhat.
+ Please upgrade to 22.10.0 or a later LTS version (even major version number)
+ ✔ Confirm deploy to network polkadotTestNet (420420420)? … yes
+ Hardhat Ignition 🚀
+ Deploying [ StorageModule ]
+ Batch #1
+ Executed StorageModule#Storage
+ [ StorageModule ] successfully deployed 🚀
+ Deployed Addresses
+ StorageModule#Storage - 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3
+
+
+!!! note
+ Save the deployed contract address - you'll need it when building your dApp. In the following sections, we'll reference a pre-deployed contract at `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`, but you can use your own deployed contract address instead.
+
+### Export the Contract ABI
+
+After deployment, you'll need the contract's Application Binary Interface (ABI) for your dApp. You can find it in the `artifacts/contracts/Storage.sol/Storage.json` file generated by Hardhat. You'll use this in the next section when setting up your dApp.
+
+Now that you have your contract deployed, you're ready to build the dApp that will interact with it!
+
+## Set Up the dApp Project
+
+Navigate to the root of the project, and create a new Next.js project called `dapp`:
+
+```bash
+npx create-next-app dapp --ts --eslint --tailwind --app --yes
+cd dapp
+```
+
+## Install Dependencies
+
+Install viem and related packages:
+
+```bash
+npm install viem@2.38.5
+npm install --save-dev typescript@5.9.3 @types/node@22.19.24
+```
+
+## Connect to Polkadot Hub
+
+To interact with Polkadot Hub, you need to set up a [Public Client](https://viem.sh/docs/clients/public#public-client){target=\_blank} that connects to the blockchain. In this example, you will interact with the Polkadot Hub TestNet, to experiment safely. Start by creating a new file called `utils/viem.ts` and add the following code:
+
+```typescript title="viem.ts"
+import { createPublicClient, http, createWalletClient, custom } from 'viem'
+import 'viem/window';
+
+const transport = http('http://127.0.0.1:8545') // TODO: change to the paseo asset hub RPC URL when it's available
+
+// Configure the Polkadot Testnet Hub chain
+export const polkadotTestnet = {
+ id: 420420420,
+ name: 'Polkadot Testnet',
+ network: 'polkadot-testnet',
+ nativeCurrency: {
+ decimals: 18,
+ name: 'PAS',
+ symbol: 'PAS',
+ },
+ rpcUrls: {
+ default: {
+ http: ['http://127.0.0.1:8545'], // TODO: change to the paseo asset hub RPC URL
+ },
+ },
+} as const
+
+// Create a public client for reading data
+export const publicClient = createPublicClient({
+ chain: polkadotTestnet,
+ transport
+})
+
+// Create a wallet client for signing transactions
+export const getWalletClient = async () => {
+ if (typeof window !== 'undefined' && window.ethereum) {
+ const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
+ return createWalletClient({
+ chain: polkadotTestnet,
+ transport: custom(window.ethereum),
+ account,
+ });
+ }
+ throw new Error('No Ethereum browser provider detected');
+};
+```
+
+This file initializes a viem client, providing helper functions for obtaining a Public Client and a [Wallet Client](https://viem.sh/docs/clients/wallet#wallet-client){target=\_blank}. The Public Client enables reading blockchain data, while the Wallet Client allows users to sign and send transactions. Also, note that by importing `viem/window` the global `window.ethereum` will be typed as an `EIP1193Provider`, check the [`window` Polyfill](https://viem.sh/docs/typescript#window-polyfill){target=\_blank} reference for more information.
+
+## Set Up the Smart Contract Interface
+
+For this dApp, you'll use a simple [Storage contract](/tutorials/smart-contracts/launch-your-first-project/create-contracts){target=\_blank} that's already deployed in the Polkadot Hub TestNet: `0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3`. To interact with it, you need to define the contract interface.
+
+Create a folder called `abis` at the root of your project, then create a file named `Storage.json` and paste the corresponding ABI of the Storage contract. You can copy and paste the following:
+
+```bash
+cp ./storage-contract/artifacts/contracts/Storage.sol/Storage.json ./dapp/abis/Storage.json
+```
+
+Next, create a file called `utils/contract.ts`:
+
+```typescript title="contract.ts"
+import { getContract } from 'viem';
+import { publicClient, getWalletClient } from './viem';
+import StorageABI from '../abis/Storage.json';
+
+export const CONTRACT_ADDRESS = '0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3'; // TODO: change when the paseo asset hub RPC URL is available, and the contract is redeployed
+export const CONTRACT_ABI = StorageABI.abi;
+
+// Create a function to get a contract instance for reading
+export const getContractInstance = () => {
+ return getContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ client: publicClient,
+ });
+};
+
+// Create a function to get a contract instance with a signer for writing
+export const getSignedContract = async () => {
+ const walletClient = await getWalletClient();
+ return getContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ client: walletClient,
+ });
+};
+```
+
+This file defines the contract address, ABI, and functions to create a viem [contract instance](https://viem.sh/docs/contract/getContract#contract-instances){target=\_blank} for reading and writing operations. viem's contract utilities enable more efficient, type-safe interaction with smart contracts.
+
+## Create the Wallet Connection Component
+
+Now, let's create a component to handle wallet connections. Create a new file called `components/WalletConnect.tsx`:
+
+```typescript title="WalletConnect.tsx"
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { polkadotTestnet } from "../utils/viem";
+
+interface WalletConnectProps {
+ onConnect: (account: string) => void;
+}
+
+const WalletConnect: React.FC = ({ onConnect }) => {
+ const [account, setAccount] = useState(null);
+ const [chainId, setChainId] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ // Check if user already has an authorized wallet connection
+ const checkConnection = async () => {
+ if (typeof window !== 'undefined' && window.ethereum) {
+ try {
+ // eth_accounts doesn't trigger the wallet popup
+ const accounts = await window.ethereum.request({
+ method: 'eth_accounts',
+ }) as string[];
+
+ if (accounts.length > 0) {
+ setAccount(accounts[0]);
+ const chainIdHex = await window.ethereum.request({
+ method: 'eth_chainId',
+ }) as string;
+ setChainId(parseInt(chainIdHex, 16));
+ onConnect(accounts[0]);
+ }
+ } catch (err) {
+ console.error('Error checking connection:', err);
+ setError('Failed to check wallet connection');
+ }
+ }
+ };
+
+ checkConnection();
+
+ if (typeof window !== 'undefined' && window.ethereum) {
+ // Setup wallet event listeners
+ window.ethereum.on('accountsChanged', (accounts: string[]) => {
+ setAccount(accounts[0] || null);
+ if (accounts[0]) onConnect(accounts[0]);
+ });
+
+ window.ethereum.on('chainChanged', (chainIdHex: string) => {
+ setChainId(parseInt(chainIdHex, 16));
+ });
+ }
+
+ return () => {
+ // Cleanup event listeners
+ if (typeof window !== 'undefined' && window.ethereum) {
+ window.ethereum.removeListener('accountsChanged', () => {});
+ window.ethereum.removeListener('chainChanged', () => {});
+ }
+ };
+ }, [onConnect]);
+
+ const connectWallet = async () => {
+ if (typeof window === 'undefined' || !window.ethereum) {
+ setError(
+ 'MetaMask not detected! Please install MetaMask to use this dApp.'
+ );
+ return;
+ }
+
+ try {
+ // eth_requestAccounts triggers the wallet popup
+ const accounts = await window.ethereum.request({
+ method: 'eth_requestAccounts',
+ }) as string[];
+
+ setAccount(accounts[0]);
+
+ const chainIdHex = await window.ethereum.request({
+ method: 'eth_chainId',
+ }) as string;
+
+ const currentChainId = parseInt(chainIdHex, 16);
+ setChainId(currentChainId);
+
+ // Prompt user to switch networks if needed
+ if (currentChainId !== polkadotTestnet.id) {
+ await switchNetwork();
+ }
+
+ onConnect(accounts[0]);
+ } catch (err) {
+ console.error('Error connecting to wallet:', err);
+ setError('Failed to connect wallet');
+ }
+ };
+
+ const switchNetwork = async () => {
+ console.log('Switch network')
+ try {
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: `0x${polkadotTestnet.id.toString(16)}` }],
+ });
+ } catch (switchError: any) {
+ // Error 4902 means the chain hasn't been added to MetaMask
+ if (switchError.code === 4902) {
+ try {
+ await window.ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [
+ {
+ chainId: `0x${polkadotTestnet.id.toString(16)}`,
+ chainName: polkadotTestnet.name,
+ rpcUrls: [polkadotTestnet.rpcUrls.default.http[0]],
+ nativeCurrency: {
+ name: polkadotTestnet.nativeCurrency.name,
+ symbol: polkadotTestnet.nativeCurrency.symbol,
+ decimals: polkadotTestnet.nativeCurrency.decimals,
+ },
+ },
+ ],
+ });
+ } catch (addError) {
+ setError('Failed to add network to wallet');
+ }
+ } else {
+ setError('Failed to switch network');
+ }
+ }
+ };
+
+ // UI-only disconnection - MetaMask doesn't support programmatic disconnection
+ const disconnectWallet = () => {
+ setAccount(null);
+ };
+
+ return (
+
+ );
+};
+
+export default WalletConnect;
+```
+
+This component handles connecting to the wallet, switching networks if necessary, and keeping track of the connected account. It provides a button for users to connect their wallet and displays the connected account address once connected.
+
+## Create the Read Contract Component
+
+Now, let's create a component to read data from the contract. Create a file called `components/ReadContract.tsx`:
+
+```typescript title="ReadContract.tsx"
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import { publicClient } from '../utils/viem';
+import { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';
+
+const ReadContract: React.FC = () => {
+ const [storedNumber, setStoredNumber] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ // Function to read data from the blockchain
+ const fetchData = async () => {
+ try {
+ setLoading(true);
+ // Call the smart contract's storedNumber function
+ const number = await publicClient.readContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ functionName: 'storedNumber',
+ args: [],
+ }) as bigint;
+
+ setStoredNumber(number.toString());
+ setError(null);
+ } catch (err) {
+ console.error('Error fetching stored number:', err);
+ setError('Failed to fetch data from the contract');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+
+ // Poll for updates every 10 seconds to keep UI in sync with blockchain
+ const interval = setInterval(fetchData, 10000);
+
+ // Clean up interval on component unmount
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+
Contract Data
+ {loading ? (
+
+
+
+ ) : error ? (
+
{error}
+ ) : (
+
+
+ Stored Number: {storedNumber}
+
+
+ )}
+
+ );
+};
+
+export default ReadContract;
+```
+
+This component reads the `storedNumber` value from the contract and displays it to the user. It also sets up a polling interval to refresh the data periodically, ensuring that the UI stays in sync with the blockchain state.
+
+## Create the Write Contract Component
+
+Finally, let's create a component that allows users to update the stored number. Create a file called `components/WriteContract.tsx`:
+
+```typescript title="WriteContract.tsx"
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { publicClient, getWalletClient } from '../utils/viem';
+import { CONTRACT_ADDRESS, CONTRACT_ABI } from '../utils/contract';
+
+interface WriteContractProps {
+ account: string | null;
+}
+
+const WriteContract: React.FC = ({ account }) => {
+ const [newNumber, setNewNumber] = useState("");
+ const [status, setStatus] = useState<{
+ type: string | null;
+ message: string;
+ }>({
+ type: null,
+ message: "",
+ });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [isCorrectNetwork, setIsCorrectNetwork] = useState(true);
+
+ // Check if the account is on the correct network
+ useEffect(() => {
+ const checkNetwork = async () => {
+ if (!account) return;
+
+ try {
+ // Get the chainId from the public client
+ const chainId = await publicClient.getChainId();
+
+ // Get the user's current chainId from their wallet
+ const walletClient = await getWalletClient();
+ if (!walletClient) return;
+
+ const walletChainId = await walletClient.getChainId();
+
+ // Check if they match
+ setIsCorrectNetwork(chainId === walletChainId);
+ } catch (err) {
+ console.error("Error checking network:", err);
+ setIsCorrectNetwork(false);
+ }
+ };
+
+ checkNetwork();
+ }, [account]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ // Validation checks
+ if (!account) {
+ setStatus({ type: "error", message: "Please connect your wallet first" });
+ return;
+ }
+
+ if (!isCorrectNetwork) {
+ setStatus({
+ type: "error",
+ message: "Please switch to the correct network in your wallet",
+ });
+ return;
+ }
+
+ if (!newNumber || isNaN(Number(newNumber))) {
+ setStatus({ type: "error", message: "Please enter a valid number" });
+ return;
+ }
+
+ try {
+ setIsSubmitting(true);
+ setStatus({ type: "info", message: "Initiating transaction..." });
+
+ // Get wallet client for transaction signing
+ const walletClient = await getWalletClient();
+
+ if (!walletClient) {
+ setStatus({ type: "error", message: "Wallet client not available" });
+ return;
+ }
+
+ // Check if account matches
+ if (
+ walletClient.account?.address.toLowerCase() !== account.toLowerCase()
+ ) {
+ setStatus({
+ type: "error",
+ message:
+ "Connected wallet account doesn't match the selected account",
+ });
+ return;
+ }
+
+ // Prepare transaction and wait for user confirmation in wallet
+ setStatus({
+ type: "info",
+ message: "Please confirm the transaction in your wallet...",
+ });
+
+ // Simulate the contract call first
+ console.log('newNumber', newNumber);
+ const { request } = await publicClient.simulateContract({
+ address: CONTRACT_ADDRESS,
+ abi: CONTRACT_ABI,
+ functionName: "setNumber",
+ args: [BigInt(newNumber)],
+ account: walletClient.account,
+ });
+
+ // Send the transaction with wallet client
+ const hash = await walletClient.writeContract(request);
+
+ // Wait for transaction to be mined
+ setStatus({
+ type: "info",
+ message: "Transaction submitted. Waiting for confirmation...",
+ });
+
+ const receipt = await publicClient.waitForTransactionReceipt({
+ hash,
+ });
+
+ setStatus({
+ type: "success",
+ message: `Transaction confirmed! Transaction hash: ${receipt.transactionHash}`,
+ });
+
+ setNewNumber("");
+ } catch (err: any) {
+ console.error("Error updating number:", err);
+
+ // Handle specific errors
+ if (err.code === 4001) {
+ // User rejected transaction
+ setStatus({ type: "error", message: "Transaction rejected by user." });
+ } else if (err.message?.includes("Account not found")) {
+ // Account not found on the network
+ setStatus({
+ type: "error",
+ message:
+ "Account not found on current network. Please check your wallet is connected to the correct network.",
+ });
+ } else if (err.message?.includes("JSON is not a valid request object")) {
+ // JSON error - specific to your current issue
+ setStatus({
+ type: "error",
+ message:
+ "Invalid request format. Please try again or contact support.",
+ });
+ } else {
+ // Other errors
+ setStatus({
+ type: "error",
+ message: `Error: ${err.message || "Failed to send transaction"}`,
+ });
+ }
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
Update Stored Number
+
+ {!isCorrectNetwork && account && (
+
+ ⚠️ You are not connected to the correct network. Please switch
+ networks in your wallet.
+
+ )}
+
+ {status.message && (
+
+ {status.message}
+
+ )}
+
+
+
+ {!account && (
+
+ Connect your wallet to update the stored number.
+
+ )}
+
+ );
+};
+
+export default WriteContract;
+```
+
+This component allows users to input a new number and send a transaction to update the value stored in the contract. It provides appropriate feedback during each step of the transaction process and handles error scenarios.
+
+Update the `app/page.tsx` file to integrate all components:
+
+```typescript title="page.tsx"
+"use client";
+
+import { useState } from "react";
+import WalletConnect from "./components/WalletConnect";
+import ReadContract from "./components/ReadContract";
+import WriteContract from "./components/WriteContract";
+
+export default function Home() {
+ const [account, setAccount] = useState(null);
+
+ const handleConnect = (connectedAccount: string) => {
+ setAccount(connectedAccount);
+ };
+
+ return (
+
+
+ Polkadot Hub - Zero To Hero DApp
+
+
+
+
+
+ );
+}
+```
+
+Run the dApp:
+
+```bash
+npm run dev
+```
+
+Navigate to `http://localhost:3000` in your browser, and you should see your dApp with the wallet connection button, the stored number displayed, and the form to update the number. You should see something like this:
+
+
+
+## How It Works
+
+This dApp uses components to interact with the blockchain in several ways.
+
+### Wallet Connection
+
+The `WalletConnect` component uses the browser's Ethereum provider (MetaMask) to connect to the user's wallet and handles network switching to ensure the user is connected to the Polkadot Hub TestNet. Once connected, it provides the user's account address to the parent component.
+
+### Data Reads
+
+The `ReadContract` component uses viem's `readContract` function to call the `storedNumber` view function and periodically poll for updates to keep the UI in sync with the blockchain state. The component also displays a loading indicator while fetching data and handles error states.
+
+### Data Writes
+
+The `WriteContract` component uses viem's `writeContract` function to send a transaction to the `setNumber` function and ensures the wallet is connected before allowing a transaction. The component shows detailed feedback during transaction submission and confirmation. After a successful transaction, the value displayed in the `ReadContract` component will update on the next poll.
+
+## Conclusion
+
+Congratulations! You've successfully built a fully functional dApp that interacts with a smart contract on Polkadot Hub using viem and Next.js. Your application can now:
+
+- Create a smart contract with Hardhat and deploy it to Polkadot Hub TestNet.
+- Connect to a user's wallet and handle network switching.
+- Read data from a smart contract and keep it updated.
+- Write data to the blockchain through transactions.
+
+These fundamental skills provide the foundation for building more complex dApps on Polkadot Hub. With this knowledge, you can extend your application to interact with more sophisticated smart contracts and create advanced user interfaces.
+
+To get started right away with a working example, you can clone the repository and navigate to the implementation:
+
+```bash
+git clone https://github.com/polkadot-developers/revm-hardhat-examples.git
+cd zero-to-hero-dapp
+```
+
+## Where to Go Next
+
+
+
+- Guide __Port Ethereum Projects to Polkadot Hub__
+
+ ---
+
+ Learn how to port an Ethereum project to Polkadot Hub using Hardhat and Viem.
+
+ [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/eth-dapps/)
+
+- Guide __Dive Deeper into Polkadot Precompiles__
+
+ ---
+
+ Learn how to use the Polkadot precompiles to interact with the blockchain.
+
+ [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/polkadot-precompiles/)
+