In this guide, we will create a simple website that interacts with your deployed Move smart contract. The website will allow users to increment the counter and display its current value using Move transactions and Viem SDK.
npx create-next-app@latest my-move-dapp --typescript
cd my-move-dapp
npm install @aptos-labs/ts-sdk viem
Create a new file src/app/Counter.tsx
:
'use client';
import { useEffect, useState } from 'react';
import { littleEndianU8ArrayToU64, counterPayload, getAccount, publicClient, walletClient } from '@/config';
export default function Counter() {
const [counter, setCounter] = useState(0);
const fetchCounter = async () => {
const callResponse = await publicClient().call({
to: await getAccount(),
data: await counterPayload('get'),
});
const data = callResponse.data?.slice(2, 10) as Uint8Array | undefined;
if (data) {
const num = littleEndianU8ArrayToU64(data);
setCounter(num);
}
};
const incrementCounter = async () => {
const hash = await walletClient().sendTransaction({
account: await getAccount(),
to: await getAccount(),
data: await counterPayload('increment'),
});
await publicClient().waitForTransactionReceipt({ hash });
fetchCounter();
};
useEffect(() => {
fetchCounter();
}, []);
return (
<div className="text-center m-24">
<h1 className="py-6">Counter: {counter}</h1>
<button className="bg-blue-700 rounded-lg px-5 py-2.5" type="button" onClick={incrementCounter}>
Increment
</button>
</div>
);
}
Replace the contents of src/app/page.tsx
with:
import Counter from "./Counter";
export default function Home() {
return (
<div>
<h1>Move Counter DApp</h1>
<Counter />
</div>
);
}
Create a new file config.ts
to define methods for creating a transaction payload and sending transactions using the Viem SDK.
import { AccountAddress, EntryFunction, TransactionPayloadEntryFunction } from '@aptos-labs/ts-sdk';
import { createPublicClient, createWalletClient, custom, defineChain } from 'viem';
import { publicActionsL2, walletActionsL2 } from 'viem/op-stack';
export const devnet = defineChain({
id: 42069,
sourceId: 42069,
name: 'Moved',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: {
http: ['https://devnet.moved.network'],
},
},
});
export const getAccount = async () => {
const [account] = await window.ethereum!.request({
method: 'eth_requestAccounts',
});
return account;
};
export const getMoveAccount = async () => {
const account = await getAccount();
const moveAccount = account.slice(0, 2) + '000000000000000000000000' + account.slice(2);
return moveAccount;
};
export const publicClient = () =>
createPublicClient({
chain: devnet,
transport: custom(window.ethereum!),
}).extend(publicActionsL2());
export const walletClient = () =>
createWalletClient({
chain: devnet,
transport: custom(window.ethereum!),
}).extend(walletActionsL2());
export const counterPayload = async (method: string) => {
const moveAccount = await getMoveAccount();
const entryFunction = EntryFunction.build(
`${moveAccount}::counter`,
method,
[],
[AccountAddress.fromString(moveAccount)],
);
const transactionPayload = new TransactionPayloadEntryFunction(entryFunction);
return transactionPayload.bcsToHex().toString() as `0x${string}`;
};
export const littleEndianU8ArrayToU64 = (array: Uint8Array) => {
if (array.length != 8) throw Error('U8 array length should be 8 to make up a U64');
let num = array[0];
for (let i = 1; i < 8; i++) {
num = (array[i] << (i * 8)) | num;
}
return num;
};
Start the development server:
npm run dev
Open http://localhost:3000
in your browser to interact with the contract.
You've successfully created a simple Next.js website to interact with your Move smart contract. Users can now send transactions to increment the counter and retrieve its value. In the next section, we will explore deploying this website for public access.