মূল কন্টেন্টে যান

আপনার কন্ট্রাক্টের জন্য একটি ইউজার ইন্টারফেস তৈরি করা

TypeScript
React
Vite
Wagmi
ফ্রন্টএন্ড
শিক্ষানবিস
ওরি পোমেরান্টজ
১ নভেম্বর, ২০২৩
18 মিনিট পড়া

আপনি ইথিরিয়াম ইকোসিস্টেমে আমাদের প্রয়োজনীয় একটি ফিচার খুঁজে পেয়েছেন। এটি বাস্তবায়ন করার জন্য আপনি স্মার্ট কন্ট্রাক্ট লিখেছেন এবং হয়তো অফচেইনে চলে এমন কিছু সম্পর্কিত কোডও লিখেছেন। এটি দারুণ! দুর্ভাগ্যবশত, একটি ইউজার ইন্টারফেস ছাড়া আপনি কোনো ব্যবহারকারী পাবেন না, এবং আপনি শেষবার যখন কোনো ওয়েবসাইট লিখেছিলেন তখন মানুষ ডায়াল-আপ মডেম ব্যবহার করত এবং JavaScript নতুন ছিল।

এই আর্টিকেলটি আপনার জন্য। আমি ধরে নিচ্ছি আপনি প্রোগ্রামিং জানেন, এবং হয়তো কিছুটা JavaScript ও HTML-ও জানেন, কিন্তু আপনার ইউজার ইন্টারফেসের দক্ষতা কিছুটা পুরোনো এবং সেকেলে হয়ে গেছে। আমরা একসাথে একটি সাধারণ আধুনিক অ্যাপ্লিকেশন নিয়ে আলোচনা করব যাতে আপনি দেখতে পারেন আজকাল কীভাবে এটি করা হয়।

এটি কেন গুরুত্বপূর্ণ

তাত্ত্বিকভাবে, আপনি মানুষকে আপনার কন্ট্রাক্টের সাথে ইন্টারঅ্যাক্ট করার জন্য শুধু Etherscan (opens in a new tab) বা Blockscout (opens in a new tab) ব্যবহার করতে বলতে পারেন। এটি অভিজ্ঞ ইথিরিয়ানদের জন্য দারুণ। কিন্তু আমরা আরও এক বিলিয়ন মানুষকে (opens in a new tab) সেবা দেওয়ার চেষ্টা করছি। একটি দারুণ ইউজার এক্সপেরিয়েন্স ছাড়া এটি সম্ভব হবে না, এবং একটি ব্যবহারকারীবান্ধব ইউজার ইন্টারফেস এর একটি বড় অংশ।

Greeter অ্যাপ্লিকেশন

আধুনিক UI কীভাবে কাজ করে তার পেছনে অনেক তত্ত্ব রয়েছে, এবং অনেক ভালো সাইট রয়েছে (opens in a new tab) যেগুলো এটি ব্যাখ্যা করে (opens in a new tab)। ওই সাইটগুলোর করা চমৎকার কাজের পুনরাবৃত্তি করার পরিবর্তে, আমি ধরে নিচ্ছি আপনি কাজ করে শিখতে পছন্দ করেন এবং এমন একটি অ্যাপ্লিকেশন দিয়ে শুরু করতে চান যা নিয়ে আপনি কাজ করতে পারবেন। কাজগুলো সম্পন্ন করার জন্য আপনার এখনও তত্ত্বের প্রয়োজন হবে, এবং আমরা সেটিতে আসব - আমরা শুধু সোর্স ফাইল ধরে ধরে এগোব, এবং যখন যে বিষয়টি আসবে তখন সেটি নিয়ে আলোচনা করব।

ইনস্টলেশন

  1. অ্যাপ্লিকেশনটি Sepolia (opens in a new tab) টেস্টনেট ব্যবহার করে। প্রয়োজন হলে, Sepolia টেস্ট ETH সংগ্রহ করুন এবং আপনার ওয়ালেটে Sepolia যোগ করুন (opens in a new tab)

  2. GitHub রিপোজিটরি ক্লোন করুন এবং প্রয়োজনীয় প্যাকেজগুলো ইনস্টল করুন।

    1git clone https://github.com/qbzzt/260301-modern-ui-web3.git
    2cd 260301-modern-ui-web3
    3npm install
  3. অ্যাপ্লিকেশনটি ফ্রি অ্যাক্সেস পয়েন্ট ব্যবহার করে, যেগুলোর পারফরম্যান্সের সীমাবদ্ধতা রয়েছে। আপনি যদি একটি নোড অ্যাজ আ সার্ভিস প্রোভাইডার ব্যবহার করতে চান, তবে src/wagmi.ts-এর URL-গুলো পরিবর্তন করুন।

  4. অ্যাপ্লিকেশনটি চালু করুন।

    1npm run dev
  5. অ্যাপ্লিকেশন দ্বারা দেখানো URL-এ ব্রাউজ করুন। বেশিরভাগ ক্ষেত্রে, এটি হলো http://localhost:5173/ (opens in a new tab)

  6. আপনি একটি ব্লক এক্সপ্লোরারে (opens in a new tab) কন্ট্রাক্টের সোর্স কোড দেখতে পারেন, যা Hardhat-এর Greeter-এর একটি পরিমার্জিত সংস্করণ।

ফাইল ওয়াক থ্রু

index.html

এই ফাইলটি একটি স্ট্যান্ডার্ড HTML বয়লারপ্লেট, শুধু এই লাইনটি ছাড়া, যা স্ক্রিপ্ট ফাইলটি ইমপোর্ট করে।

1<script type="module" src="/src/main.tsx"></script>

src/main.tsx

