کار با کتابخانه web3.js و ایجاد واسط کاربری

در این پست می خواهیم یک واسط کاربری را برای ارتباط با قرار داد هوشمند ساده SimpleStorage ایجاد کنیم که از کتابخانه web3.js برای تعامل با این قرارداد استفاده می کند.

Web3.js مجموعه ای از کتابخانه هاست که به شما امکان تعامل با یک نود لوکال یا ریموت در شبکه اتریوم با استفاده از HTTP، IPC یا WebSocket را می دهد.

ما فرض می کنیم که شما با استفاده از

npx create-react-app infura_experiments

یک پروژه react را ایجاد کرده اید. شما می توانید برای واسط کاربری بهتر از react-bootstrap در کنار react استفاده کنید:

npm install react-bootstrap@next bootstrap@5.1.0

(برای استفاده از react-bootstrap در فایل index.js باید این کتابخانه را import کنید:

import 'bootstrap/dist/css/bootstrap.min.css';

)
به فایل App.js بروید و برای شروع فایل را به صورت زیر اصلاح کنید.


import React, { Component } from "react";
import { Container, Row, Col, FormControl, InputGroup, Button } from 'react-bootstrap'

class App extends Component {
 render() {
   return (
     <div>
       Simple Storage
     </div>)
 }
}
export default App;

برای نصب Web3.js دستور زیر را اجرا کنید:

~$ npm install web3

فایل app.js در فولدر src،جایی است که باید Web3 را import کنید:

import Web3 from "web3";

توجه: کلاس Web3 یک پکیج برای تمامی ماژول های مربوط به اتریوم است. جزییات بیشتر در این مورد را می توانید در لینک زیر ببینید:
https://web3js.readthedocs.io/en/v1.3.0/

برای شروع باید از داشتن یک Provider اطمینان حاصل کنیم! کتابخانه web3.js به یک شی Provider نیاز دارد که شامل پروتکل اتصال به نودی است که می خواهید به آن متصل شوید.

const web3 = new Web3(Web3.givenProvider);

در اینجا به اطلاعات قرارداد دیپلوی شده شامل ABI و آدرس قرارداد احتیاج دارید (اگر از ترافل برای دیپلوی قرارداد خود استفاده کرده باشید این اطلاعات در می توانید یک فایل json در فولدر build مشاهده کنید).
در فولدر src یک فولدر جدید به نام ABI بسازید و یک فایل abi.js در آن قرار دهید و اطلاعات ABI را به این فایل بیاورید.
کد فایل:

export const SimpleStorage = [

{

constant: false,

inputs: [

{

name: "x",

type: "uint256",

},

],

name: "set",

outputs: [],

payable: false,

stateMutability: "nonpayable",

type: "function",

},

{

constant: true,

inputs: [],

name: "get",

outputs: [

{

name: "",

type: "uint256",

},

],

payable: false,

stateMutability: "view",

type: "function",

},

];

برای ایجاد بستر تعامل با قرارداد لازم است SimpleStorage که حاوی اطلاعات ABI است را import کنیم/

import { SimpleStorage } from './ABI/SimpleStorage'

در این مرحله ما به آدرسی که قرارداد در آن دیپلوی شده است احتیاج داریم.

یک متغیر برای ذخیره آدرس قرارداد تعریف کنید:

const contractAddress = "your contract address here";

کد زیر را در ادامه وارد کنید:

const storageContract = new web3.eth.Contract(SimpleStorage, contractAddress);

در اینجا متغیر جدیدی که به نام storageContract تعریف کردیم حاوی آدرس قرارداد (برای آنکه بداند قرارداد در چه آدرسی قرار دارد) و ABI (برای اینکه بداند چگونه باید با قرارداد تعامل داشته باشد) خواهد بود.

کد ما در این مرحله باید به صورت زیر باشد:

import React, { Component } from "react";

import { Container, Row, Col, FormControl, InputGroup, Button } from 'react-bootstrap'

import Web3 from 'web3';

import { SimpleStorage } from './ABI/SimpleStorage'

const web3 = new Web3(Web3.givenProvider);

const contractAddress = "0x816da4d3Fd13aB025504e5AbaD48Ad999b3A3275"

