مرکزی مواد پر جائیں

اپنے کنٹریکٹ کے لیے یوزر انٹرفیس بنانا

TypeScript
React
Vite
Wagmi
فرنٹ اینڈ
ابتدائی
اوری پومرانٹز
۱ نومبر، ۲۰۲۳
22 منٹ کی پڑھائی

آپ کو ایتھیریم ایکو سسٹم میں ایک ایسی خصوصیت مل گئی ہے جس کی ہمیں ضرورت ہے۔ آپ نے اسے نافذ کرنے کے لیے اسمارٹ کنٹریکٹس لکھے، اور شاید کچھ متعلقہ کوڈ بھی جو آف چین چلتا ہے۔ یہ بہت اچھا ہے! بدقسمتی سے، یوزر انٹرفیس کے بغیر آپ کے پاس کوئی صارف نہیں ہوگا، اور آخری بار جب آپ نے کوئی ویب سائٹ لکھی تھی تو لوگ ڈائل اپ موڈیم استعمال کرتے تھے اور 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 میں URLs کو تبدیل کریں۔

  4. ایپلیکیشن شروع کریں۔

    1npm run dev
  5. ایپلیکیشن کے دکھائے گئے URL پر براؤز کریں۔ زیادہ تر صورتوں میں، یہ http://localhost:5173/ (opens in a new tab) ہوتا ہے۔

  6. آپ کنٹریکٹ کا سورس کوڈ، جو Hardhat کے Greeter کا ایک ترمیم شدہ ورژن ہے، بلاک چین ایکسپلورر پر (opens in a new tab) دیکھ سکتے ہیں۔

فائلوں کا جائزہ

index.html

یہ فائل ایک معیاری HTML بوائلرپلیٹ ہے سوائے اس لائن کے، جو اسکرپٹ فائل کو امپورٹ کرتی ہے۔

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

src/main.tsx