ফাইলের এক্সটেনশন নির্দেশ করে যে এটি একটি React কম্পোনেন্ট (opens in a new tab) যা TypeScript (opens in a new tab)-এ লেখা, এটি JavaScript-এর একটি এক্সটেনশন যা টাইপ চেকিং (opens in a new tab) সাপোর্ট করে। TypeScript-কে JavaScript-এ কম্পাইল করা হয়, তাই আমরা এটি ক্লায়েন্ট সাইডে ব্যবহার করতে পারি।

আপনার আগ্রহ থাকতে পারে ভেবে এই ফাইলটি মূলত ব্যাখ্যা করা হয়েছে। সাধারণত আপনি এই ফাইলটি পরিবর্তন করবেন না, বরং src/App.tsx এবং এটি যেসব ফাইল ইমপোর্ট করে সেগুলো পরিবর্তন করবেন।

1import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2import React from 'react'
3import ReactDOM from 'react-dom/client'
4import { WagmiProvider } from 'wagmi'

আমাদের প্রয়োজনীয় লাইব্রেরি কোড ইমপোর্ট করুন।

1import App from './App.tsx'

অ্যাপ্লিকেশনটি ইমপ্লিমেন্ট করে এমন React কম্পোনেন্ট ইমপোর্ট করুন (নিচে দেখুন)।

1import { config } from './wagmi.ts'

wagmi (opens in a new tab) কনফিগারেশন ইমপোর্ট করুন, যার মধ্যে ব্লকচেইন কনফিগারেশন অন্তর্ভুক্ত রয়েছে।

1const queryClient = new QueryClient()

React Query-এর (opens in a new tab) ক্যাশ ম্যানেজারের একটি নতুন ইনস্ট্যান্স তৈরি করে। এই অবজেক্টটি স্টোর করবে:

  • ক্যাশ করা RPC কলগুলো
  • কন্ট্রাক্ট রিডগুলো
  • ব্যাকগ্রাউন্ড রিফেচিং স্টেট

আমাদের ক্যাশ ম্যানেজার প্রয়োজন কারণ wagmi v3 অভ্যন্তরীণভাবে React Query ব্যবহার করে।

1ReactDOM.createRoot(document.getElementById('root')!).render(

রুট React কম্পোনেন্ট তৈরি করুন। render-এর প্যারামিটার হলো JSX (opens in a new tab), এটি একটি এক্সটেনশন ল্যাঙ্গুয়েজ যা HTML এবং JavaScript/TypeScript উভয়ই ব্যবহার করে। এখানকার বিস্ময়সূচক চিহ্নটি TypeScript কম্পোনেন্টকে বলে: "তুমি জানো না যে document.getElementById('root') ReactDOM.createRoot-এর জন্য একটি ভ্যালিড প্যারামিটার হবে কিনা, কিন্তু চিন্তা কোরো না - আমি ডেভেলপার এবং আমি তোমাকে বলছি যে এটি হবে"।

1 <React.StrictMode>

অ্যাপ্লিকেশনটি একটি React.StrictMode কম্পোনেন্টের (opens in a new tab) ভেতরে যাচ্ছে। এই কম্পোনেন্টটি React লাইব্রেরিকে অতিরিক্ত ডিবাগিং চেক ইনসার্ট করতে বলে, যা ডেভেলপমেন্টের সময় বেশ উপকারী।

1 <WagmiProvider config={config}>

অ্যাপ্লিকেশনটি একটি WagmiProvider কম্পোনেন্টের (opens in a new tab) ভেতরেও রয়েছে। wagmi (we are going to make it) লাইব্রেরি (opens in a new tab) একটি ইথিরিয়াম ডিসেন্ট্রালাইজড এপ্লিকেশন লেখার জন্য React UI ডেফিনিশনগুলোকে viem লাইব্রেরির (opens in a new tab) সাথে কানেক্ট করে।

1 <QueryClientProvider client={queryClient}>

এবং সবশেষে, একটি React Query প্রোভাইডার যোগ করুন যাতে যেকোনো অ্যাপ্লিকেশন কম্পোনেন্ট ক্যাশ করা কোয়েরিগুলো ব্যবহার করতে পারে।

1 <App />

এখন আমরা অ্যাপ্লিকেশনের জন্য কম্পোনেন্টটি পেতে পারি, যা আসলে UI ইমপ্লিমেন্ট করে। কম্পোনেন্টের শেষে থাকা /> React-কে বলে যে XML স্ট্যান্ডার্ড অনুযায়ী এই কম্পোনেন্টের ভেতরে কোনো ডেফিনিশন নেই।

1 </QueryClientProvider>
2 </WagmiProvider>
3 </React.StrictMode>,
4)

অবশ্যই, আমাদের অন্যান্য কম্পোনেন্টগুলো ক্লোজ করতে হবে।

src/App.tsx

1import {
2 useConnect,
3 useConnection,
4 useDisconnect,
5 useSwitchChain
6} from 'wagmi'
7
8import { useEffect } from 'react'
9import { Greeter } from './Greeter'
সব দেখান

আমাদের প্রয়োজনীয় লাইব্রেরিগুলো, সেইসাথে Greeter কম্পোনেন্ট ইমপোর্ট করুন।

1const SEPOLIA_CHAIN_ID = 11155111

Sepolia চেইন ID।