const storageContract = new web3.eth.Contract(SimpleStorage, contractAddress);

نکته بعدی که باید مد نظر داشته باشیم این است که DApp ما مجوز دسترسی به موجودی کاربر برای پرداخت بهای gas مصرفی برای ارسال تراکنش ها را داشته باشد. تراکنش ها نیاز دارند که توابع موجود در قرارداد ما را فراخوانی کنند.

اینجاست که ارتباط ما با MetaMask برای امضای تراکنش ها که منجر به تغییر مقدار UINT موجود در قرارداد ما می شوند مفید واقع می شود و بهای gas مورد نیاز برای مانینگ تراکنش را می پردازد.
کد های زیر به ترتیب:

const accounts = await window.ethereum.enable();

درخواست اجازه دسترسی به کیف پول کاربر را می دهد،

const account = accounts[0];

آدرس کاربر فعلی را می گیرد.

برای اینکه به محض لودن پیج اتصال به متامسک برقرار شود می توانید کد های زیر را در فانکشن componentDidMount قرار دهید.

  componentDidMount = async (t) => {

        const accounts = await window.ethereum.enable();
        const account = accounts[0];
        this.setState({ account: account });

}

در این مرحله تمامی ابزار های لازم برای ارتباط با یک قرارداد را در اختیار دارید. در یک تابع setValue از طریق methods.set() و با استفاده از آدرس تعریف شده (آدرس کاربر)، تابع set را فراخوانی می کنیم:

  setValue = async (t) => {

    const val = this.value.current.value;
    const gas = await storageContract.methods.set(val).estimateGas();

    const result = await storageContract.methods.set(val).send({

      from: this.state.account,

      gas,

    });
  };

و تابع getValue:

  getValue = async (t) => {

    const result = await storageContract.methods.get().call({

      from: this.state.account,

    });
    this.setState({ storedValue: result });
  }

تا اینجا کد dApp ما باید به این صورت درآمده باشد:

import React, { Component } from "react";

import { Container, Row, Col, FormControl, InputGroup, Button } from 'react-bootstrap'

import Web3 from 'web3';

import { SimpleStorage } from './ABI/SimpleStorage'

const web3 = new Web3(Web3.givenProvider);

const contractAddress = "0x816da4d3Fd13aB025504e5AbaD48Ad999b3A3275"

const storageContract = new web3.eth.Contract(SimpleStorage, contractAddress);

class App extends Component {

  constructor(props) {

    super(props);

    this.state = {

      storedValue: 0,

      account: null

    };

    this.value = React.createRef();

  }

  componentDidMount = async (t) => {

        const accounts = await window.ethereum.enable();

        const account = accounts[0];

        this.setState({ account: account });

}

  setValue = async (t) => {

    const val = this.value.current.value;

    const gas = await storageContract.methods.set(val).estimateGas();

    const result = await storageContract.methods.set(val).send({

      from: this.state.account,

      gas,

    });

  }

  getValue = async (t) => {

    const result = await storageContract.methods.get().call({

      from: this.state.account,

    });

    this.setState({ storedValue: result });

  }

}

export default App;

