بناء واجهة مستخدم لعقدك
لقد وجدت ميزة نحتاجها في نظام إيثيريوم البيئي. لقد كتبت العقود الذكية لتنفيذها، وربما حتى بعض التعليمات البرمجية ذات الصلة التي تعمل خارج السلسلة. هذا رائع! لسوء الحظ، بدون واجهة مستخدم لن يكون لديك أي مستخدمين، وآخر مرة كتبت فيها موقعًا إلكترونيًا كان الناس يستخدمون أجهزة المودم للاتصال الهاتفي وكان JavaScript جديدًا.
هذه المقالة لك. أفترض أنك تعرف البرمجة، وربما القليل من JavaScript وHTML، لكن مهاراتك في واجهة المستخدم صدئة وقديمة. معًا سنستعرض تطبيقًا حديثًا بسيطًا لترى كيف يتم ذلك هذه الأيام.
لماذا هذا مهم
نظريًا، يمكنك فقط جعل الأشخاص يستخدمون Etherscan (opens in a new tab) أو Blockscout (opens in a new tab) للتفاعل مع عقودك. هذا رائع لمستخدمي إيثيريوم ذوي الخبرة. لكننا نحاول خدمة مليار شخص آخر (opens in a new tab). لن يحدث هذا بدون تجربة مستخدم رائعة، وواجهة المستخدم الودية هي جزء كبير من ذلك.
تطبيق Greeter
هناك الكثير من النظريات وراء كيفية عمل واجهة المستخدم الحديثة، والكثير من المواقع الجيدة (opens in a new tab) التي تشرح ذلك (opens in a new tab). بدلاً من تكرار العمل الرائع الذي قامت به تلك المواقع، سأفترض أنك تفضل التعلم بالممارسة والبدء بتطبيق يمكنك اللعب به. لا تزال بحاجة إلى النظرية لإنجاز الأمور، وسنصل إليها - سننتقل فقط من ملف مصدر إلى ملف مصدر، ونناقش الأمور عندما نصل إليها.
التثبيت
-
يستخدم التطبيق شبكة اختبار Sepolia (opens in a new tab). إذا لزم الأمر، احصل على ETH لاختبار Sepolia وأضف 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 -
يستخدم التطبيق نقاط وصول مجانية، والتي لها قيود على الأداء. إذا كنت ترغب في استخدام مزود عقدة كخدمة، فاستبدل عناوين URL في
src/wagmi.ts. -
ابدأ التطبيق.
npm run dev -
تصفح إلى عنوان URL الذي يعرضه التطبيق. في معظم الحالات، يكون ذلك http://localhost:5173/ (opens in a new tab).
-
يمكنك رؤية الكود المصدري للعقد، وهو نسخة معدلة من Greeter الخاص بـ Hardhat، على مستكشف سلسلة الكتل (opens in a new tab).
جولة في الملفات
index.html
هذا الملف عبارة عن نموذج HTML قياسي باستثناء هذا السطر، الذي يستورد ملف البرنامج النصي.
<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 والملفات التي يستوردها.
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). سيقوم هذا الكائن بتخزين:
- استدعاءات RPC المخزنة مؤقتًا
- قراءات العقد
- حالة إعادة الجلب في الخلفية
نحتاج إلى مدير ذاكرة التخزين المؤقت لأن Wagmi v3 يستخدم React Query داخليًا.
ReactDOM.createRoot(document.getElementById('root')!).render(
إنشاء مكون React الجذري. المعلمة لـ render هي JSX (opens in a new tab)، وهي لغة امتداد تستخدم كلاً من HTML وJavaScript/TypeScript. علامة التعجب هنا تخبر مكون 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) تعريفات واجهة مستخدم React مع مكتبة Viem (opens in a new tab) لكتابة تطبيق لامركزي (dapp) على إيثيريوم.
<QueryClientProvider client={queryClient}>
وأخيرًا، أضف مزود React Query حتى يتمكن أي مكون تطبيق من استخدام الاستعلامات المخزنة مؤقتًا.
<App />
الآن يمكننا الحصول على المكون الخاص بالتطبيق، والذي ينفذ واجهة المستخدم فعليًا. يخبر /> في نهاية المكون 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.
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) تشغيل دالة كلما تغيرت قيمة متغير لمزامنة نظام خارجي.
if (connection.status === 'connected' &&
connection.chainId !== SEPOLIA_CHAIN_ID
) {
switchChain({ chainId: SEPOLIA_CHAIN_ID })
}
إذا كنا متصلين، ولكن ليس بسلسلة كتل Sepolia، فقم بالتبديل إلى Sepolia.
}, [connection.status, connection.chainId])
أعد تشغيل الدالة في كل مرة تتغير فيها حالة الاتصال أو معرف السلسلة (chainId) للاتصال.
return (
<>
يجب أن يُرجع JSX الخاص بمكون React مكون 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`".
هذه هي الطريقة القياسية لوضع عبارات if داخل JSX.
<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 لدينا قائمة بالموصلات. نستخدم map (opens in a new tab) لتحويلها إلى قائمة بأزرار JSX لعرضها.
<button
key={connector.uid}
في JSX، من الضروري أن يكون للعلامات "الشقيقة" (العلامات التي تنحدر من نفس الأصل) معرفات مختلفة.
onClick={() => connect({ connector })}
type="button"
>
{connector.name}
</button>
))}
أزرار الموصل.
<div>{status}</div>
<div>{error?.message}</div>
</div>
)}
توفير معلومات إضافية. يخبر بناء جملة التعبير <variable>?.<field> JavaScript أنه إذا تم تعريف المتغير، فقم بالتقييم إلى ذلك الحقل. إذا لم يتم تعريف المتغير، فإن هذا التعبير يتم تقييمه إلى undefined.
التعبير error.message، عندما لا يكون هناك خطأ، سيثير استثناءً. يتيح لنا استخدام error?.message تجنب هذه المشكلة.
src/Greeter.tsx
يحتوي هذا الملف على معظم وظائف واجهة المستخدم. ويتضمن تعريفات تكون عادةً في ملفات متعددة، ولكن نظرًا لأن هذا برنامج تعليمي، فقد تم تحسين البرنامج ليكون سهل الفهم في المرة الأولى، بدلاً من الأداء أو سهولة الصيانة.
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
واجهة التطبيق الثنائية (ABI) لعقد Greeter.
إذا كنت تقوم بتطوير العقود وواجهة المستخدم في نفس الوقت، فعادة ما تضعها في نفس المستودع وتستخدم ABI الذي تم إنشاؤه بواسطة مترجم Solidity كملف في تطبيقك. ومع ذلك، هذا ليس ضروريًا هنا لأن العقد قد تم تطويره بالفعل ولن يتغير.
نستخدم as const (opens in a new tab) لإخبار TypeScript أن هذا ثابت حقيقي. عادةً، عندما تحدد في JavaScript const x = {"a": 1}، يمكنك تغيير القيمة في x، لكنك لا تستطيع التعيين إليها.
type AddressPerBlockchainType = {
[key: number]: AddressType
}
لغة TypeScript مكتوبة بقوة. نستخدم هذا التعريف لتحديد العنوان الذي يتم فيه نشر عقد 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. نقوم بتغليف هذا داخل 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) دالة greet الخاصة بـ العقد (opens in a new tab).
const [ currentGreeting, setCurrentGreeting ] =
useState("Please wait while we fetch the greeting from the blockchain...")
const [ newGreeting, setNewGreeting ] = useState("")
يتيح لنا خطاف useState (opens in a new tab) الخاص بـ React تحديد متغير حالة، تستمر قيمته من عرض واحد للمكون إلى آخر. القيمة الأولية هي المعلمة، وفي هذه الحالة السلسلة الفارغة.
يُرجع خطاف useState قائمة بقيمتين:
- القيمة الحالية لمتغير الحالة.
- دالة لتعديل متغير الحالة عند الحاجة. نظرًا لأن هذا خطاف، في كل مرة يتم استدعاؤه يتم عرض المكون مرة أخرى.
في هذه الحالة، نستخدم متغير حالة للتحية الجديدة التي يريد المستخدم تعيينها.
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 في كل مرة تتغير فيها معلمة. بعد ذلك، عندما يريد المستخدم فعليًا إرسال المعاملة (في هذه الحالة عن طريق الضغط على تحديث التحية)، تكون تكلفة الغاز معروفة، ويمكن للمستخدم رؤية صفحة المحفظة على الفور.
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 />
هذا هو حقل النص المدخل حيث يمكن للمستخدم تعيين تحية جديدة. في كل مرة يضغط فيها المستخدم على مفتاح، نستدعي greetingChange، والذي يستدعي setNewGreeting. نظرًا لأن setNewGreeting يأتي من useState، فإنه يتسبب في إعادة عرض مكون Greeter. هذا يعني أن:
- نحتاج إلى تحديد
valueللاحتفاظ بقيمة التحية الجديدة، لأنه بخلاف ذلك ستعود إلى الافتراضي، السلسلة الفارغة. - يتم تحديث
simulationأيضًا في كل مرة يتغير فيهاnewGreeting، مما يعني أننا سنحصل على محاكاة بالتحية الصحيحة. قد يكون هذا ذا صلة لأن تكلفة الغاز تعتمد على حجم بيانات الاستدعاء، والتي تعتمد على طول السلسلة.
<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()
نقطة نهاية HTTP الافتراضية التي تأتي مع Viem جيدة بما فيه الكفاية. إذا أردنا عنوان 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أ. استيراد نوع
defineChainمن Viem.import { defineChain } from 'viem'ب. أضف تعريف الشبكة. لست بحاجة حقًا للقيام بذلك لـ 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', } }, })ج. أضف السلسلة الجديدة إلى استدعاء
createConfig.export const config = createConfig({ chains: [sepolia, optimismSepolia], connectors: [ injected(), ], transports: { [optimismSepolia.id]: http(), [sepolia.id]: http() }, multiInjectedProviderDiscovery: false, }) -
قم بتحرير
src/App.tsxللتعليق على التبديل التلقائي إلى Sepolia. في نظام الإنتاج، من المحتمل أن تعرض أزرارًا بها روابط لكل سلسلة من سلاسل الكتل التي تدعمها./* 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", } -
في متصفحك.
أ. تصفح إلى ChainList (opens in a new tab) وانقر على أحد الأزرار الموجودة على الجانب الأيمن من الجدول لإضافة السلسلة إلى محفظتك.
ب. في التطبيق، انقر على قطع الاتصال (Disconnect) ثم أعد الاتصال لتغيير سلسلة الكتل. هناك طرق أفضل للتعامل مع هذا، لكنها تتطلب تغييرات في التطبيق.
الخاتمة
بالطبع، أنت لا تهتم حقًا بتوفير واجهة مستخدم لـ Greeter. أنت تريد إنشاء واجهة مستخدم لعقودك الخاصة. لإنشاء تطبيقك الخاص، قم بتشغيل هذه الخطوات:
-
حدد لإنشاء تطبيق Wagmi.
npm create wagmi -
اكتب
yللمتابعة. -
قم بتسمية التطبيق.
-
حدد إطار عمل React.
-
حدد متغير Vite.
الآن اذهب واجعل عقودك قابلة للاستخدام للعالم الواسع.