I'm trying to build a decentralized app for a lottery smart contract, deployed on a local ganache ethereum network, but in this case I'm using Drizzle tool from Truffle Suite.
This is the smart contract, it's pretty simple, it has pickWinner, enter and getPlayers functions.
contract Lottery {
address public manager;
address payable[] public players;
uint256 index;
constructor() payable {
manager = msg.sender;
}
function enter() public payable {
require(msg.value == .02 ether);
players.push(payable(msg.sender));
}
function random() private view returns (uint256) {
return
uint256(
keccak256(
abi.encodePacked(block.difficulty, block.timestamp, players)
)
);
}
function pickWinner() public restricted {
index = random() % players.length;
players[index].transfer(address(this).balance);
players = new address payable[](0);
}
modifier restricted() {
require(msg.sender == manager);
_;
}
function getPlayers() public view returns (address payable[] memory) {
return players;
}
}
I have installed @drizzle/store and web3 modules. And I also created a symbolic link between a folder called contracts and the smart contracts' artifacts generated when they were deployed on local Ganache network, this in order to have an instance of the smart contract created and when accessing through Drizzle to the smart contract, have its last version if it is updated. I used ln -s ../../build/contracts contracts.
On the project, I have an index.js file. This is in charged of created a Drizzle object and to load the smart contract from the source file linked with the artifacts, then the idea is to have a Redux store to contain lottery smart contract's state and include Drizzle object into the App component of the aplication.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Drizzle, generateStore } from "@drizzle/store";
import Lottery from "./contracts/Lottery.json";
const options = { contracts: [Lottery] };
const drizzleStore = generateStore(options);
const drizzle = new Drizzle(options, drizzleStore);
ReactDOM.render(<App drizzle={drizzle} />, document.getElementById("root"));
Then I have an App.js file. This is in charged of defining the App component, this component is passed a Drizzle object and the drizzle store from the index.js file. App component uses them to subscribe to updates from the drizzle store. As the application is modularized, there are sub-components, to which the drizzle and drizzleState objects are passed in order to access and update the lottery smart contract.
import React, { Component } from 'react';
import './App.css';
import ViewPlayers from "./ViewPlayers";
import EnterLottery from "./EnterLottery";
import PickWinner from "./PickWinner";
import ViewDetails from "./ViewDetails";
class App extends Component {
state = { loading: true, drizzleState: null };
async componentDidMount() {
const { drizzle } = this.props;
this.unsubscribe = drizzle.store.subscribe(() => {
const drizzleState = drizzle.store.getState();
if (drizzleState.drizzleStatus.initialized) {
this.setState({ loading: false, drizzleState });
}
});
}
compomentWillUnmount() {
this.unsubscribe();
}
render() {
if (this.state.loading) return "Loading Drizzle...";
return (
<div className="App">
<h2 >Lottery Contract</h2>
<div align="left">
<ViewDetails
drizzle={this.props.drizzle}
drizzleState={this.state.drizzleState}
/>
</div>
<hr />
<div align="left">
<ViewPlayers
drizzle={this.props.drizzle}
drizzleState={this.state.drizzleState}
/>
</div>
<hr />
<div align="left">
<EnterLottery
drizzle={this.props.drizzle}
drizzleState={this.state.drizzleState}
/>
</div>
<hr />
<div align="left">
<PickWinner
drizzle={this.props.drizzle}
drizzleState={this.state.drizzleState}
/>
</div>
<hr />
</div>
);
}
}
export default App;
Then ViewPlayers, EnterLottery, PickWinner and ViewDetails sub-components are defined as below:
// ViewPlayers.js
import React from "react";
class ViewPlayers extends React.Component {
state = { dataKey: null };
componentDidMount() {
const { drizzle } = this.props;
const contract = drizzle.contracts.Lottery;
var dataKey = contract.methods.getPlayers.cacheCall();
console.log(contract);
this.setState({ dataKey });
}
render() {
const { Lottery } = this.props.drizzleState.contracts;
const players = Lottery.getPlayers[this.state.dataKey];
var rows = [];
for (var i = 0; i < (players && players.value.length); i++) {
rows.push(<label key={i}> <br />Player {i+1} - {players && players.value[i]} </label>);
}
return <p>All {players && players.value.length } players are {rows}</p>;
}
}
export default ViewPlayers;
// EnterLottery.js
import React from "react";
class EnterLottery extends React.Component {
enter = () => {
const { drizzle, drizzleState } = this.props;
const contract = drizzle.contracts.Lottery;
contract.methods.enter.cacheSend({
from: drizzleState.accounts[0],
value: window.web3.utils.toWei(0.2, 'ether')
});
};
render() {
return (
<div>
<h4>Want to try your luck?</h4>
<label>Pay 0.2 ether to enter</label> <br /><br />
<button className='button'
style={{height: 40,
width: 150,
borderRadius: 10,
fontSize: 15}}
onClick={this.enter}> <b>Enter</b> </button>
</div>
);
}
}
export default EnterLottery;
// PickWinner.js
import React from "react";
class PickWinner extends React.Component {
pick = () => {
const { drizzle, drizzleState } = this.props;
const contract = drizzle.contracts.Lottery;
contract.methods.pickWinner.cacheSend({
from: drizzleState.accounts[0]
});
};
render() {
return (
<div>
<h4>Ready to pick a winner?</h4>
<div><button className='button'
style={{height: 40,
width: 150,
borderRadius: 10,
fontSize: 15}}
onClick={this.pick} >
<b>Pick Winner</b>
</button></div>
</div>
);
}
}
export default PickWinner;
// ViewDetails.js
import React from "react";
class ViewDetails extends React.Component {
state = { manager: null, winningPrize: null};
async componentDidMount() {
const { drizzle } = this.props;
console.log(drizzle);
const contract = drizzle.contracts.Lottery;
var manager = contract.methods.manager.cacheCall();
var winningPrize = await drizzle.web3.eth.getBalance(contract.address)
this.setState({ manager, winningPrize});
}
render() {
const { drizzle } = this.props;
const { Lottery } = this.props.drizzleState.contracts;
const manager = Lottery.manager[this.state.manager];
const balance = this.state.winningPrize;
return (
<p>This contract is managed by <b>{manager && manager.value}</b>.
All players are competing to win <b>{balance && drizzle.web3.utils.fromWei(balance,'ether')}</b> ether!</p>
)
}
}
export default ViewDetails;
I'm connected to Metamask.
Then when I run npm start, the web page is rendered but when I click the enter button, this does not connect to Metamask. I have this log on browser console:
MetaMask no longer injects web3. For details, see: https://docs.metamask.io/guide/provider-migration.html#replacing-window-web3
Uncaught TypeError: Cannot read properties of undefined (reading 'toWei')
at EnterLottery.enter (EnterLottery.js:17:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994:1)
at invokeGuardedCallback (react-dom.development.js:4056:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070:1)
at executeDispatch (react-dom.development.js:8243:1)
at processDispatchQueueItemsInOrder (react-dom.development.js:8275:1)
at processDispatchQueue (react-dom.development.js:8288:1)
at dispatchEventsForPlugins (react-dom.development.js:8299:1)
at react-dom.development.js:8508:1
From the log, I understand web3 injections are unsupported anymore, so one solution is to use window.web3.currentProvider, but I don't know how to use this on the EnterLottery.js file (maybe I should even use it somewhere else?), I don't really know where to implement the change for this to work. Well, I do also have an error with toWei method of the web3 module.
How should I make this work?.