آبجکت web3.eth.contract تعامل با قراردادهای هوشمند روی اتریوم را آسان می کند. زمانیکه شما یک آبجکت قرارداد ایجاد می کنید، به آن JSON interface مربوط به قرارداد دیپلوی شده خود را می دهید و Web3 به طور اتوماتیک تمامی فراخوانی ها را برای شما تبدیل به فراخوانی های ABI سطح پایین بر روی RPC میکند.
در واقع شما با استفاده از این امکانات می توانید با قراردادهای هوشمند خود به صورت آبجکت های جاوا اسکریپتی رفتار کنید.
به یاد داشته باشید ، هر زمان که ما به storageContract مراجعه می کنیم ، آن را مبنای استفاده از web3.eth قرار می دهیم. به این ترتیب ما می توانیم با قرارداد ها تعامل داشته باشیم و همان طور که در کدهای بالا میبینید، با POST مقدار دهی انجام دهیم و در numberGet با GET مقدار را بخوانیم.این توابع مربوط به قرارداد که ما آنها را در اینجا فراخوانی می کنیم را خودتان می توانید در ABI ببینید.
const post در واقع یک uint را دریافت می کند و تراکنش را از طریق کیف پول شما در MetaMask روی شبکه Rinkrdby تایید می کند(post بهای gas را پرداخت می کند).
ما تراکنش های مربوط به قرارداد هوشمند خود را با ارسال پارامترهای ورودی به تابع به methods.set() و مقدار تخمینی gas و آدرس حساب کاربر را به send()، ایجاد می کنیم.
حالا می توانیم بر اساس منطق قرارداد، یک واسط کاربری ساده را در تابع render ایجاد کنیم.

  render() {

    return (<div>
      <h2> Simple Storage  </h2>

      <div class="form-row">
        <div class="col xs = {12}">
          <h7> MM Account: {this.state.account}  </h7>
        </div>
      </div>
      <>
        <br></br>
        <br></br>
        <br></br>
        <br></br>
        <Container>

          <Row>

            <InputGroup className="mb-3">

              <FormControl

                ref={this.value}

                placeholder="Value to Store"

                aria-label="Value to Store"

                aria-describedby="basic-addon2"

              />

              <Button variant="secondary" id="button-addon2" onClick={this.setValue}>

                Store</Button>

            </InputGroup>

          </Row>

          <Row>

            <Col><Button variant="success" onClick={this.getValue}>Retrieve</Button>{' '}</Col>

            <Col>The Stored Value is: {this.state.storedValue}

            </Col>

          </Row>

        </Container>

      </>

    </div>

    )

  };
4 پسندیده

من قرارد داد simplestorage رو نوشتم کامپایل و دیپلوی هم موفقیت امیز بود. همه مراحلو چک کردم اوکی بود اما نمیدونم چرا retrieve نمیشه… متامسک و گاناشی همه رو چک کردم بعد روی یه لوکال هاست دیگه هم چک کردم… ادرس MM Account هم با متامسکم چک کردم که همون باشه… وقتی میخواد صفحه با ری اکت بالا بیاد متامسک هم باز میشه و کانکت میشن اما موقعی که مقدار میدم و store میزنم دیگه تراکنشی نمیاد که تایید کنم. حالا باز فردا امتحان میکنم شمام اگه فکر میکنین مشکل از جای خاصی هست ممنون میشم بگید . مرسییییییییی :raised_hands:

داخل developer tools ببینید چه خطایی بهتون میده(با ctrl+shift+i)

1 پسندیده

فکر میکنم درست شد مرسی.

ببخشید ی سوال داشتم
فرق Web3 , WebVC در چی هستش؟

سلام یه رفرنس بدین ببینم منظورتون از webVC چی هست

سلام من تمام مراحل رو انجام دادم اما هنگام اجرای پروژه تعداد زیادی ارور میده

@soheil برای خطاهایی که می گیری٬ اینجا رو ببین:

برای منم همین خطاها رو میده طبق ویدیو هم جلو رفتم گفتم حتما جایی رو اشتباه کردم و دوباره انجام دادم ولی باز هم ارور داد سرچ هم کردم اطلاعات به خصوصی نیاورد خواهشا اگه کسی میدونه برای چیه ممنون میشم راهنمایی کنه ؟

سلام طبق این لینک برو جلو مشکل حل میشه

1 پسندیده

سلام
پروژه ( فایل SimpleStorageCompile ) بعد از اینکه دیپلوی میکنم اطلاعات نتورک داخل آرتیفکت پروژم نمیاد میخواستم بدونم علتش چی میتونه باشه؟
(کامپایل پروژه بدون مشکل انجام میشه و این مشکل هم تازه پیدا کردم قبلا اوکی بود)

