در این پست می خواهیم یک واسط کاربری را برای ارتباط با قرار داد هوشمند ساده 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>
)
};