1function App() {

এটি একটি React কম্পোনেন্ট তৈরি করার স্ট্যান্ডার্ড উপায়: একটি ফাংশন ডিফাইন করুন যা রেন্ডার করার প্রয়োজন হলেই কল করা হয়। এই ফাংশনে সাধারণত TypeScript বা JavaScript কোড থাকে, যার পরে একটি return স্টেটমেন্ট থাকে যা JSX কোড রিটার্ন করে।

1 const connection = useConnection()

বর্তমান কানেকশন সম্পর্কিত তথ্য, যেমন এডড্রেস এবং chainId পেতে useConnection (opens in a new tab) ব্যবহার করুন।

নিয়ম অনুযায়ী, React-এ use... নামের ফাংশনগুলো হলো হুক (hooks) (opens in a new tab)। এই ফাংশনগুলো শুধু কম্পোনেন্টে ডাটা রিটার্ন করে না; ডাটা পরিবর্তিত হলে এগুলো কম্পোনেন্টটিকে পুনরায় রেন্ডার করাও নিশ্চিত করে (কম্পোনেন্ট ফাংশনটি আবার এক্সিকিউট হয় এবং এর আউটপুট HTML-এ আগেরটিকে রিপ্লেস করে)।

1 const { connectors, connect, status, error } = useConnect()

ওয়ালেট কানেকশন সম্পর্কে তথ্য পেতে useConnect (opens in a new tab) ব্যবহার করুন।

1 const { disconnect } = useDisconnect()

এই হুকটি (opens in a new tab) আমাদের ওয়ালেট থেকে ডিসকানেক্ট করার ফাংশন দেয়।

1 const { switchChain } = useSwitchChain()

এই হুকটি (opens in a new tab) আমাদের চেইন সুইচ করতে দেয়।

1 useEffect(() => {

React হুক useEffect (opens in a new tab) আপনাকে কোনো এক্সটার্নাল সিস্টেম সিঙ্ক্রোনাইজ করার জন্য কোনো ভেরিয়েবলের মান পরিবর্তিত হলেই একটি ফাংশন রান করতে দেয়।

1 if (connection.status === 'connected' &&
2 connection.chainId !== SEPOLIA_CHAIN_ID
3 ) {
4 switchChain({ chainId: SEPOLIA_CHAIN_ID })
5 }

যদি আমরা কানেক্টেড থাকি, কিন্তু Sepolia ব্লকচেইনে না থাকি, তবে Sepolia-তে সুইচ করুন।

1 }, [connection.status, connection.chainId])

কানেকশন স্ট্যাটাস বা কানেকশন chainId পরিবর্তিত হওয়ার প্রতিবার ফাংশনটি পুনরায় রান করুন।

1 return (
2 <>

একটি React কম্পোনেন্টের JSX-কে অবশ্যই একটি সিঙ্গেল HTML কম্পোনেন্ট রিটার্ন করতে হবে। যখন আমাদের একাধিক কম্পোনেন্ট থাকে এবং সেগুলোকে র‍্যাপ করার জন্য কোনো কন্টেইনারের প্রয়োজন হয় না, তখন আমরা সেগুলোকে একটি সিঙ্গেল কম্পোনেন্টে একত্রিত করতে একটি এম্পটি কম্পোনেন্ট (<> ... </>) ব্যবহার করি।

1 <h2>Connection</h2>
2 <div>
3 status: {connection.status}
4 <br />
5 addresses: {JSON.stringify(connection.addresses)}
6 <br />
7 chainId: {connection.chainId}
8
9</div>
সব দেখান

বর্তমান কানেকশন সম্পর্কে তথ্য প্রদান করুন। JSX-এর ভেতরে, {<expression>} মানে হলো এক্সপ্রেশনটিকে JavaScript হিসেবে ইভ্যালুয়েট করা।

1 {connection.status === 'connected' && (

সিনট্যাক্স {<condition> && <value>} মানে হলো "যদি কন্ডিশনটি true হয়, তবে ভ্যালুতে ইভ্যালুয়েট করো; যদি না হয়, তবে false-এ ইভ্যালুয়েট করো"।

এটি JSX-এর ভেতরে if স্টেটমেন্ট রাখার স্ট্যান্ডার্ড উপায়।

1 <div>
2 <Greeter />
3 <hr />

JSX XML স্ট্যান্ডার্ড অনুসরণ করে, যা HTML-এর চেয়ে বেশি কড়াকড়ি। যদি কোনো ট্যাগের সংশ্লিষ্ট এন্ড ট্যাগ না থাকে, তবে এটিকে টার্মিনেট করার জন্য এর শেষে অবশ্যই একটি স্ল্যাশ (/) থাকতে হবে।

এখানে আমাদের এমন দুটি ট্যাগ রয়েছে, <Greeter /> (যাতে আসলে HTML কোড থাকে যা কন্ট্রাক্টের সাথে কথা বলে) এবং একটি হরাইজন্টাল লাইনের জন্য <hr /> (opens in a new tab)

1 <button type="button" onClick={disconnect}>
2 Disconnect
3 </button>
4
5</div>
6 )}

ব্যবহারকারী যদি এই বাটনে ক্লিক করেন, তবে disconnect ফাংশনটি কল করুন।

1 {connection.status !== 'connected' && (

যদি আমরা কানেক্টেড না থাকি, তবে ওয়ালেটে কানেক্ট করার প্রয়োজনীয় অপশনগুলো দেখান।

1 <div>
2 <h2>Connect</h2>
3 {connectors.map((connector) => (

connectors-এ আমাদের কাছে কানেক্টরগুলোর একটি তালিকা রয়েছে। আমরা এটিকে ডিসপ্লে করার জন্য JSX বাটনের একটি তালিকায় পরিণত করতে map (opens in a new tab) ব্যবহার করি।

1 <button
2 key={connector.uid}

JSX-এ "সিবলিং" ট্যাগগুলোর (যে ট্যাগগুলো একই প্যারেন্ট থেকে আসে) জন্য আলাদা আইডেন্টিফায়ার থাকা প্রয়োজন।

1 onClick={() => connect({ connector })}
2 type="button"
3 >
4 {connector.name}
5 </button>
6 ))}

কানেক্টর বাটনগুলো।

1 <div>{status}</div>
2 <div>{error?.message}</div>
3
4</div>
5 )}

অতিরিক্ত তথ্য প্রদান করুন। এক্সপ্রেশন সিনট্যাক্স <variable>?.<field> JavaScript-কে বলে যে ভেরিয়েবলটি ডিফাইন করা থাকলে, সেই ফিল্ডে ইভ্যালুয়েট করো। যদি ভেরিয়েবলটি ডিফাইন করা না থাকে, তবে এই এক্সপ্রেশনটি undefined-এ ইভ্যালুয়েট হয়।

এক্সপ্রেশন error.message, যখন কোনো এরর থাকে না, তখন একটি এক্সেপশন রেইজ করবে। error?.message ব্যবহার করলে আমরা এই সমস্যাটি এড়াতে পারি।

src/Greeter.tsx

এই ফাইলে বেশিরভাগ UI ফাংশনালিটি রয়েছে। এতে এমন সব ডেফিনিশন অন্তর্ভুক্ত রয়েছে যা সাধারণত একাধিক ফাইলে থাকে, কিন্তু যেহেতু এটি একটি টিউটোরিয়াল, তাই প্রোগ্রামটিকে পারফরম্যান্স বা মেইনটেন্যান্সের সুবিধার চেয়ে প্রথমবার সহজে বোঝার জন্য অপ্টিমাইজ করা হয়েছে।

1import {
2 useState,
3 useEffect,
4 } from 'react'
5import { useChainId,
6 useAccount,
7 useReadContract,
8 useWriteContract,
9 useWatchContractEvent,
10 useSimulateContract
11 } from 'wagmi'
সব দেখান

আমরা এই লাইব্রেরি ফাংশনগুলো ব্যবহার করি। আবারও বলছি, এগুলো যেখানে ব্যবহার করা হয়েছে তার নিচে ব্যাখ্যা করা হয়েছে।

1import { AddressType } from 'abitype'

abitype লাইব্রেরি (opens in a new tab) আমাদের বিভিন্ন ইথিরিয়াম ডাটা টাইপের জন্য TypeScript ডেফিনিশন প্রদান করে, যেমন AddressType (opens in a new tab)

1let greeterABI = [
2 { "type": "function", "name": "greet", ... },
3 { "type": "function", "name": "setGreeting", ... },
4 { "type": "event", "name": "SetGreeting", ... },
5] as const // greeterABI

Greeter কন্ট্রাক্টের জন্য ABI। আপনি যদি একই সময়ে কন্ট্রাক্ট এবং UI ডেভেলপ করেন, তবে আপনি সাধারণত সেগুলোকে একই রিপোজিটরিতে রাখবেন এবং Solidity কম্পাইলার দ্বারা জেনারেট করা ABI-কে আপনার অ্যাপ্লিকেশনে একটি ফাইল হিসেবে ব্যবহার করবেন। তবে, এখানে এটি প্রয়োজনীয় নয় কারণ কন্ট্রাক্টটি আগে থেকেই ডেভেলপ করা হয়েছে এবং এটি পরিবর্তিত হবে না।

আমরা TypeScript-কে এটি একটি প্রকৃত কনস্ট্যান্ট তা বোঝাতে as const (opens in a new tab) ব্যবহার করি। সাধারণত, আপনি যখন JavaScript-এ const x = {"a": 1} নির্দিষ্ট করেন, তখন আপনি x-এর মান পরিবর্তন করতে পারেন, আপনি শুধু এতে অ্যাসাইন করতে পারবেন না।

1type AddressPerBlockchainType = {
2 [key: number]: AddressType
3}

TypeScript স্ট্রংলি টাইপড। আমরা এই ডেফিনিশনটি ব্যবহার করে সেই এডড্রেসটি নির্দিষ্ট করি যেখানে বিভিন্ন চেইন জুড়ে Greeter কন্ট্রাক্টটি ডিপ্লয় করা হয়েছে। কী (key) হলো একটি সংখ্যা (chainId), এবং ভ্যালু হলো একটি AddressType (একটি এডড্রেস)।

1const contractAddrs : AddressPerBlockchainType = {
2 // সেপোলিয়া
3 11155111: '0xC87506C66c7896366b9E988FE0aA5B6dDE77CFfA'
4}

Sepolia (opens in a new tab)-তে কন্ট্রাক্টের এডড্রেস।

Timer কম্পোনেন্ট

Timer কম্পোনেন্ট একটি নির্দিষ্ট সময় থেকে কত সেকেন্ড পার হয়েছে তা দেখায়। এটি ইউজেবিলিটির উদ্দেশ্যে গুরুত্বপূর্ণ। ব্যবহারকারীরা যখন কিছু করেন, তখন তারা তাৎক্ষণিক প্রতিক্রিয়ার আশা করেন। ব্লকচেইনে, এটি প্রায়শই অসম্ভব কারণ কোনো লেনদেন ব্লকে প্লেস না হওয়া পর্যন্ত কিছুই ঘটে না। এর একটি সমাধান হলো ব্যবহারকারী অ্যাকশনটি পারফর্ম করার পর কতক্ষণ পার হয়েছে তা দেখানো, যাতে ব্যবহারকারী সিদ্ধান্ত নিতে পারেন যে প্রয়োজনীয় সময়টি যুক্তিসঙ্গত কিনা।

1type TimerProps = {
2 lastUpdate: Date
3}

Timer কম্পোনেন্ট একটি প্যারামিটার নেয়, lastUpdate, যা হলো শেষ অ্যাকশনের সময়।

1const Timer = ({ lastUpdate }: TimerProps) => {
2 const [_, setNow] = useState(new Date())

কম্পোনেন্টটি সঠিকভাবে কাজ করার জন্য আমাদের স্টেট (কম্পোনেন্টের সাথে যুক্ত একটি ভেরিয়েবল) থাকতে হবে এবং এটি আপডেট করতে হবে। কিন্তু আমাদের এটি পড়ার কোনো প্রয়োজন নেই, তাই কোনো ভেরিয়েবল তৈরি করার দরকার নেই।

1 useEffect(() => {
2 const id = setInterval(() => setNow(new Date()), 1000)
3 return () => clearInterval(id)
4 }, [])

setInterval (opens in a new tab) ফাংশনটি আমাদের পর্যায়ক্রমিকভাবে রান করার জন্য একটি ফাংশন শিডিউল করতে দেয়। এই ক্ষেত্রে, প্রতি সেকেন্ডে। ফাংশনটি স্টেট আপডেট করার জন্য setNow কল করে, তাই Timer কম্পোনেন্টটি পুনরায় রেন্ডার হবে। আমরা এটিকে একটি এম্পটি ডিপেন্ডেন্সি লিস্টের সাথে useEffect (opens in a new tab)-এর ভেতরে র‍্যাপ করি যাতে এটি কম্পোনেন্ট রেন্ডার হওয়ার প্রতিবারের পরিবর্তে শুধু একবার ঘটে।

1 const secondsSinceUpdate = Math.floor(
2 (Date.now() - lastUpdate.getTime()) / 1000
3 )
4
5 return (
6 <span>{secondsSinceUpdate} seconds ago</span>
7 )
8}

শেষ আপডেটের পর থেকে কত সেকেন্ড পার হয়েছে তা হিসাব করুন এবং এটি রিটার্ন করুন।

Greeter কম্পোনেন্ট
1const Greeter = () => {

অবশেষে, আমরা কম্পোনেন্টটি ডিফাইন করতে পারি।

1 const chainId = useChainId()
2 const account = useAccount()

আমরা যে চেইন এবং একাউন্ট ব্যবহার করছি সে সম্পর্কে তথ্য, wagmi (opens in a new tab)-এর সৌজন্যে। যেহেতু এটি একটি হুক (use...), তাই এই তথ্য পরিবর্তিত হলেই কম্পোনেন্টটি পুনরায় রেন্ডার হয়।

1 const greeterAddr = chainId && contractAddrs[chainId]

Greeter কন্ট্রাক্টের এডড্রেস, যা undefined হবে যদি আমাদের কাছে চেইনের তথ্য না থাকে, অথবা আমরা এমন কোনো চেইনে থাকি যেখানে সেই কন্ট্রাক্টটি নেই।

1 const readResults = useReadContract({
2 address: greeterAddr,
3 abi: greeterABI,
4 functionName: "greet", // কোনো আর্গুমেন্ট নেই
5 })

useReadContract হুকটি (opens in a new tab) কন্ট্রাক্টের (opens in a new tab) greet ফাংশন কল করে।

1 const [ currentGreeting, setCurrentGreeting ] =
2 useState("Please wait while we fetch the greeting from the blockchain...")
3 const [ newGreeting, setNewGreeting ] = useState("")

React-এর useState হুক (opens in a new tab) আমাদের একটি স্টেট ভেরিয়েবল নির্দিষ্ট করতে দেয়, যার মান কম্পোনেন্টের এক রেন্ডারিং থেকে অন্য রেন্ডারিং পর্যন্ত বজায় থাকে। এর প্রাথমিক মান হলো প্যারামিটার, এই ক্ষেত্রে এম্পটি স্ট্রিং।

useState হুক দুটি ভ্যালুসহ একটি লিস্ট রিটার্ন করে:

  1. স্টেট ভেরিয়েবলের বর্তমান মান।
  2. প্রয়োজন হলে স্টেট ভেরিয়েবল মডিফাই করার জন্য একটি ফাংশন। যেহেতু এটি একটি হুক, তাই প্রতিবার এটি কল করা হলে কম্পোনেন্টটি আবার রেন্ডার হয়।

এই ক্ষেত্রে, ব্যবহারকারী যে নতুন গ্রিটিং সেট করতে চান তার জন্য আমরা একটি স্টেট ভেরিয়েবল ব্যবহার করছি।

1 const [ lastSetterAddress, setLastSetterAddress ] = useState("")

একাধিক ব্যবহারকারী যদি একই সময়ে একই কন্ট্রাক্ট ব্যবহার করেন, তবে তারা একে অপরের গ্রিটিং ওভাররাইট করতে পারেন। এটি ব্যবহারকারীদের কাছে মনে হতে পারে যেন অ্যাপ্লিকেশনটি ঠিকমতো কাজ করছে না। অ্যাপ্লিকেশনটি যদি দেখায় যে কে সর্বশেষ গ্রিটিং সেট করেছে, তবে ব্যবহারকারী বুঝতে পারবেন যে এটি অন্য কেউ ছিল এবং অ্যাপ্লিকেশনটি সঠিকভাবে কাজ করছে।

1 const [ status, setStatus ] = useState("")
2 const [ statusTime, setStatusTime ] = useState(new Date())

ব্যবহারকারীরা দেখতে পছন্দ করেন যে তাদের অ্যাকশনগুলোর তাৎক্ষণিক প্রভাব পড়ছে। তবে, ব্লকচেইনে এমনটি হয় না। এই স্টেট ভেরিয়েবলগুলো আমাদের অন্তত ব্যবহারকারীদের কিছু দেখাতে দেয় যাতে তারা জানতে পারেন যে তাদের অ্যাকশনটি প্রসেস হচ্ছে।

1 useEffect(() => {
2 if (readResults.data) {
3 setCurrentGreeting(readResults.data)
4 setStatus("Greeting fetched from blockchain")
5 }
6 }, [readResults.data])

যদি উপরের readResults ডাটা পরিবর্তন করে এবং এটি কোনো ফলস ভ্যালুতে (undefined, উদাহরণস্বরূপ) সেট করা না থাকে, তবে বর্তমান গ্রিটিংটিকে ব্লকচেইন থেকে পড়া গ্রিটিংয়ে আপডেট করুন। এছাড়াও, স্ট্যাটাস আপডেট করুন।

1 useWatchContractEvent({
2 address: greeterAddr,
3 abi: greeterABI,
4 eventName: 'SetGreeting',
5 chainId,

SetGreeting ইভেন্টগুলো শুনুন।

1 enabled: !!greeterAddr,

!!<value> মানে হলো যদি ভ্যালুটি false হয়, অথবা এমন কোনো ভ্যালু হয় যা ফলস হিসেবে ইভ্যালুয়েট হয়, যেমন undefined, 0, বা একটি এম্পটি স্ট্রিং, তবে সামগ্রিকভাবে এক্সপ্রেশনটি false। অন্য যেকোনো ভ্যালুর জন্য, এটি true। এটি ভ্যালুগুলোকে বুলিয়ানে রূপান্তর করার একটি উপায়, কারণ যদি কোনো greeterAddr না থাকে, তবে আমরা ইভেন্টগুলো শুনতে চাই না।

1 onLogs: logs => {
2 const greetingFromContract = logs[0].args.greeting
3 setCurrentGreeting(greetingFromContract)
4 setLastSetterAddress(logs[0].args.sender)
5 updateStatus("Greeting updated by event")
6 },
7 })

যখন আমরা লগ দেখি (যা ঘটে যখন আমরা একটি নতুন ইভেন্ট দেখি), এর মানে হলো গ্রিটিংটি মডিফাই করা হয়েছে। সেই ক্ষেত্রে, আমরা currentGreeting এবং lastSetterAddress-কে নতুন ভ্যালুতে আপডেট করতে পারি। এছাড়াও, আমরা স্ট্যাটাস ডিসপ্লে আপডেট করতে চাই।

1 const updateStatus = (newStatus: string) => {
2 setStatus(newStatus)
3 setStatusTime(new Date())
4 }

যখন আমরা স্ট্যাটাস আপডেট করি তখন আমরা দুটি কাজ করতে চাই:

  1. স্ট্যাটাস স্ট্রিং (status) আপডেট করা
  2. শেষ স্ট্যাটাস আপডেটের সময় (statusTime) বর্তমান সময়ে আপডেট করা।
1 const greetingChange = (evt) =>
2 setNewGreeting(evt.target.value)

এটি নতুন গ্রিটিং ইনপুট ফিল্ডের পরিবর্তনের জন্য ইভেন্ট হ্যান্ডলার। আমরা evt প্যারামিটারের টাইপ নির্দিষ্ট করতে পারতাম, কিন্তু TypeScript একটি টাইপ অপশনাল ল্যাঙ্গুয়েজ। যেহেতু এই ফাংশনটি শুধুমাত্র একবার কল করা হয়, একটি HTML ইভেন্ট হ্যান্ডলারে, তাই আমি মনে করি না এটি প্রয়োজনীয়।

1 const { writeContractAsync } = useWriteContract()

একটি কন্ট্রাক্টে লেখার ফাংশন। এটি writeContracts (opens in a new tab)-এর মতো, তবে এটি আরও ভালো স্ট্যাটাস আপডেট এনাবল করে।

1 const simulation = useSimulateContract({
2 address: greeterAddr,
3 abi: greeterABI,
4 functionName: 'setGreeting',
5 args: [newGreeting],
6 account: account.address
7 })

ক্লায়েন্টের দৃষ্টিকোণ থেকে একটি ব্লকচেইন লেনদেন সাবমিট করার প্রক্রিয়াটি হলো:

  1. eth_estimateGas (opens in a new tab) ব্যবহার করে ব্লকচেইনের একটি নোডে লেনদেনটি পাঠান।
  2. নোড থেকে রেসপন্সের জন্য অপেক্ষা করুন।
  3. রেসপন্স পাওয়া গেলে, ব্যবহারকারীকে ওয়ালেটের মাধ্যমে লেনদেনটি সাইন করতে বলুন। এই ধাপটি নোডের রেসপন্স পাওয়ার পর অবশ্যই ঘটতে হবে কারণ সাইন করার আগে ব্যবহারকারীকে লেনদেনের গ্যাস কস্ট দেখানো হয়।
  4. ব্যবহারকারীর অনুমোদনের জন্য অপেক্ষা করুন।
  5. লেনদেনটি আবার পাঠান, এবার eth_sendRawTransaction (opens in a new tab) ব্যবহার করে।

ধাপ ২-এ লক্ষণীয় পরিমাণ সময় লাগতে পারে, যে সময়ে ব্যবহারকারীরা ভাবতে পারেন যে তাদের কমান্ডটি ইউজার ইন্টারফেস দ্বারা গৃহীত হয়েছে কিনা এবং কেন তাদের এখনও লেনদেনটি সাইন করতে বলা হচ্ছে না। এটি একটি খারাপ ইউজার এক্সপেরিয়েন্স (UX) তৈরি করে।

এর একটি সমাধান হলো প্রতিবার কোনো প্যারামিটার পরিবর্তিত হলে eth_estimateGas পাঠানো। তারপর, ব্যবহারকারী যখন সত্যিই লেনদেনটি পাঠাতে চান (এই ক্ষেত্রে Update greeting প্রেস করে), তখন গ্যাস কস্ট জানা থাকে এবং ব্যবহারকারী তাৎক্ষণিকভাবে ওয়ালেট পেজটি দেখতে পারেন।

1 return (

এখন আমরা অবশেষে রিটার্ন করার জন্য আসল HTML তৈরি করতে পারি।

1 <>
2 <h2>Greeter</h2>
3 {currentGreeting}

বর্তমান গ্রিটিং দেখান।

1 {lastSetterAddress && (
2 <p>Last updated by {
3 lastSetterAddress === account.address ? "you" : lastSetterAddress
4 }</p>
5 )}

যদি আমরা জানি যে কে সর্বশেষ গ্রিটিং সেট করেছে, তবে সেই তথ্যটি ডিসপ্লে করুন। Greeter এই তথ্যের ট্র্যাক রাখে না, এবং আমরা SetGreeting ইভেন্টগুলোর জন্য পেছনে ফিরে তাকাতে চাই না, তাই আমরা এটি কেবল তখনই পাই যখন আমাদের রান করার সময় গ্রিটিংটি পরিবর্তিত হয়।

1 <hr />
2 <input type="text"
3 value={newGreeting}
4 onChange={greetingChange}
5 />
6 <br />

এটি হলো ইনপুট টেক্সট ফিল্ড যেখানে ব্যবহারকারী একটি নতুন গ্রিটিং সেট করতে পারেন। ব্যবহারকারী প্রতিবার কোনো কি (key) প্রেস করলে, আমরা greetingChange কল করি, যা setNewGreeting কল করে। যেহেতু setNewGreeting useState থেকে আসে, তাই এটি Greeter কম্পোনেন্টটিকে পুনরায় রেন্ডার করায়। এর মানে হলো:

  • নতুন গ্রিটিংয়ের ভ্যালু ধরে রাখতে আমাদের value নির্দিষ্ট করতে হবে, কারণ অন্যথায় এটি ডিফল্ট, অর্থাৎ এম্পটি স্ট্রিংয়ে ফিরে যাবে।
  • newGreeting পরিবর্তিত হওয়ার প্রতিবার simulation-ও আপডেট হয়, যার মানে হলো আমরা সঠিক গ্রিটিংয়ের সাথে একটি সিমুলেশন পাব। এটি প্রাসঙ্গিক হতে পারে কারণ গ্যাস কস্ট কল ডাটার সাইজের ওপর নির্ভর করে, যা স্ট্রিংয়ের দৈর্ঘ্যের ওপর নির্ভর করে।
1 <button disabled={!simulation.data}

লেনদেন পাঠানোর জন্য প্রয়োজনীয় তথ্য পাওয়ার পরই কেবল বাটনটি এনাবল করুন।

1 onClick={async () => {
2 updateStatus("Please confirm in wallet...")

স্ট্যাটাস আপডেট করুন। এই পর্যায়ে, ব্যবহারকারীকে ওয়ালেটে কনফার্ম করতে হবে।

1 await writeContractAsync(simulation.data.request)
2 updateStatus("Transaction sent, waiting for greeting to change...")
3 }}
4 >
5 Update greeting
6 </button>
7

লেনদেনটি আসলে পাঠানোর পরই কেবল writeContractAsync রিটার্ন করে। এটি আমাদের ব্যবহারকারীকে দেখাতে দেয় যে লেনদেনটি ব্লকচেইনে অন্তর্ভুক্ত হওয়ার জন্য কতক্ষণ ধরে অপেক্ষা করছে।

1 <h4>Status: {status}</h4>
2 <p>Updated <Timer lastUpdate={statusTime} /> </p>
3 </>
4 )
5}

স্ট্যাটাস এবং এটি আপডেট হওয়ার পর কতক্ষণ পার হয়েছে তা দেখান।

1export {Greeter}

কম্পোনেন্টটি এক্সপোর্ট করুন।

src/wagmi.ts

অবশেষে, wagmi সম্পর্কিত বিভিন্ন ডেফিনিশন src/wagmi.ts-এ রয়েছে। আমি এখানে সবকিছু ব্যাখ্যা করব না, কারণ এর বেশিরভাগই বয়লারপ্লেট যা আপনার পরিবর্তন করার প্রয়োজন হওয়ার সম্ভাবনা কম।

1import { http, webSocket, createConfig, fallback } from 'wagmi'
2import { sepolia } from 'wagmi/chains'
3import { injected } from 'wagmi/connectors'
4
5export const config = createConfig({
6 chains: [sepolia],

wagmi কনফিগারেশনে এই অ্যাপ্লিকেশন দ্বারা সাপোর্টেড চেইনগুলো অন্তর্ভুক্ত রয়েছে। আপনি অ্যাভেইলেবল চেইনগুলোর তালিকা (opens in a new tab) দেখতে পারেন।

1 connectors: [
2 injected(),
3 ],

এই কানেক্টরটি (opens in a new tab) আমাদের ব্রাউজারে ইনস্টল করা একটি ওয়ালেটের সাথে কথা বলতে দেয়।

1 transports: {
2 [sepolia.id]: http()

Viem-এর সাথে আসা ডিফল্ট HTTP এন্ডপয়েন্টটি যথেষ্ট ভালো। আমরা যদি ভিন্ন কোনো URL চাই, তবে আমরা http("https:// hostname ") বা webSocket("wss:// hostname ") ব্যবহার করতে পারি।

1 },
2 multiInjectedProviderDiscovery: false,
3})

আরেকটি ব্লকচেইন যোগ করা

আজকাল অনেক L2 স্কেলিং সলিউশন (opens in a new tab) রয়েছে, এবং আপনি হয়তো এমন কিছু সাপোর্ট করতে চাইতে পারেন যা viem এখনও সাপোর্ট করে না। এটি করতে, আপনি src/wagmi.ts মডিফাই করবেন। এই নির্দেশিকাগুলো ব্যাখ্যা করে কীভাবে Optimism Sepolia (opens in a new tab) যোগ করতে হয়।

  1. src/wagmi.ts এডিট করুন

    A. viem থেকে defineChain টাইপ ইমপোর্ট করুন।

    1import { defineChain } from 'viem'

    B. নেটওয়ার্ক ডেফিনিশন যোগ করুন। Optimism Sepolia-এর জন্য আপনার আসলে এটি করার প্রয়োজন নেই, এটি আগে থেকেই viem-এ রয়েছে (opens in a new tab), কিন্তু এভাবে আপনি শিখতে পারবেন কীভাবে এমন একটি ব্লকচেইন যোগ করতে হয় যা viem-এ নেই।

    1const optimismSepolia = defineChain({
    2 id: 11_155_420,
    3 name: 'OP Sepolia',
    4 nativeCurrency: { name: 'Sepolia Ether', symbol: 'ETH', decimals: 18 },
    5 rpcUrls: {
    6 default: {
    7 http: ['https://sepolia.optimism.io'],
    8 webSocket: ['wss://optimism-sepolia.drpc.org'],
    9 },
    10 },
    11 blockExplorers: {
    12 default: {
    13 name: 'Blockscout',
    14 url: 'https://optimism-sepolia.blockscout.com',
    15 apiUrl: 'https://optimism-sepolia.blockscout.com/api',
    16 }
    17 },
    18})
    সব দেখান

    C. createConfig কলে নতুন চেইনটি যোগ করুন।

    1export const config = createConfig({
    2 chains: [sepolia, optimismSepolia],
    3 connectors: [
    4 injected(),
    5 ],
    6 transports: {
    7 [optimismSepolia.id]: http(),
    8 [sepolia.id]: http()
    9 },
    10 multiInjectedProviderDiscovery: false,
    11})
    সব দেখান
  2. Sepolia-তে স্বয়ংক্রিয় সুইচিং কমেন্ট আউট করতে src/App.tsx এডিট করুন। একটি প্রোডাকশন সিস্টেমে, আপনি সম্ভবত আপনার সাপোর্টেড প্রতিটি ব্লকচেইনের লিংকসহ বাটন দেখাবেন।

    1/* useEffect(() => {
    2 if (connection.status === 'connected' &&
    3 connection.chainId !== SEPOLIA_CHAIN_ID
    4 ) {
    5 switchChain({ chainId: SEPOLIA_CHAIN_ID })
    6 }
    7}, [connection.status, connection.chainId]) */
  3. অ্যাপ্লিকেশনটি যাতে নতুন নেটওয়ার্কে আপনার কন্ট্রাক্টগুলোর এডড্রেস জানে তা নিশ্চিত করতে src/Greeter.tsx এডিট করুন।

    1const contractAddrs: AddressPerBlockchainType = {
    2 // অপটিমিজম সেপোলিয়া
    3 11155420: "0x4dd85791923E9294E934271522f63875EAe5806f",
    4
    5 // সেপোলিয়া
    6 11155111: "0x7143d5c190F048C8d19fe325b748b081903E3BF0",
    7}
  4. আপনার ব্রাউজারে।

    A. ChainList (opens in a new tab)-এ ব্রাউজ করুন এবং আপনার ওয়ালেটে চেইনটি যোগ করতে টেবিলের ডানদিকের যেকোনো একটি বাটনে ক্লিক করুন।

    B. অ্যাপ্লিকেশনে, ব্লকচেইন পরিবর্তন করতে Disconnect করুন এবং তারপর পুনরায় কানেক্ট করুন। এটি হ্যান্ডেল করার আরও ভালো উপায় রয়েছে, তবে সেগুলোর জন্য অ্যাপ্লিকেশনে পরিবর্তন আনতে হবে।

উপসংহার

অবশ্যই, আপনি Greeter-এর জন্য একটি ইউজার ইন্টারফেস প্রদান করার বিষয়ে খুব একটা আগ্রহী নন। আপনি আপনার নিজের কন্ট্রাক্টগুলোর জন্য একটি ইউজার ইন্টারফেস তৈরি করতে চান। আপনার নিজের অ্যাপ্লিকেশন তৈরি করতে, এই ধাপগুলো রান করুন:

  1. একটি wagmi অ্যাপ্লিকেশন তৈরি করার জন্য নির্দিষ্ট করুন।

    1npm create wagmi
  2. এগিয়ে যেতে y টাইপ করুন।

  3. অ্যাপ্লিকেশনটির নাম দিন।

  4. React ফ্রেমওয়ার্ক নির্বাচন করুন।

  5. Vite ভ্যারিয়েন্ট নির্বাচন করুন।

এখন যান এবং আপনার কন্ট্রাক্টগুলোকে পুরো বিশ্বের জন্য ব্যবহারযোগ্য করে তুলুন।

আমার আরও কাজের জন্য এখানে দেখুন (opens in a new tab)

পেজ সর্বশেষ আপডেট: ৩ মার্চ, ২০২৬

এই টিউটোরিয়ালটি কি সহায়ক ছিল?