سلام
این ارور ها علتش این هست که react-scripts نسخه ۴ برای اجرا نیاز هست و نسخه های بعدی فایل polyfills ندارن و ارورها ایجاد میشن.
من راه حل هایی که دوستان فرستادنو رفتم اما متاسفانه نتونستم نتیجه بگیرم
یک راه حل آسونتر این هست که بعد از ایجاد پروژه react (با دستور
[npx create-react-app [project-name) داخل مسیر فایل پروژه react دستورات :
npm uninstall react-scripts
npm i react-scripts@4.0.3
اجرا کنید
(فقط حواستون باشه این دستورات قبل از باز کردن کد توی vccode اجرا کنید)

3 پسندیده

سلام و خدا قوت
من هم دقیقا به همون خطای آقای soheil برخوردم . من دستور اول رو زدم اوکی بود اما دومیش رو زدم این پیغام خطا رو میده . از vpn هم استفاده نمیکنم البته من دستورات رو دقیقا قبل از اینکه vscode رو باز کنم میزنم یعنی دقیقا بعد از ساختن react درسته؟ مشکل از کجاست؟

timeout میشه برای حلش
توی فایل npmrc.
timeout=2400000
اضافه کنید

متشکرم از پاسخگوییتون . درست شد روشی که آقای soheil گفتن رو رفتم درست نشد یه پیغام خطای دیگه داد . یه جستجو کردم گفتن باید npm رو دوباره نصب کنی و من هم دوباره نصب کردم و بعدش web3 , react-bootstrap رو هم دوباره نصب کردم و درست شد و پروژه درست اجرا شد . فقط تو ترمینالم یه چیغام خطایی نشون میده نمیدونم چیه . ممکنه خطایی رخ داده باشه؟ ممنون میشم راهنماییم کنید

سلام مجدد
من کلا پر.ژه رو دوباره از اول رفتم انجام دادم . همون کاری رو که شما فرمودید انجام دادم @i.man

یک راه حل آسونتر این هست که بعد از ایجاد پروژه react (با دستور
[npx create-react-app [project-name) داخل مسیر فایل پروژه react دستورات :
npm uninstall react-scripts
npm i react-scripts@4.0.3
اجرا کنید
(فقط حواستون باشه این دستورات قبل از باز کردن کد توی vccode اجرا کنید)

و توی فایل npmrc.
timeout=2400000 قرار دادم همونطور که فرموده بودید . در نهایت پروژه بدون هیچ خطایی کامل اجرا شد . سپاسگذارم از راهنمایی شما @i.man

خود خطایی که هست نوشته که Button قبلا تعریف شده. اگر از کدتون هم اسکرین شات بدید شاید دقیق تر بشه بررسی کرد. ولی ببینین اگر توی خط های قبلی Button دارید شاید مشکل از اون باشه.

طبق آموزش رفتم جلو اما تعداد زیادی ارور میده

سلام. دوستان اگر به این مشکل polyfill برخورد کردن با لینکی که بچه ها دادن حل میشه، اما اگر نتونستین، همینجا مینویسم براتون که چیکار کنین:
توی نسخه ری اکت فعلی که هستش، توی ارتباط با web3 به مشکل ممکنه بخورین، برای رفعش به این صورت اقدام کنین:

اول این ها رو نصب کنین:
اگر با yarn هستین:

yarn add --dev react-app-rewired crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer

و اگر با npm هستین:

npm install --save-dev react-app-rewired crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer process

بعدش تو روت پروژه ری اکتتون، یه فایل جدید با این اسم بسازین:
config-overrides.js

و محتوای زیر رو داخلش قرار بدین:

const webpack = require('webpack');

module.exports = function override(config) {
    const fallback = config.resolve.fallback || {};
    Object.assign(fallback, {
        "crypto": require.resolve("crypto-browserify"),
        "stream": require.resolve("stream-browserify"),
        "assert": require.resolve("assert"),
        "http": require.resolve("stream-http"),
        "https": require.resolve("https-browserify"),
        "os": require.resolve("os-browserify"),
        "url": require.resolve("url")
    })
    config.resolve.fallback = fallback;
    config.plugins = (config.plugins || []).concat([
        new webpack.ProvidePlugin({
            process: 'process/browser',
            Buffer: ['buffer', 'Buffer']
        })
    ])
    return config;
}

در نهایت هم باید توی فایل package.json تون قسمت scripts تغییر نهایی رو ایجاد کنین و به این صورت بشه:

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
},

همین…با این کار مشکل رفع میشه و میتونین پروژه رو بخوبی لود کنین :wink:

ممنون. روش های زیادی رو تست کردم. این روش جواب داد.