-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBlog.html
More file actions
603 lines (589 loc) · 26 KB
/
Copy pathBlog.html
File metadata and controls
603 lines (589 loc) · 26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
<!doctype html>
<html lang="fa-IR" dir="rtl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light" />
<title>کیف پول را وصل کن: وب۳ برای برنامه نویس فرانت اند — بی باگ</title>
<meta
name="description"
content="راهنمای عملی اتصال کیف پول به رابط وب با ethers.js، خواندن از قرارداد، ارسال تراکنش و مدیریت خطاهای واقعی کاربر."
/>
<link rel="icon" href="./assets/img/logo.jpg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300..800&family=Lalezar&family=Kalam:wght@400;700&family=Source+Code+Pro:wght@400;600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./assets/css/all.css" />
<link rel="stylesheet" href="./assets/css/blog.css" />
</head>
<body>
<a class="skip" href="#main">پرش به محتوای اصلی</a>
<div class="scrim" data-close-nav aria-hidden="true"></div>
<header class="mast" id="mast">
<div class="wrap mast__in">
<a class="brand" href="./index.html" aria-label="بی باگ، صفحه اصلی">
<span class="brand__mark" aria-hidden="true"
><svg class="ic"><use href="#i-bug"></use></svg
></span>
<span class="brand__txt"
><b>بی باگ</b><small>کد بنویس، از باگ نترس</small></span
>
</a>
<nav class="nav" id="nav" aria-label="منوی اصلی">
<div class="nav__head">
<span>منو</span>
<button
class="ibtn"
type="button"
data-close-nav
aria-label="بستن منو"
>
<svg class="ic"><use href="#i-x"></use></svg>
</button>
</div>
<a class="nav__l" href="./index.html"
><svg class="ic"><use href="#i-home"></use></svg> خانه</a
>
<a class="nav__l" href="./categories.html" aria-current="page"
><svg class="ic"><use href="#i-layers"></use></svg> وبلاگ و
مقالات</a
>
<a class="nav__l" href="./category.html"
><svg class="ic"><use href="#i-box"></use></svg> وب۳</a
>
<button class="nav__l" type="button" data-writing>
<svg class="ic"><use href="#i-pen"></use></svg> ثبت مقاله
</button>
<a class="nav__l" href="./aboutUs.html"
><svg class="ic"><use href="#i-user"></use></svg> درباره ما</a
>
<a class="nav__l" href="./contactUs.html"
><svg class="ic"><use href="#i-send"></use></svg> ارتباط با ما</a
>
<div class="nav__foot">
<a
class="social-mini"
href="https://github.com/condev-dev"
target="_blank"
rel="noopener me"
aria-label="گیت هاب"
><span class="ibtn"
><svg class="ic"><use href="#i-git"></use></svg></span
></a>
<a
href="https://instagram.com/con.dev"
target="_blank"
rel="noopener me"
aria-label="اینستاگرام"
><span class="ibtn"
><svg class="ic"><use href="#i-ig"></use></svg></span
></a>
<a href="mailto:condevtp@gmail.com" aria-label="ایمیل"
><span class="ibtn"
><svg class="ic"><use href="#i-mail"></use></svg></span
></a>
</div>
</nav>
<button
class="ibtn mast__burger"
id="burger"
type="button"
aria-expanded="false"
aria-controls="nav"
aria-label="باز کردن منو"
>
<svg class="ic"><use href="#i-menu"></use></svg>
</button>
</div>
</header>
<main id="main">
<div class="wrap">
<div class="head">
<ol class="crumb">
<li><a href="./index.html">بی باگ</a></li>
<li><a href="./categories.html">مقالات</a></li>
<li><a href="./category.html">وب۳</a></li>
<li aria-current="page">اتصال کیف پول</li>
</ol>
<h1>کیف پول را وصل کن: وب۳ برای برنامه نویس فرانت اند</h1>
<p class="lead">
بدون قیمت ارز و بدون شعار آینده اینترنت. فقط همان چیزی که لازم است
تا یک رابط وب واقعی با بلاک چین حرف بزند و وقتی کاربر تراکنش را کنسل
کرد، رابطت از هم نپاشد.
</p>
<div class="meta">
<span
><svg class="ic"><use href="#i-user"></use></svg> کان دو</span
><span
><svg class="ic"><use href="#i-clock"></use></svg> ۱۲ دقیقه</span
><span
><svg class="ic"><use href="#i-cal"></use></svg> ۱۲ مرداد
۱۴۰۴</span
><span
><svg class="ic"><use href="#i-eye"></use></svg> ۲٫۴ هزار
بازدید</span
><span
><svg class="ic"><use href="#i-code"></use></svg> ethers نسخه
۶</span
>
</div>
</div>
<figure class="hero">
<div class="ph">
<img
src="./assets/img/box/box5.jpg"
alt="نمای رابط وب متصل به کیف پول"
/>
</div>
</figure>
<div class="article">
<article class="prose">
<p>
بیشتر آموزش های وب۳ فارسی از جای اشتباهی شروع می شوند: از قیمت و
صرافی. برای یک برنامه نویس فرانت اند، وب۳ فقط یک چیز است: یک
<strong>API عجیب</strong> که در مرورگر کاربر تزریق شده، امضای
دیجیتال می خواهد، چند ثانیه تاخیر دارد و هر درخواستش می تواند پول
خرج کند. همین. اگر این را بپذیری، بقیه ماجرا مهندسی معمولی است.
</p>
<div class="note note--info">
<div class="note__t">
<svg class="ic"><use href="#i-info"></use></svg> پیش نیاز
</div>
<p>
جاوااسکریپت مدرن، async و await، و یک کیف پول مرورگری نصب شده.
سالیدیتی لازم نیست. در این مقاله قرارداد نمی نویسیم، فقط با آن
حرف می زنیم.
</p>
</div>
<h2 id="s1">سه لایه ای که هر dApp روی آن سوار است</h2>
<p>
هر اپلیکیشن غیرمتمرکز، هر قدر هم پیچیده، از همین سه لایه ساخته می
شود:
</p>
<ol>
<li>
<strong>Provider</strong>: اتصال فقط خواندنی به شبکه. وضعیت را
می خواند و هزینه ای ندارد.
</li>
<li>
<strong>Signer</strong>: نماینده کاربر. تنها چیزی که می تواند
تراکنش امضا کند.
</li>
<li>
<strong>Contract</strong>: یک آبجکت جاوااسکریپتی که از آدرس و
ABI ساخته می شود.
</li>
</ol>
<blockquote class="pull">
کلید خصوصی هرگز از کیف پول بیرون نمی آید. کد تو فقط درخواست امضا
می دهد و منتظر بله یا نه کاربر می ماند.<cite
>قاعده اول امنیت در فرانت اند وب۳</cite
>
</blockquote>
<h2 id="s2">قدم اول: اتصال، بدون خراب کاری</h2>
<p>
هیچ وقت فرض نکن کیف پول نصب است. هیچ وقت هم بدون کلیک کاربر
درخواست اتصال نفرست. کیف پول ها این رفتار را اسپم حساب می کنند.
</p>
<div class="code">
<div class="code__bar">
<span>connect.js · ethers v6</span
><button class="code__copy" data-copy>کپی</button>
</div>
<pre><code><span class="tok-k">import</span> { <span class="tok-f">BrowserProvider</span> } <span class="tok-k">from</span> <span class="tok-s">"ethers"</span>;
<span class="tok-k">export async function</span> <span class="tok-f">connectWallet</span>() {
<span class="tok-k">if</span> (!window.ethereum) {
<span class="tok-k">throw new</span> Error(<span class="tok-s">"کیف پولی پیدا نشد"</span>);
}
<span class="tok-k">const</span> provider = <span class="tok-k">new</span> <span class="tok-f">BrowserProvider</span>(window.ethereum);
<span class="tok-k">const</span> [account] = <span class="tok-k">await</span> provider.send(<span class="tok-s">"eth_requestAccounts"</span>, []);
<span class="tok-k">const</span> signer = <span class="tok-k">await</span> provider.getSigner();
<span class="tok-k">const</span> { chainId, name } = <span class="tok-k">await</span> provider.getNetwork();
<span class="tok-k">return</span> { provider, signer, account, chainId, network: name };
}</code></pre>
</div>
<p>
کاربر می تواند وسط کار حساب یا شبکه را عوض کند. اگر گوش ندهی،
رابطت دیتای حساب دیگری را نشان می دهد.
</p>
<div class="code">
<div class="code__bar">
<span>watch.js</span
><button class="code__copy" data-copy>کپی</button>
</div>
<pre><code>window.ethereum?.on(<span class="tok-s">"accountsChanged"</span>, (accounts) => {
accounts.length ? setAccount(accounts[0]) : signOut();
});
window.ethereum?.on(<span class="tok-s">"chainChanged"</span>, () => location.reload());</code></pre>
</div>
<div class="note note--warn">
<div class="note__t">
<svg class="ic"><use href="#i-alert"></use></svg> باگی که همه می
خورند
</div>
<p>
آدرس ها را برای مقایسه با <code>toLowerCase()</code> یکدست کن.
کیف پول ها آدرس را با حروف بزرگ و کوچک ترکیبی برمی گردانند و
مساوی ساده بی رحمانه false می دهد.
</p>
</div>
<h2 id="s3">قدم دوم: خواندن از قرارداد</h2>
<p>
خواندن با provider انجام می شود، نه signer. یعنی حتی بدون اتصال
کیف پول هم می توانی داده نشان بدهی. برای صفحه اول سایت این خیلی
مهم است.
</p>
<div class="code">
<div class="code__bar">
<span>read.js</span
><button class="code__copy" data-copy>کپی</button>
</div>
<pre><code><span class="tok-k">import</span> { Contract, formatUnits } <span class="tok-k">from</span> <span class="tok-s">"ethers"</span>;
<span class="tok-k">const</span> ABI = [
<span class="tok-s">"function balanceOf(address) view returns (uint256)"</span>,
<span class="tok-s">"function decimals() view returns (uint8)"</span>,
<span class="tok-s">"function symbol() view returns (string)"</span>,
];
<span class="tok-k">export async function</span> <span class="tok-f">getBalance</span>(provider, token, wallet) {
<span class="tok-k">const</span> contract = <span class="tok-k">new</span> Contract(token, ABI, provider);
<span class="tok-k">const</span> [raw, decimals, symbol] = <span class="tok-k">await</span> Promise.all([
contract.balanceOf(wallet),
contract.decimals(),
contract.symbol(),
]);
<span class="tok-k">return</span> `${formatUnits(raw, decimals)} ${symbol}`;
}</code></pre>
</div>
<p>
عددهای بلاک چین BigInt هستند، نه Number. اگر آن ها را داخل
parseFloat بیندازی، روی مبالغ بزرگ دقت را از دست می دهی.
</p>
<h2 id="s4">قدم سوم: ارسال تراکنش و خطاهایی که واقعا می افتند</h2>
<p>
تراکنش سه حالت دارد: رد شدن توسط کاربر، شکست در شبکه، و موفقیت با
تاخیر. هر سه را باید در رابطت نشان بدهی.
</p>
<div class="code">
<div class="code__bar">
<span>mint.js</span
><button class="code__copy" data-copy>کپی</button>
</div>
<pre><code><span class="tok-k">import</span> { parseEther } <span class="tok-k">from</span> <span class="tok-s">"ethers"</span>;
<span class="tok-k">async function</span> <span class="tok-f">mint</span>(contract) {
setState(<span class="tok-s">"waiting-signature"</span>);
<span class="tok-k">try</span> {
<span class="tok-k">const</span> tx = <span class="tok-k">await</span> contract.mint(1n, { value: parseEther(<span class="tok-s">"0.01"</span>) });
setState(<span class="tok-s">"pending"</span>, tx.hash);
<span class="tok-k">const</span> receipt = <span class="tok-k">await</span> tx.wait();
setState(<span class="tok-s">"done"</span>, receipt.transactionHash);
} <span class="tok-k">catch</span> (err) {
<span class="tok-k">if</span> (err.code === <span class="tok-s">4001</span>) return setState(<span class="tok-s">"idle"</span>);
setState(<span class="tok-s">"error"</span>, err.shortMessage || <span class="tok-s">"تراکنش شکست خورد"</span>);
}
}</code></pre>
</div>
<p>
لینک explorer را همان لحظه که tx.hash را گرفتی نشان بده. کاربر
دوست ندارد روی یک دکمه بی جان خیره شود و نداند چه خبر است.
</p>
<h2 id="s5">چک لیست رابط کاربری</h2>
<table class="tbl">
<thead>
<tr>
<th>وضعیت</th>
<th>چیزی که باید نشان بدهی</th>
</tr>
</thead>
<tbody>
<tr>
<td>قبل از اتصال</td>
<td>یک دکمه واضح و توضیح کوتاه</td>
</tr>
<tr>
<td>در انتظار امضا</td>
<td>پیام واضح که پنجره کیف پول باز شده</td>
</tr>
<tr>
<td>در انتظار تایید شبکه</td>
<td>لینک تراکنش و وضعیت pending</td>
</tr>
<tr>
<td>خطا</td>
<td>پیام انسانی، نه آبجکت خام خطا</td>
</tr>
<tr>
<td>موفق</td>
<td>تایید نهایی و آپدیت داده ها</td>
</tr>
</tbody>
</table>
<p>
همین پنج مورد اگر درست اجرا شوند، محصولت از نود درصد نمونه کارهای
دم دستی وب۳ بهتر است.
</p>
</article>
<aside class="aside">
<div class="card">
<strong style="display: block; margin-bottom: 0.75rem"
>فهرست مقاله</strong
>
<nav class="toc">
<a href="#s1">سه لایه اصلی</a><a href="#s2">اتصال کیف پول</a
><a href="#s3">خواندن از قرارداد</a
><a href="#s4">ارسال تراکنش</a><a href="#s5">چک لیست رابط</a>
</nav>
</div>
<div class="card">
<strong style="display: block; margin-bottom: 0.75rem"
>نویسنده</strong
>
<div class="who" style="padding: 0; border: 0; background: none">
<div class="avatar">CD</div>
<div>
<h3
style="
font-size: 1.05rem;
font-family: var(--fb);
font-weight: 700;
"
>
کان دو
</h3>
<p style="font-size: var(--t-sm); color: var(--ink2)">
توسعه دهنده وب، علاقه مند به فرانت اند، RTL و وب۳ کاربردی.
</p>
</div>
</div>
</div>
</aside>
</div>
<section class="reveal" style="margin-top: 4rem">
<div class="who">
<div class="avatar">CD</div>
<div>
<h3
style="
font-size: 1.05rem;
font-family: var(--fb);
font-weight: 700;
"
>
درباره نویسنده
</h3>
<p>
چیزهایی که خودم موقع ساختن پروژه ها یاد می گیرم را همان جا می
نویسم. نه برای این که کامل ترین مرجع باشم، برای این که موقع گیر
کردن به درد بخورم.
</p>
<div class="chips" style="margin-top: 0.75rem">
<span class="chip">جاوااسکریپت</span
><span class="chip">فرانت اند</span><span class="chip">وب۳</span
><span class="chip">RTL</span>
</div>
</div>
</div>
</section>
<section class="reveal" style="margin-top: 4rem">
<div class="sec-head">
<div>
<span
style="
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--ink3);
"
>بعدی برای خواندن</span
>
<h2>مقاله های <em>مرتبط</em></h2>
</div>
</div>
<div class="posts">
<article class="post">
<a class="ph" href="./Blog.html"
><img
src="./assets/img/box/box8.jpg"
alt=""
loading="lazy" /></a
><span class="post__cat">وب۳</span>
<h4>
<a href="./Blog.html">اولین قرارداد هوشمندت با Hardhat</a>
</h4>
<p>اگر این مقاله را خواندی، قدم بعدی همین است.</p>
</article>
<article class="post">
<a class="ph" href="./Blog.html"
><img
src="./assets/img/box/box7.jpg"
alt=""
loading="lazy" /></a
><span class="post__cat">فرانت اند</span>
<h4><a href="./Blog.html">ethers یا viem؟</a></h4>
<p>مقایسه ای که وقت انتخاب کتابخانه نجاتت می دهد.</p>
</article>
<article class="post">
<a class="ph" href="./Blog.html"
><img
src="./assets/img/box/box4.jpg"
alt=""
loading="lazy" /></a
><span class="post__cat">امنیت</span>
<h4>
<a href="./Blog.html">حمله reentrancy را بساز، بعد ببند</a>
</h4>
<p>امنیت را با مثال واقعی یاد بگیر، نه با تعریف.</p>
</article>
</div>
</section>
<section class="news reveal">
<div>
<h2>هر شنبه، یک ایمیل کوتاه</h2>
<p>خلاصه مقاله های هفته و یک باگ واقعی. بدون تبلیغ.</p>
</div>
<form
data-validate
data-ok-title="عضویتت ثبت شد"
data-ok="از این شنبه ایمیل ها برایت می آید."
>
<input
type="email"
id="mail"
dir="ltr"
placeholder="you@example.com"
data-req
data-msg="ایمیلت را بنویس."
/><button class="btn">عضویت</button>
</form>
</section>
</div>
</main>
<footer class="foot">
<div class="wrap">
<div class="foot__grid">
<div>
<a class="brand" href="./index.html">
<span class="brand__mark" aria-hidden="true"
><svg class="ic"><use href="#i-bug"></use></svg
></span>
<span class="brand__txt"
><b>بی باگ</b><small>کد بنویس، از باگ نترس</small></span
>
</a>
<p>
وبلاگ فارسی برنامه نویسی. هر مقاله از یک باگ واقعی شروع می شود و
با کدی که کار می کند تمام می شود.
</p>
<div class="social">
<a
href="https://github.com/condev-dev"
target="_blank"
rel="noopener me"
aria-label="گیت هاب"
><svg class="ic"><use href="#i-git"></use></svg
></a>
<a
href="https://instagram.com/con.dev"
target="_blank"
rel="noopener me"
aria-label="اینستاگرام"
><svg class="ic"><use href="#i-ig"></use></svg
></a>
<a
href="https://linkedin.com/in/con-dev-5b43a538b"
target="_blank"
rel="noopener me"
aria-label="لینکدین"
><svg class="ic"><use href="#i-in"></use></svg
></a>
<a
href="https://youtube.com/@condev-tp"
target="_blank"
rel="noopener me"
aria-label="یوتیوب"
><svg class="ic"><use href="#i-yt"></use></svg
></a>
<a
href="https://aparat.com/con.dev"
target="_blank"
rel="noopener me"
aria-label="آپارات"
><svg class="ic"><use href="#i-play"></use></svg
></a>
<a href="mailto:condevtp@gmail.com" aria-label="ایمیل"
><svg class="ic"><use href="#i-mail"></use></svg
></a>
</div>
</div>
<div>
<h4>مسیرها</h4>
<div class="foot__ls">
<a href="./index.html">صفحه اصلی</a>
<a href="./categories.html">وبلاگ و مقالات</a>
<a href="./category.html">وب۳ و بلاک چین</a>
<a href="./aboutUs.html">درباره ما</a>
<a href="./contactUs.html">ارتباط با ما</a>
</div>
</div>
<div>
<h4>دسته های پرطرفدار</h4>
<div class="foot__ls">
<a href="./categories.html">برنامه نویسی</a>
<a href="./categories.html">هوش مصنوعی</a>
<a href="./category.html">وب۳</a>
<a href="./categories.html">استایل دهی</a>
</div>
</div>
<div>
<h4>دم دست</h4>
<div class="foot__ls">
<a href="http://cv.condev.ir/" target="_blank" rel="noopener"
>رزومه و نمونه کار</a
>
<a
href="https://github.com/condev-dev"
target="_blank"
rel="noopener"
>مخزن کدها</a
>
<a href="mailto:condevtp@gmail.com">condevtp@gmail.com</a>
<a href="./404Page.html">صفحه خطا</a>
</div>
</div>
</div>
<div class="foot__bot">
<span>© ۱۴۰۴ بی باگ · محتوا با ذکر منبع آزاد است.</span>
<span>
<a href="http://cv.condev.ir/" target="_blank" rel="noopener"
><b>ConDev</b></a
></span
>
</div>
</div>
</footer>
<div
class="toasts"
id="toasts"
role="region"
aria-live="polite"
aria-label="اعلان ها"
></div>
<script src="./assets/js/writing-popup-fix.js"></script>
<script src="./assets/js/blog.js"></script>
<script src="./assets/js/header.js"></script>
<!-- Load Icon Svg -->
<script>
fetch("./assets/icon/icons.svg")
.then((response) => response.text())
.then((data) => {
const div = document.createElement("div");
div.style.display = "none";
div.innerHTML = data;
document.body.insertBefore(div, document.body.childNodes[0]);
});
</script>
<!-- END - Load Icon Svg -->
</body>
</html>