اپنے کنٹریکٹ کے لیے یوزر انٹرفیس بنانا
آپ کو ایتھیریم ایکو سسٹم میں ایک ایسی خصوصیت مل گئی ہے جس کی ہمیں ضرورت ہے۔ آپ نے اسے نافذ کرنے کے لیے سمارٹ کنٹریکٹس لکھے، اور شاید کچھ متعلقہ کوڈ بھی جو آف چین چلتا ہے۔ یہ بہت اچھا ہے! بدقسمتی سے، یوزر انٹرفیس کے بغیر آپ کے پاس کوئی صارف نہیں ہوگا، اور آخری بار جب آپ نے کوئی ویب سائٹ لکھی تھی تو لوگ ڈائل اپ موڈیم استعمال کرتے تھے اور 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)۔ ان سائٹس کے کیے گئے بہترین کام کو دہرانے کے بجائے، میں یہ فرض کروں گا کہ آپ عملی طور پر سیکھنے کو ترجیح دیتے ہیں اور ایک ایسی ایپلی کیشن سے شروعات کریں گے جس کے ساتھ آپ کھیل سکیں۔ آپ کو چیزیں مکمل کرنے کے لیے اب بھی تھیوری کی ضرورت ہے، اور ہم اس تک پہنچیں گے - ہم بس سورس فائل در سورس فائل جائیں گے، اور جیسے جیسے چیزیں سامنے آئیں گی ان پر تبادلہ خیال کریں گے۔
انسٹالیشن
-
ایپلی کیشن Sepolia ٹیسٹ نیٹ ورک استعمال کرتی ہے۔ اگر ضروری ہو تو، Sepolia ٹیسٹ ETH حاصل کریں اور اپنے والیٹ میں Sepolia شامل کریں (opens in a new tab)۔
-
GitHub ریپوزٹری کو کلون کریں اور ضروری پیکجز انسٹال کریں۔
git clone https://github.com/qbzzt/260301-modern-ui-web3.git cd 260301-modern-ui-web3 npm install -
ایپلی کیشن مفت رسائی کے پوائنٹس استعمال کرتی ہے، جن کی کارکردگی کی حدود ہوتی ہیں۔ اگر آپ نوڈ بطور سروس فراہم کنندہ استعمال کرنا چاہتے ہیں، تو
src/wagmi.tsمیں URLs کو تبدیل کریں۔ -
ایپلی کیشن شروع کریں۔
npm run dev -
ایپلی کیشن کے دکھائے گئے URL پر براؤز کریں۔ زیادہ تر معاملات میں، یہ http://localhost:5173/ (opens in a new tab) ہوتا ہے۔
-
آپ کنٹریکٹ کا سورس کوڈ، جو Hardhat کے Greeter کا ایک ترمیم شدہ ورژن ہے، بلاک چین ایکسپلورر پر (opens in a new tab) دیکھ سکتے ہیں۔
فائلوں کا جائزہ
index.html
یہ فائل ایک معیاری HTML بوائلر پلیٹ ہے سوائے اس لائن کے، جو سکرپٹ فائل کو امپورٹ کرتی ہے۔
<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 میں مرتب (compile) کیا جاتا ہے، لہذا ہم اسے کلائنٹ سائیڈ پر استعمال کر سکتے ہیں۔
اس فائل کی زیادہ تر وضاحت اس صورت میں کی گئی ہے کہ آپ دلچسپی رکھتے ہوں۔ عام طور پر آپ اس فائل میں ترمیم نہیں کرتے، بلکہ src/App.tsx اور ان فائلوں میں کرتے ہیں جنہیں یہ امپورٹ کرتی ہے۔
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { WagmiProvider } from 'wagmi'
ہمیں جس لائبریری کوڈ کی ضرورت ہے اسے امپورٹ کریں۔
import App from './App.tsx'
اس React جزو کو امپورٹ کریں جو ایپلی کیشن کو نافذ کرتا ہے (نیچے دیکھیں)۔
import { config } from './wagmi.ts'
Wagmi (opens in a new tab) کنفیگریشن امپورٹ کریں، جس میں بلاک چین کنفیگریشن شامل ہے۔
const queryClient = new QueryClient()
React Query کے (opens in a new tab) کیشے مینیجر کی ایک نئی مثال (instance) بناتا ہے۔ یہ آبجیکٹ درج ذیل کو محفوظ کرے گا:
- کیش کی گئی RPC کالز
- کنٹریکٹ ریڈز
- بیک گراؤنڈ ری فیچنگ کی حالت
ہمیں کیشے مینیجر کی ضرورت ہے کیونکہ Wagmi کا v3 اندرونی طور پر React Query استعمال کرتا ہے۔
ReactDOM.createRoot(document.getElementById('root')!).render(
روٹ React جزو بنائیں۔ render کا پیرامیٹر JSX (opens in a new tab) ہے، جو ایک ایکسٹینشن زبان ہے جو HTML اور JavaScript/TypeScript دونوں کا استعمال کرتی ہے۔ یہاں فجائیہ نشان (exclamation point) TypeScript جزو کو بتاتا ہے: "آپ نہیں جانتے کہ document.getElementById('root')، ReactDOM.createRoot کے لیے ایک درست پیرامیٹر ہوگا، لیکن فکر نہ کریں - میں ڈویلپر ہوں اور میں آپ کو بتا رہا ہوں کہ ایسا ہی ہوگا"۔
<React.StrictMode>
ایپلی کیشن ایک React.StrictMode جزو (opens in a new tab) کے اندر جا رہی ہے۔ یہ جزو React لائبریری کو اضافی ڈیبگنگ چیکس داخل کرنے کا کہتا ہے، جو ڈیولپمنٹ کے دوران مفید ہے۔
<WagmiProvider config={config}>
ایپلی کیشن ایک WagmiProvider جزو (opens in a new tab) کے اندر بھی ہے۔ Wagmi (جو ہم بنانے جا رہے ہیں) لائبریری (opens in a new tab) ایک ایتھیریم غیر مرکزی ایپلی کیشن (dapp) لکھنے کے لیے React UI کی تعریفوں کو Viem لائبریری (opens in a new tab) کے ساتھ جوڑتی ہے۔
<QueryClientProvider client={queryClient}>
اور آخر میں، ایک React Query پرووائیڈر شامل کریں تاکہ کوئی بھی ایپلی کیشن جزو کیش کی گئی کیوریز استعمال کر سکے۔
<App />
اب ہمارے پاس ایپلی کیشن کے لیے جزو ہو سکتا ہے، جو دراصل UI کو نافذ کرتا ہے۔ جزو کے آخر میں /> React کو بتاتا ہے کہ اس جزو کے اندر کوئی تعریفیں نہیں ہیں، جیسا کہ XML معیار کے مطابق ہے۔
</QueryClientProvider>
</WagmiProvider>
</React.StrictMode>,
)
یقیناً، ہمیں دوسرے اجزاء کو بند کرنا ہوگا۔
src/App.tsx
import {
useConnect,
useConnection,
useDisconnect,
useSwitchChain
} from 'wagmi'
import { useEffect } from 'react'
import { Greeter } from './Greeter'
ہمیں جن لائبریریوں کی ضرورت ہے انہیں امپورٹ کریں، اور ساتھ ہی Greeter جزو کو بھی۔
const SEPOLIA_CHAIN_ID = 11155111
Sepolia چین کی ID۔
function App() {
React جزو بنانے کا یہ معیاری طریقہ ہے: ایک فنکشن کی وضاحت کریں جسے جب بھی رینڈر کرنے کی ضرورت ہو کال کیا جائے۔ اس فنکشن میں عام طور پر TypeScript یا JavaScript کوڈ ہوتا ہے، جس کے بعد ایک return سٹیٹمنٹ ہوتی ہے جو JSX کوڈ واپس کرتی ہے۔
const connection = useConnection()
موجودہ کنکشن سے متعلق معلومات حاصل کرنے کے لیے useConnection (opens in a new tab) کا استعمال کریں، جیسے کہ پتہ اور chainId۔
روایت کے مطابق، React میں use... کہلانے والے فنکشنز ہکس (hooks) (opens in a new tab) ہوتے ہیں۔ یہ فنکشنز نہ صرف جزو کو ڈیٹا واپس کرتے ہیں؛ بلکہ وہ یہ بھی یقینی بناتے ہیں کہ جب وہ ڈیٹا تبدیل ہو تو اسے دوبارہ رینڈر کیا جائے (جزو کا فنکشن دوبارہ چلایا جاتا ہے، اور اس کا آؤٹ پٹ HTML میں پچھلے والے کی جگہ لے لیتا ہے)۔
const { connectors, connect, status, error } = useConnect()
والیٹ کنکشن کے بارے میں معلومات حاصل کرنے کے لیے useConnect (opens in a new tab) کا استعمال کریں۔
const { disconnect } = useDisconnect()
یہ ہک (opens in a new tab) ہمیں والیٹ سے منقطع ہونے کا فنکشن دیتا ہے۔
const { switchChain } = useSwitchChain()
یہ ہک (opens in a new tab) ہمیں چینز تبدیل کرنے کی سہولت دیتا ہے۔
useEffect(() => {
React ہک useEffect (opens in a new tab) آپ کو کسی بیرونی سسٹم کو ہم آہنگ کرنے کے لیے متغیر (variable) کی قدر تبدیل ہونے پر فنکشن چلانے کی سہولت دیتا ہے۔
if (connection.status === 'connected' &&
connection.chainId !== SEPOLIA_CHAIN_ID
) {
switchChain({ chainId: SEPOLIA_CHAIN_ID })
}
اگر ہم جڑے ہوئے ہیں، لیکن Sepolia بلاک چین سے نہیں، تو Sepolia پر سوئچ کریں۔
}, [connection.status, connection.chainId])
جب بھی کنکشن کی حالت یا کنکشن کی chainId تبدیل ہو تو فنکشن کو دوبارہ چلائیں۔
return (
<>
React جزو کا JSX لازمی طور پر ایک واحد HTML جزو واپس کرے۔ جب ہمارے پاس متعدد اجزاء ہوں اور ہمیں ان سب کو لپیٹنے کے لیے کسی کنٹینر کی ضرورت نہ ہو، تو ہم انہیں ایک واحد جزو میں ملانے کے لیے ایک خالی جزو (<> ... </>) استعمال کرتے ہیں۔
<h2>Connection</h2>
<div>
status: {connection.status}
<br />
addresses: {JSON.stringify(connection.addresses)}
<br />
chainId: {connection.chainId}
</div>
موجودہ کنکشن کے بارے میں معلومات فراہم کریں۔ JSX کے اندر، {<expression>} کا مطلب ہے کہ ایکسپریشن کا JavaScript کے طور پر جائزہ لیا جائے۔
{connection.status === 'connected' && (
سنٹیکس {<condition> && <value>} means "if the condition is true, evaluate to the value; if it isn't, evaluate to false`"۔
JSX کے اندر if سٹیٹمنٹس رکھنے کا یہ معیاری طریقہ ہے۔
<div>
<Greeter />
<hr />
JSX XML معیار کی پیروی کرتا ہے، جو HTML سے زیادہ سخت ہے۔ اگر کسی ٹیگ کا متعلقہ اختتامی ٹیگ نہیں ہے، تو اسے ختم کرنے کے لیے اس کے آخر میں سلیش (/) لازمی ہونا چاہیے۔
یہاں ہمارے پاس ایسے دو ٹیگز ہیں، <Greeter /> (جس میں دراصل وہ HTML کوڈ ہوتا ہے جو کنٹریکٹ سے بات کرتا ہے) اور افقی لائن کے لیے <hr /> (opens in a new tab)۔
<button type="button" onClick={disconnect}>
Disconnect
</button>
</div>
)}
اگر صارف اس بٹن پر کلک کرتا ہے، تو disconnect فنکشن کو کال کریں۔
{connection.status !== 'connected' && (
اگر ہم جڑے ہوئے نہیں ہیں، تو والیٹ سے جڑنے کے لیے ضروری آپشنز دکھائیں۔
<div>
<h2>Connect</h2>
{connectors.map((connector) => (
connectors میں ہمارے پاس کنیکٹرز کی ایک فہرست ہے۔ ہم اسے دکھانے کے لیے JSX بٹنوں کی فہرست میں تبدیل کرنے کے لیے map (opens in a new tab) کا استعمال کرتے ہیں۔
<button
key={connector.uid}
JSX میں "بہن بھائی (sibling)" ٹیگز (وہ ٹیگز جو ایک ہی پیرنٹ سے نکلتے ہیں) کے لیے مختلف شناخت کنندگان (identifiers) کا ہونا ضروری ہے۔
onClick={() => connect({ connector })}
type="button"
>
{connector.name}
</button>
))}
کنیکٹر بٹنز۔
<div>{status}</div>
<div>{error?.message}</div>
</div>
)}
اضافی معلومات فراہم کریں۔ ایکسپریشن سنٹیکس <variable>?.<field> JavaScript کو بتاتا ہے کہ اگر متغیر کی تعریف کی گئی ہے، تو اس فیلڈ کا جائزہ لیں۔ اگر متغیر کی تعریف نہیں کی گئی ہے، تو اس ایکسپریشن کا نتیجہ undefined نکلتا ہے۔
ایکسپریشن error.message، جب کوئی خرابی نہ ہو، تو ایک ایکسیپشن (exception) پیدا کرے گا۔ error?.message کا استعمال ہمیں اس مسئلے سے بچنے دیتا ہے۔
src/Greeter.tsx
اس فائل میں زیادہ تر UI فعالیت شامل ہے۔ اس میں وہ تعریفیں شامل ہیں جو عام طور پر متعدد فائلوں میں ہوتی ہیں، لیکن چونکہ یہ ایک ٹیوٹوریل ہے، اس لیے پروگرام کو کارکردگی یا دیکھ بھال میں آسانی کے بجائے پہلی بار سمجھنے میں آسان ہونے کے لیے بہتر بنایا گیا ہے۔
import {
useState,
useEffect,
} from 'react'
import { useChainId,
useAccount,
useReadContract,
useWriteContract,
useWatchContractEvent,
useSimulateContract
} from 'wagmi'
ہم ان لائبریری فنکشنز کا استعمال کرتے ہیں۔ ایک بار پھر، ان کی وضاحت نیچے کی گئی ہے جہاں وہ استعمال ہوتے ہیں۔
import { AddressType } from 'abitype'
abitype لائبریری (opens in a new tab) ہمیں مختلف ایتھیریم ڈیٹا ٹائپس کے لیے TypeScript تعریفیں فراہم کرتی ہے، جیسے کہ AddressType (opens in a new tab)۔
let greeterABI = [
{ "type": "function", "name": "greet", ... },
{ "type": "function", "name": "setGreeting", ... },
{ "type": "event", "name": "SetGreeting", ... },
] as const // greeterABI
Greeter کنٹریکٹ کے لیے ABI۔
اگر آپ کنٹریکٹس اور UI ایک ہی وقت میں تیار کر رہے ہیں، تو آپ عام طور پر انہیں ایک ہی ریپوزٹری میں رکھیں گے اور Solidity کمپائلر کے ذریعے تیار کردہ ABI کو اپنی ایپلی کیشن میں ایک فائل کے طور پر استعمال کریں گے۔ تاہم، یہاں یہ ضروری نہیں ہے کیونکہ کنٹریکٹ پہلے ہی تیار ہو چکا ہے اور تبدیل نہیں ہوگا۔
ہم TypeScript کو یہ بتانے کے لیے as const (opens in a new tab) کا استعمال کرتے ہیں کہ یہ ایک حقیقی مستقل (constant) ہے۔ عام طور پر، جب آپ JavaScript میں const x = {"a": 1} کی وضاحت کرتے ہیں، تو آپ x میں قدر تبدیل کر سکتے ہیں، آپ بس اسے تفویض (assign) نہیں کر سکتے۔
type AddressPerBlockchainType = {
[key: number]: AddressType
}
TypeScript سختی سے ٹائپ شدہ (strongly typed) ہے۔ ہم اس تعریف کا استعمال اس پتے کی وضاحت کرنے کے لیے کرتے ہیں جہاں Greeter کنٹریکٹ مختلف چینز پر تعینات کیا گیا ہے۔ کلید ایک نمبر (chainId) ہے، اور قدر ایک AddressType (ایک پتہ) ہے۔
const contractAddrs : AddressPerBlockchainType = {
// Sepolia
11155111: '0xC87506C66c7896366b9E988FE0aA5B6dDE77CFfA'
}
Sepolia (opens in a new tab) پر کنٹریکٹ کا پتہ۔
Timer جزو
Timer جزو کسی دیے گئے وقت سے سیکنڈز کی تعداد دکھاتا ہے۔ یہ استعمال کی سہولت کے مقاصد کے لیے اہم ہے۔ جب صارفین کچھ کرتے ہیں، تو وہ فوری ردعمل کی توقع کرتے ہیں۔ بلاک چینز میں، یہ اکثر ناممکن ہوتا ہے کیونکہ جب تک کسی ٹرانزیکشن کو بلاک میں نہیں رکھا جاتا تب تک کچھ نہیں ہوتا۔ اس کا ایک حل یہ دکھانا ہے کہ صارف کو کارروائی کیے کتنا وقت گزر چکا ہے، تاکہ صارف فیصلہ کر سکے کہ آیا درکار وقت معقول ہے۔
type TimerProps = {
lastUpdate: Date
}
Timer جزو ایک پیرامیٹر لیتا ہے، lastUpdate، جو آخری کارروائی کا وقت ہے۔
const Timer = ({ lastUpdate }: TimerProps) => {
const [_, setNow] = useState(new Date())
جزو کے صحیح طریقے سے کام کرنے کے لیے ہمیں حالت (جزو سے منسلک ایک متغیر) کی ضرورت ہے اور اسے اپ ڈیٹ کرنا ہوگا۔ لیکن ہمیں اسے کبھی پڑھنے کی ضرورت نہیں ہوتی، اس لیے متغیر بنانے کی زحمت نہ کریں۔
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000)
return () => clearInterval(id)
}, [])
setInterval (opens in a new tab) فنکشن ہمیں وقتاً فوقتاً چلنے کے لیے ایک فنکشن شیڈول کرنے کی سہولت دیتا ہے۔ اس صورت میں، ہر سیکنڈ۔ فنکشن حالت کو اپ ڈیٹ کرنے کے لیے setNow کو کال کرتا ہے، تاکہ Timer جزو دوبارہ رینڈر ہو جائے۔ ہم اسے ایک خالی انحصاری فہرست (dependency list) کے ساتھ useEffect (opens in a new tab) کے اندر لپیٹتے ہیں تاکہ یہ جزو کے ہر بار رینڈر ہونے کے بجائے صرف ایک بار ہو۔
const secondsSinceUpdate = Math.floor(
(Date.now() - lastUpdate.getTime()) / 1000
)
return (
<span>{secondsSinceUpdate} seconds ago</span>
)
}
آخری اپ ڈیٹ کے بعد سے سیکنڈز کی تعداد کا حساب لگائیں اور اسے واپس کریں۔
Greeter جزو
const Greeter = () => {
آخر کار، ہم جزو کی وضاحت کرنے پر آتے ہیں۔
const chainId = useChainId()
const account = useAccount()
اس چین اور اکاؤنٹ کے بارے میں معلومات جو ہم استعمال کر رہے ہیں، Wagmi (opens in a new tab) کے بشکریہ۔ چونکہ یہ ایک ہک (use...) ہے، اس لیے جب بھی یہ معلومات تبدیل ہوتی ہیں تو جزو دوبارہ رینڈر ہوتا ہے۔
const greeterAddr = chainId && contractAddrs[chainId]
Greeter کنٹریکٹ کا پتہ، جو undefined ہوتا ہے اگر ہمارے پاس چین کی معلومات نہ ہوں، یا ہم اس کنٹریکٹ کے بغیر کسی چین پر ہوں۔
const readResults = useReadContract({
address: greeterAddr,
abi: greeterABI,
functionName: "greet", // کوئی آرگومنٹس نہیں
})
useReadContract ہک (opens in a new tab) کنٹریکٹ (opens in a new tab) کے greet فنکشن کو کال کرتا ہے۔
const [ currentGreeting, setCurrentGreeting ] =
useState("Please wait while we fetch the greeting from the blockchain...")
const [ newGreeting, setNewGreeting ] = useState("")
React کا useState ہک (opens in a new tab) ہمیں ایک حالت متغیر (state variable) کی وضاحت کرنے دیتا ہے، جس کی قدر جزو کی ایک رینڈرنگ سے دوسری تک برقرار رہتی ہے۔ ابتدائی قدر پیرامیٹر ہے، اس صورت میں خالی سٹرنگ۔
useState ہک دو اقدار کے ساتھ ایک فہرست واپس کرتا ہے:
- حالت متغیر کی موجودہ قدر۔
- ضرورت پڑنے پر حالت متغیر میں ترمیم کرنے کا فنکشن۔ چونکہ یہ ایک ہک ہے، جب بھی اسے کال کیا جاتا ہے تو جزو دوبارہ رینڈر ہوتا ہے۔
اس صورت میں، ہم اس نئی مبارکباد (greeting) کے لیے ایک حالت متغیر استعمال کر رہے ہیں جسے صارف سیٹ کرنا چاہتا ہے۔
const [ lastSetterAddress, setLastSetterAddress ] = useState("")
اگر متعدد صارفین ایک ہی وقت میں ایک ہی کنٹریکٹ استعمال کر رہے ہیں، تو وہ ایک دوسرے کی مبارکباد کو اوور رائٹ کر سکتے ہیں۔ یہ صارفین کو ایسا لگے گا جیسے ایپلی کیشن خراب ہو رہی ہے۔ اگر ایپلی کیشن یہ دکھاتی ہے کہ آخری بار مبارکباد کس نے سیٹ کی تھی، تو صارف کو معلوم ہو جائے گا کہ یہ کوئی اور تھا اور ایپلی کیشن صحیح طریقے سے کام کر رہی ہے۔
const [ status, setStatus ] = useState("")
const [ statusTime, setStatusTime ] = useState(new Date())
صارفین یہ دیکھنا پسند کرتے ہیں کہ ان کے اعمال کا فوری اثر ہو۔ تاہم، بلاک چین پر، ایسا نہیں ہوتا ہے۔ یہ حالت متغیرات ہمیں کم از کم صارفین کو کچھ دکھانے کی سہولت دیتے ہیں تاکہ انہیں معلوم ہو کہ ان کی کارروائی جاری ہے۔
useEffect(() => {
if (readResults.data) {
setCurrentGreeting(readResults.data)
setStatus("Greeting fetched from blockchain")
}
}, [readResults.data])
اگر اوپر موجود readResults ڈیٹا کو تبدیل کرتا ہے اور یہ کسی غلط قدر (مثال کے طور پر، undefined) پر سیٹ نہیں ہے، تو موجودہ مبارکباد کو بلاک چین سے پڑھی گئی مبارکباد میں اپ ڈیٹ کریں۔ نیز، سٹیٹس کو اپ ڈیٹ کریں۔
useWatchContractEvent({
address: greeterAddr,
abi: greeterABI,
eventName: 'SetGreeting',
chainId,
SetGreeting ایونٹس کو سنیں۔
enabled: !!greeterAddr,
!!<value> کا مطلب ہے کہ اگر قدر false ہے، یا کوئی ایسی قدر جس کا نتیجہ غلط نکلتا ہے، جیسے کہ undefined، 0، یا ایک خالی سٹرنگ، تو مجموعی طور پر ایکسپریشن false ہے۔ کسی بھی دوسری قدر کے لیے، یہ true ہے۔ یہ اقدار کو بولینز (booleans) میں تبدیل کرنے کا ایک طریقہ ہے، کیونکہ اگر کوئی greeterAddr نہیں ہے، تو ہم ایونٹس کو سننا نہیں چاہتے۔
onLogs: logs => {
const greetingFromContract = logs[0].args.greeting
setCurrentGreeting(greetingFromContract)
setLastSetterAddress(logs[0].args.sender)
updateStatus("Greeting updated by event")
},
})
جب ہم لاگز دیکھتے ہیں (جو اس وقت ہوتا ہے جب ہم کوئی نیا ایونٹ دیکھتے ہیں)، تو اس کا مطلب ہے کہ مبارکباد میں ترمیم کی گئی ہے۔ اس صورت میں، ہم currentGreeting اور lastSetterAddress کو نئی اقدار میں اپ ڈیٹ کر سکتے ہیں۔ نیز، ہم سٹیٹس ڈسپلے کو اپ ڈیٹ کرنا چاہتے ہیں۔
const updateStatus = (newStatus: string) => {
setStatus(newStatus)
setStatusTime(new Date())
}
جب ہم سٹیٹس کو اپ ڈیٹ کرتے ہیں تو ہم دو کام کرنا چاہتے ہیں:
- سٹیٹس سٹرنگ (
status) کو اپ ڈیٹ کریں - آخری سٹیٹس اپ ڈیٹ کے وقت (
statusTime) کو موجودہ وقت پر اپ ڈیٹ کریں۔
const greetingChange = (evt) =>
setNewGreeting(evt.target.value)
یہ نئی مبارکباد کی ان پٹ فیلڈ میں تبدیلیوں کے لیے ایونٹ ہینڈلر ہے۔ ہم evt پیرامیٹر کی قسم بتا سکتے ہیں، لیکن TypeScript ایک ٹائپ آپشنل زبان ہے۔ چونکہ یہ فنکشن صرف ایک بار کال کیا جاتا ہے، ایک HTML ایونٹ ہینڈلر میں، مجھے نہیں لگتا کہ یہ ضروری ہے۔
const { writeContractAsync } = useWriteContract()
کنٹریکٹ میں لکھنے کا فنکشن۔ یہ writeContracts (opens in a new tab) سے ملتا جلتا ہے، لیکن بہتر سٹیٹس اپ ڈیٹس کو فعال کرتا ہے۔
const simulation = useSimulateContract({
address: greeterAddr,
abi: greeterABI,
functionName: 'setGreeting',
args: [newGreeting],
account: account.address
})
کلائنٹ کے نقطہ نظر سے بلاک چین ٹرانزیکشن جمع کرانے کا عمل یہ ہے:
eth_estimateGas(opens in a new tab) کا استعمال کرتے ہوئے بلاک چین میں ایک نوڈ کو ٹرانزیکشن بھیجیں۔- نوڈ سے جواب کا انتظار کریں۔
- جب جواب موصول ہو جائے، تو صارف سے والیٹ کے ذریعے ٹرانزیکشن پر دستخط کرنے کو کہیں۔ یہ قدم نوڈ کا جواب موصول ہونے کے بعد لازمی ہونا چاہیے کیونکہ صارف کو دستخط کرنے سے پہلے ٹرانزیکشن کی گیس کی قیمت دکھائی جاتی ہے۔
- صارف کی منظوری کا انتظار کریں۔
- ٹرانزیکشن دوبارہ بھیجیں، اس بار
eth_sendRawTransaction(opens in a new tab) کا استعمال کرتے ہوئے۔
مرحلہ 2 میں ممکنہ طور پر قابل ذکر وقت لگ سکتا ہے، جس کے دوران صارفین سوچ سکتے ہیں کہ آیا ان کی کمانڈ یوزر انٹرفیس کو موصول ہوئی تھی اور ان سے ابھی تک ٹرانزیکشن پر دستخط کرنے کو کیوں نہیں کہا جا رہا ہے۔ اس سے صارف کا تجربہ (UX) خراب ہوتا ہے۔
اس کا ایک حل یہ ہے کہ جب بھی کوئی پیرامیٹر تبدیل ہو تو eth_estimateGas بھیجا جائے۔ پھر، جب صارف واقعی ٹرانزیکشن بھیجنا چاہتا ہے (اس صورت میں Update greeting دبا کر)، تو گیس کی قیمت معلوم ہوتی ہے، اور صارف فوری طور پر والیٹ کا صفحہ دیکھ سکتا ہے۔
return (
اب ہم آخر کار واپس کرنے کے لیے اصل HTML بنا سکتے ہیں۔
<>
<h2>Greeter</h2>
{currentGreeting}
موجودہ مبارکباد دکھائیں۔
{lastSetterAddress && (
<p>Last updated by {
lastSetterAddress === account.address ? "you" : lastSetterAddress
}</p>
)}
اگر ہم جانتے ہیں کہ آخری بار مبارکباد کس نے سیٹ کی تھی، تو وہ معلومات دکھائیں۔ Greeter اس معلومات کا ٹریک نہیں رکھتا، اور ہم SetGreeting ایونٹس کے لیے پیچھے مڑ کر نہیں دیکھنا چاہتے، اس لیے ہم اسے صرف ایک بار حاصل کرتے ہیں جب ہمارے چلنے کے دوران مبارکباد تبدیل ہوتی ہے۔
<hr />
<input type="text"
value={newGreeting}
onChange={greetingChange}
/>
<br />
یہ ان پٹ ٹیکسٹ فیلڈ ہے جہاں صارف نئی مبارکباد سیٹ کر سکتا ہے۔ جب بھی صارف کوئی کلید (key) دباتا ہے، ہم greetingChange کو کال کرتے ہیں، جو setNewGreeting کو کال کرتا ہے۔ چونکہ setNewGreeting، useState سے آتا ہے، اس لیے یہ Greeter جزو کو دوبارہ رینڈر کرنے کا سبب بنتا ہے۔ اس کا مطلب یہ ہے کہ:
- ہمیں نئی مبارکباد کی قدر کو برقرار رکھنے کے لیے
valueکی وضاحت کرنے کی ضرورت ہے، کیونکہ بصورت دیگر یہ واپس ڈیفالٹ، یعنی خالی سٹرنگ میں تبدیل ہو جائے گی۔ - جب بھی
newGreetingتبدیل ہوتا ہے توsimulationبھی اپ ڈیٹ ہوتا ہے، جس کا مطلب ہے کہ ہمیں درست مبارکباد کے ساتھ ایک نقلی (simulation) ملے گی۔ یہ متعلقہ ہو سکتا ہے کیونکہ گیس کی قیمت کال ڈیٹا کے سائز پر منحصر ہوتی ہے، جو سٹرنگ کی لمبائی پر منحصر ہوتا ہے۔
<button disabled={!simulation.data}
بٹن کو صرف اس وقت فعال کریں جب ہمارے پاس وہ معلومات ہوں جو ہمیں ٹرانزیکشن بھیجنے کے لیے درکار ہیں۔
onClick={async () => {
updateStatus("Please confirm in wallet...")
سٹیٹس کو اپ ڈیٹ کریں۔ اس مقام پر، صارف کو والیٹ میں تصدیق کرنے کی ضرورت ہے۔
await writeContractAsync(simulation.data.request)
updateStatus("Transaction sent, waiting for greeting to change...")
}}
>
Update greeting
</button>
writeContractAsync صرف اس وقت واپس آتا ہے جب ٹرانزیکشن دراصل بھیج دی جاتی ہے۔ اس سے ہم صارف کو دکھا سکتے ہیں کہ ٹرانزیکشن کو بلاک چین میں شامل ہونے کے لیے کتنا انتظار کرنا پڑا ہے۔
<h4>Status: {status}</h4>
<p>Updated <Timer lastUpdate={statusTime} /> </p>
</>
)
}
سٹیٹس دکھائیں اور یہ بھی کہ اسے اپ ڈیٹ ہوئے کتنا وقت گزر چکا ہے۔
export {Greeter}
جزو کو ایکسپورٹ کریں۔
src/wagmi.ts
آخر میں، Wagmi سے متعلق مختلف تعریفیں src/wagmi.ts میں ہیں۔ میں یہاں ہر چیز کی وضاحت نہیں کرنے جا رہا ہوں، کیونکہ اس کا زیادہ تر حصہ بوائلر پلیٹ ہے جسے آپ کو تبدیل کرنے کی ضرورت نہیں پڑے گی۔
import { http, webSocket, createConfig, fallback } from 'wagmi'
import { sepolia } from 'wagmi/chains'
import { injected } from 'wagmi/connectors'
export const config = createConfig({
chains: [sepolia],
Wagmi کنفیگریشن میں اس ایپلی کیشن کے ذریعے سپورٹ کردہ چینز شامل ہیں۔ آپ دستیاب چینز کی فہرست (opens in a new tab) دیکھ سکتے ہیں۔
connectors: [
injected(),
],
یہ کنیکٹر (opens in a new tab) ہمیں براؤزر میں انسٹال کردہ والیٹ سے بات کرنے کی سہولت دیتا ہے۔
transports: {
[sepolia.id]: http()
Viem کے ساتھ آنے والا ڈیفالٹ HTTP اینڈ پوائنٹ کافی اچھا ہے۔ اگر ہم ایک مختلف URL چاہتے ہیں، تو ہم http("https:// hostname ") یا webSocket("wss:// hostname ") استعمال کر سکتے ہیں۔
},
multiInjectedProviderDiscovery: false,
})
ایک اور بلاک چین شامل کرنا
آج کل بہت سے L2 سکیلنگ سلوشنز (opens in a new tab) موجود ہیں، اور آپ شاید کچھ ایسے سلوشنز کو سپورٹ کرنا چاہیں جنہیں Viem ابھی تک سپورٹ نہیں کرتا۔ ایسا کرنے کے لیے، آپ src/wagmi.ts میں ترمیم کرتے ہیں۔ یہ ہدایات بتاتی ہیں کہ Optimism Sepolia (opens in a new tab) کو کیسے شامل کیا جائے۔
-
src/wagmi.tsمیں ترمیم کریںA. Viem سے
defineChainٹائپ امپورٹ کریں۔import { defineChain } from 'viem'B. نیٹ ورک کی تعریف شامل کریں۔ آپ کو واقعی Optimism Sepolia کے لیے ایسا کرنے کی ضرورت نہیں ہے، یہ پہلے سے ہی
viemمیں موجود ہے (opens in a new tab)، لیکن اس طرح آپ سیکھتے ہیں کہ ایک ایسی بلاک چین کو کیسے شامل کیا جائے جوviemمیں نہیں ہے۔const optimismSepolia = defineChain({ id: 11_155_420, name: 'OP Sepolia', nativeCurrency: { name: 'Sepolia Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: ['https://sepolia.optimism.io'], webSocket: ['wss://optimism-sepolia.drpc.org'], }, }, blockExplorers: { default: { name: 'Blockscout', url: 'https://optimism-sepolia.blockscout.com', apiUrl: 'https://optimism-sepolia.blockscout.com/api', } }, })C. نئی چین کو
createConfigکال میں شامل کریں۔export const config = createConfig({ chains: [sepolia, optimismSepolia], connectors: [ injected(), ], transports: { [optimismSepolia.id]: http(), [sepolia.id]: http() }, multiInjectedProviderDiscovery: false, }) -
Sepolia پر خودکار سوئچ کو کمنٹ آؤٹ کرنے کے لیے
src/App.tsxمیں ترمیم کریں۔ پروڈکشن سسٹم پر، آپ شاید ان تمام بلاک چینز کے لنکس کے ساتھ بٹن دکھائیں گے جنہیں آپ سپورٹ کرتے ہیں۔/* useEffect(() => { if (connection.status === 'connected' && connection.chainId !== SEPOLIA_CHAIN_ID ) { switchChain({ chainId: SEPOLIA_CHAIN_ID }) } }, [connection.status, connection.chainId]) */ -
یہ یقینی بنانے کے لیے
src/Greeter.tsxمیں ترمیم کریں کہ ایپلی کیشن نئے نیٹ ورک پر آپ کے کنٹریکٹس کا پتہ جانتی ہے۔const contractAddrs: AddressPerBlockchainType = { // Optimism Sepolia 11155420: "0x4dd85791923E9294E934271522f63875EAe5806f", // Sepolia 11155111: "0x7143d5c190F048C8d19fe325b748b081903E3BF0", } -
اپنے براؤزر میں۔
A. ChainList (opens in a new tab) پر براؤز کریں اور چین کو اپنے والیٹ میں شامل کرنے کے لیے ٹیبل کے دائیں جانب موجود بٹنوں میں سے کسی ایک پر کلک کریں۔
B. ایپلی کیشن میں، بلاک چین کو تبدیل کرنے کے لیے Disconnect کریں اور پھر دوبارہ جڑیں۔ اسے سنبھالنے کے اور بھی اچھے طریقے ہیں، لیکن ان کے لیے ایپلی کیشن میں تبدیلیوں کی ضرورت ہوگی۔
نتیجہ
یقیناً، آپ کو واقعی Greeter کے لیے یوزر انٹرفیس فراہم کرنے کی پرواہ نہیں ہے۔ آپ اپنے کنٹریکٹس کے لیے یوزر انٹرفیس بنانا چاہتے ہیں۔ اپنی خود کی ایپلی کیشن بنانے کے لیے، ان مراحل پر عمل کریں:
-
Wagmi ایپلی کیشن بنانے کی وضاحت کریں۔
npm create wagmi -
آگے بڑھنے کے لیے
yٹائپ کریں۔ -
ایپلی کیشن کا نام رکھیں۔
-
React فریم ورک منتخب کریں۔
-
Vite ویریئنٹ منتخب کریں۔
اب جائیں اور اپنے کنٹریکٹس کو پوری دنیا کے لیے قابل استعمال بنائیں۔