فائل کی ایکسٹینشن ظاہر کرتی ہے کہ یہ ایک React component (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 component کو امپورٹ کریں جو ایپلیکیشن کو نافذ کرتا ہے (نیچے دیکھیں)۔

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 component بنائیں۔ render کا پیرامیٹر JSX (opens in a new tab) ہے، جو ایک ایکسٹینشن لینگویج ہے اور HTML اور JavaScript/TypeScript دونوں کا استعمال کرتی ہے۔ یہاں فجائیہ نشان (!) TypeScript component کو بتاتا ہے: "آپ نہیں جانتے کہ document.getElementById('root')، ReactDOM.createRoot کے لیے ایک درست پیرامیٹر ہوگا، لیکن فکر نہ کریں - میں ڈیولپر ہوں اور میں آپ کو بتا رہا ہوں کہ یہ ہوگا۔"

1 <React.StrictMode>

ایپلیکیشن ایک React.StrictMode component (opens in a new tab) کے اندر جا رہی ہے۔ یہ component React لائبریری کو اضافی ڈیبگنگ چیکس داخل کرنے کا کہتا ہے، جو ڈیولپمنٹ کے دوران مفید ہے۔

1 <WagmiProvider config={config}>

ایپلیکیشن ایک WagmiProvider component (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 پرووائیڈر شامل کریں تاکہ کوئی بھی ایپلیکیشن component کیش کی گئی کیوریز استعمال کر سکے۔

1 <App />

اب ہمارے پاس ایپلیکیشن کے لیے component ہو سکتا ہے، جو دراصل UI کو نافذ کرتا ہے۔ component کے آخر میں /> React کو بتاتا ہے کہ XML معیار کے مطابق، اس component کے اندر کوئی تعریفیں نہیں ہیں۔

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

یقیناً، ہمیں دوسرے components کو بند کرنا ہوگا۔

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 component کو بھی۔

1const SEPOLIA_CHAIN_ID = 11155111

Sepolia کی چین ID۔

1function App() {

React component بنانے کا یہ معیاری طریقہ ہے: ایک فنکشن کی وضاحت کریں جسے جب بھی رینڈر کرنے کی ضرورت ہو کال کیا جائے۔ اس فنکشن میں عام طور پر TypeScript یا JavaScript کوڈ ہوتا ہے، جس کے بعد ایک return اسٹیٹمنٹ ہوتی ہے جو JSX کوڈ واپس کرتی ہے۔

1 const connection = useConnection()

موجودہ کنکشن سے متعلق معلومات حاصل کرنے کے لیے useConnection (opens in a new tab) کا استعمال کریں، جیسے کہ ایڈریس اور chainId۔

روایت کے مطابق، React میں use... کہلانے والے فنکشنز ہکس (hooks) (opens in a new tab) ہوتے ہیں۔ یہ فنکشنز نہ صرف component کو ڈیٹا واپس کرتے ہیں؛ بلکہ یہ اس بات کو بھی یقینی بناتے ہیں کہ جب وہ ڈیٹا تبدیل ہو تو اسے دوبارہ رینڈر کیا جائے (component فنکشن دوبارہ چلایا جاتا ہے، اور اس کا آؤٹ پٹ 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) آپ کو کسی بیرونی سسٹم کو ہم آہنگ کرنے کے لیے جب بھی کسی متغیر (variable) کی قدر تبدیل ہوتی ہے تو ایک فنکشن چلانے کی سہولت دیتا ہے۔

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 component کے JSX کو لازمی طور پر ایک واحد HTML component واپس کرنا چاہیے۔ جب ہمارے پاس متعدد components ہوں اور ہمیں ان سب کو لپیٹنے کے لیے کسی کنٹینر کی ضرورت نہ ہو، تو ہم انہیں ایک واحد component میں ملانے کے لیے ایک خالی component (<> ... </>) استعمال کرتے ہیں۔

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 میں "سِبلنگ" (sibling) ٹیگز (وہ ٹیگز جو ایک ہی پیرنٹ سے نکلتے ہیں) کے لیے مختلف شناخت کنندگان (identifiers) کا ہونا ضروری ہے۔

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، جب کوئی ایرر نہ ہو، تو ایک ایکسیپشن (exception) پیدا کرے گا۔ 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) کا استعمال کرتے ہیں کہ یہ ایک حقیقی کانسٹنٹ (constant) ہے۔ عام طور پر، جب آپ JavaScript میں const x = {"a": 1} کی وضاحت کرتے ہیں، تو آپ x میں قدر کو تبدیل کر سکتے ہیں، آپ بس اسے تفویض (assign) نہیں کر سکتے۔

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

TypeScript سختی سے ٹائپ کی گئی (strongly typed) ہے۔ ہم اس تعریف کا استعمال اس ایڈریس کی وضاحت کرنے کے لیے کرتے ہیں جہاں Greeter کنٹریکٹ مختلف چینز پر ڈیپلائے کیا گیا ہے۔ کلید (key) ایک نمبر (chainId) ہے، اور قدر ایک AddressType (ایک ایڈریس) ہے۔

1const contractAddrs : AddressPerBlockchainType = {
2 // سیپولیا
3 11155111: '0xC87506C66c7896366b9E988FE0aA5B6dDE77CFfA'
4}

Sepolia (opens in a new tab) پر کنٹریکٹ کا ایڈریس۔

Timer component

Timer component کسی دیے گئے وقت سے لے کر اب تک کے سیکنڈز کی تعداد دکھاتا ہے۔ یہ استعمال کی سہولت کے مقاصد کے لیے اہم ہے۔ جب صارفین کچھ کرتے ہیں، تو وہ فوری ردعمل کی توقع کرتے ہیں۔ بلاک چینز میں، یہ اکثر ناممکن ہوتا ہے کیونکہ جب تک کوئی ٹرانزیکشن کسی بلاک میں نہیں رکھی جاتی تب تک کچھ نہیں ہوتا۔ اس کا ایک حل یہ دکھانا ہے کہ صارف کو کارروائی کیے کتنا وقت گزر چکا ہے، تاکہ صارف یہ فیصلہ کر سکے کہ آیا درکار وقت معقول ہے یا نہیں۔

1type TimerProps = {
2 lastUpdate: Date
3}

Timer component ایک پیرامیٹر لیتا ہے، lastUpdate، جو آخری کارروائی کا وقت ہے۔

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

component کے صحیح طریقے سے کام کرنے کے لیے ہمیں اسٹیٹ (component سے جڑا ایک متغیر) رکھنے اور اسے اپ ڈیٹ کرنے کی ضرورت ہے۔ لیکن ہمیں اسے کبھی پڑھنے کی ضرورت نہیں ہوتی، اس لیے متغیر بنانے کی زحمت نہ کریں۔

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

setInterval (opens in a new tab) فنکشن ہمیں کسی فنکشن کو وقفے وقفے سے چلانے کے لیے شیڈول کرنے دیتا ہے۔ اس صورت میں، ہر سیکنڈ۔ فنکشن اسٹیٹ کو اپ ڈیٹ کرنے کے لیے setNow کو کال کرتا ہے، تاکہ Timer component دوبارہ رینڈر ہو جائے۔ ہم اسے ایک خالی ڈیپینڈینسی لسٹ کے ساتھ useEffect (opens in a new tab) کے اندر لپیٹتے ہیں تاکہ یہ component کے ہر بار رینڈر ہونے کے بجائے صرف ایک بار ہو۔

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

آخری اپ ڈیٹ کے بعد سے سیکنڈز کی تعداد کا حساب لگائیں اور اسے واپس کریں۔

Greeter component
1const Greeter = () => {

آخر کار، ہم component کی وضاحت کرنے پر آتے ہیں۔

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

ہم جو چین اور اکاؤنٹ استعمال کر رہے ہیں اس کے بارے میں معلومات، wagmi (opens in a new tab) کے بشکریہ۔ چونکہ یہ ایک ہک (use...) ہے، اس لیے جب بھی یہ معلومات تبدیل ہوتی ہیں تو component دوبارہ رینڈر ہوتا ہے۔

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) ہمیں ایک اسٹیٹ متغیر کی وضاحت کرنے دیتا ہے، جس کی قدر component کی ایک رینڈرنگ سے دوسری تک برقرار رہتی ہے۔ ابتدائی قدر پیرامیٹر ہے، اس صورت میں خالی اسٹرنگ۔

useState ہک دو اقدار کے ساتھ ایک فہرست واپس کرتا ہے:

  1. اسٹیٹ متغیر کی موجودہ قدر۔
  2. ضرورت پڑنے پر اسٹیٹ متغیر میں ترمیم کرنے کا ایک فنکشن۔ چونکہ یہ ایک ہک ہے، اس لیے جب بھی اسے کال کیا جاتا ہے تو component دوبارہ رینڈر ہوتا ہے۔

اس صورت میں، ہم اس نئی گریٹنگ (greeting) کے لیے ایک اسٹیٹ متغیر استعمال کر رہے ہیں جسے صارف سیٹ کرنا چاہتا ہے۔

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 ہے، یا کوئی ایسی قدر ہے جس کا جائزہ false کے طور پر لیا جاتا ہے، جیسے کہ undefined، 0، یا ایک خالی اسٹرنگ، تو مجموعی طور پر ایکسپریشن false ہے۔ کسی بھی دوسری قدر کے لیے، یہ true ہے۔ یہ اقدار کو بولینز (booleans) میں تبدیل کرنے کا ایک طریقہ ہے، کیونکہ اگر کوئی 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) کا استعمال کرتے ہوئے۔

قدم 2 میں ممکنہ طور پر قابل ذکر وقت لگ سکتا ہے، جس کے دوران صارفین یہ سوچ سکتے ہیں کہ آیا ان کی کمانڈ یوزر انٹرفیس کو موصول ہوئی ہے یا نہیں اور ان سے ابھی تک ٹرانزیکشن پر دستخط کرنے کا کیوں نہیں کہا جا رہا ہے۔ اس سے ایک خراب یوزر ایکسپیرینس (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 component کو دوبارہ رینڈر کرنے کا سبب بنتا ہے۔ اس کا مطلب ہے کہ:

  • ہمیں نئی گریٹنگ کی قدر کو برقرار رکھنے کے لیے 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}

component کو ایکسپورٹ کریں۔

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)۔

صفحہ کی آخری اپ ڈیٹ: ۳ مارچ، ۲۰۲۶

کیا یہ ٹیوٹوریل مددگار تھا؟