yeah cool what is the issue
@mcgrane5 so I did the first steps for refactoring the logic for the DEX. I created the LoadingContainer
import React, { useState, useEffect } from "react";
import { ethers } from "ethers";
import App from './App.js';
import Dex from "./artifacts/contracts/Dex.sol/Dex.json";
//import RealToken from "./artifacts/contracts/tokens.sol/RealToken.json";
const dexContractAddress = "0x5fbdb2315678afecb367f032d93f642f64180aa3";
function LoadingContainer() {
const [web3, setWeb3] = useState(undefined);
const [accounts, setAccounts] = useState([]);
const [contracts, setContracts] = useState(undefined);
useEffect(() => {
const initDex = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const dex = new ethers.Contract(dexContractAddress, Dex.abi, signer);
const accounts = await provider.send("eth_requestAccounts", []);
setWeb3(provider);
setContracts(dex);
setAccounts(accounts);
}
initDex();
// eslint-disable-next-line
}, []);
const isReady = () => {
return (
typeof web3 !== 'undefined'
&& typeof contracts !== 'undefined'
&& accounts.length > 0
);
}
if (!isReady()) {
return <div>Loading...</div>;
}
return (
<App
web3={web3}
accounts={accounts}
contracts={contracts}
/>
);
}
export default LoadingContainer;
modified the index page
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'bootswatch/dist/slate/bootstrap.min.css';
//import App from './App';
import LoadingContainer from './LoadingContainer';
import './App.css';
ReactDOM.render(
/*
<React.StrictMode>
<App />
</React.StrictMode>
*/
<LoadingContainer />
,
document.getElementById('root')
);
but I’ve tried to avoid re-using the provider, signer, contract but I have not figured out how to get around that so in the App.js file, here with this function, how would I rewrite this?
const handleWithDraw = async (e) => {
e.preventDefault();
try {
if (!window.ethereum) return alert("Please install or sign-in to Metamask");
const data = new FormData(e.target);
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const dex = new ethers.Contract(dexContractAddress, Dex.abi, signer);
await provider.send("eth_requestAccounts", []);
const withdrawTx = await dex.withdraw(
ethers.utils.parseEther(data.get("amount")), ethers.utils.formatBytes32String(data.get("ticker"))
);
await withdrawTx.wait();
console.log("withdraw: ", withdrawTx);
setWithDrawSuccessMsg(true);
setWithDrawAmountInfo(data.get(("amount")));
} catch (error) {
console.log("error", error);
setErrorDexWithdraw(true);
}
};
@bjamRez ok so its 12 oclock here so i will have read and refactor your code tomorrow. i will do it in a shaion that i use to set up web3 and main page which is very good and uses web3 react. if you dont want to wait for me to get up and refactor you code tomorrow then i can point you to the useAuth.js file in the repo of my current project. the file is in src/hooks/useAuth.js. the connect function int his file is the impelemented in my web3 modal file located at src/components/web3Modal/web3Modal.js. it might be hard to understand cos its a bit advanced react but ill refactor your two files above fro u tomorrow to show u how to make a really cool connect wallet button that presists and the initiation of contracts on page load
thanks - sorry - did not know the time over there
i can wait
no your fine its no worries at all. i will chat tomorrow
hey @bjamRez i cloned your code and made the changes i mentioned regaring the issue you were having. i made a few changes so i created an explination video fo ryou ill post in now
Link to YT vid explaining web3 % smart contract connection best practices in react
https://youtu.be/fhXdFqudy_w
let me know if you rather make me a Pull request to your git repo or put the files in a post here. if your not used to PR i can also make a post which other ppl here can see and maybe if there doing front end it could be useful fo them also
Wow man - I have ways to go. Thank you!
I’ll check it out once I get home but me it might be better and okay to post the changes here in the forum….that way others can benefit from it as well. I have some pull requests before but I would like to code what you’ve done my self and try to replicate it. I can’t wait.
@mcgrane5 nice! just watched the video - I’ll try that right now and let you know if I get stuck or just to give you an update
cool, im in the middle of writing the post for here also
@mcgrane5 so I tried the code, good stuff, definitely more advanced. I am getting an error though and can’t figure it out - it says account is not defined
ERROR in src\App.js
Line 57:77: 'account' is not defined no-undef
Line 60:16: 'account' is not defined no-undef
Search for the keywords to learn more about each error.
webpack 5.70.0 compiled with 1 error and 1 warning in 709 ms
here is the init at the top of the App.js file
import { useWeb3React } from "@web3-react/core";
import { getContract } from './utils/utils';
function App() {
const [contract, setContract] = useState(null);
const { library } = useWeb3React(); // import library
useEffect(() => {
if (library) {
// to init a contract
const dexContract = getContract(dexContractAddress, Dex.abi, library, account);
setContract(dexContract);
}
}, [library, account]);
this is the useWeb3.js
import React, { useEffect } from 'react';
import { UnsupportedChainIdError, useWeb3React } from "@web3-react/core";
import { InjectedConnector } from '@web3-react/injected-connector';
export const injected = new InjectedConnector({ //instantiate connector
supportedChainIds: [1, 2, 3, 4, 42, 1337, 43114], // multi chain injector
});
// PERSISTANT CONNECTION
function useWeb3() {
var { active, account, library, activate, deactivate } = useWeb3React(); // web3.eth.balance ? ... not sure or library.getBalance
// web3 react hooks
// active is boolean(connected or not), account is account, library is provider, getBalance
// activate connects to Web3 and deactivate, disconnects Web3
var disconnect = React.useCallback(() => {
// useCallback is preventing re-renders
try {
deactivate();
localStorage.removeItem('provider'); // remove provider from local storage
} catch (err) {
console.error(err);
}
}, [deactivate]);
// use network polling intervals to warn user their offline
const connectOnLoad = React.useCallback(() => {
var provider = localStorage.getItem("provider");
if (provider == null) return // if null, we don't connect on load
activate(injected, undefined, true).catch((error) => {
if (error instanceof UnsupportedChainIdError) { // check for unsupported chains
activate(injected) // if chain is supported, activate
} else {
disconnect();
localStorage.setItem("provider", true);
}
})
}, [activate, disconnect]);
useEffect(() => {
if (!library) {// will run when library is defined
connectOnLoad() // it will need a provider to connect
}
}, [library, connectOnLoad, account]);
function connectOn() {
activate(injected, undefined, true).then(() => {
localStorage.setItem("provider", true); // once it activates; it sets provider to true to local storage
})
.catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(injected.connector);
localStorage.setItem("provider", true)
} else {
disconnect();
localStorage.removeItem("provider");
}
});
};
return { connectOnLoad, disconnect, connectOn }
};
export default useWeb3;
here is the utils.js
import { ethers } from "ethers";
import UnCheckedJsonRpcSigner from "./signer";
export function isAddress(value) {
try {
return ethers.utils.getAddress(value.toLowerCase())//make sure it's an address
} catch {
return false;
}
}
export function getProviderOrSigner(library, account) {
return account ? new UnCheckedJsonRpcSigner(library.getSigner(account)) : library
}
export function getContract(address, ABI, library, account) {
// making sure Eth address starts with 0
if (!isAddress(address) || address === ethers.constants.AddressZero) {
throw Error(`Invalid "address" parameter ${address}`);
}
return new ethers.Contract(address, ABI, getProviderOrSigner(library, account));
// return new contract
}
signer is just the boiler plate
and here is the Header.jsx
import React from 'react';
import useWeb3 from '../hooks/useWeb3';
import styled from 'styled-components';
import { useWeb3React } from "@web3-react/core";
export const StyledHeader = styled.h1`
text-align: left;
max-width: 730px;
`
export const Wrapper = styled.section`
padding: 4em;
`;
const HomeConnectButton = ({ height, width, text, click, disconnect }) => {
const { active, account } = useWeb3React(); // it controls the display-state of the button
// if active; it displays account of user
// if not connected to Web3, we display a connect wallet button
// if we are connected and we display account, then we can disconnect
return (
<>
{ !active
? <button width={width} height={height} onClick={click}>{text}</button>
: <button width={width} height={height} onClick={disconnect}>{account?.substring(0, 7)}...{account?.substring(account?.length - 5)}</button>
}
</>
)
}
function Header() {
const { connectOn, disconnect } = useWeb3(); // imported hooks from useWeb3 (disconnect function and connectOn function)
return (
<div className='container-1'>
<div className='box-1'>
<main className="mt-4 p-4">
<Wrapper>
<StyledHeader className="text-xl font-semibold text-info text-left">
Interact with ERC20 Smart Contracts and DEX UI
</StyledHeader>
<HomeConnectButton width="200px" height="50px" click={connectOn} disconnect={disconnect} text={"Connect Wallet"}></HomeConnectButton>
</Wrapper>
<p><small className="text-muted">Read and write from any ERC20 Token smart contract on the Robsten testnet blockchain. Use approve, transfer, transfer from and receive event messages from the blockchain.</small> </p>
<br />
<a className="nav-link text-info" href='https://ropsten.etherscan.io/token/0xe4b6351dc44f54e5cbbbe9008f06fa253001bcfb'>Link to my ERC20 RETK token contract </a>
</main>
</div>
</div>
);
};
export default Header;
in app.js you neeed to destructutre account from the useReact hook. you currently have in your app.js
const { library } = useWeb3React(); // import library
but you need
const { library, account } = useWeb3React(); // import library
Ok people, this post might not be useful for most ppl, but for those of who who are absolutley crushing it like @bjamRez and have decided to come back to make a frontent for this project, then i have some tips and tricks and just generally best practices to share to you in regards to coding UI’s in React.js. It can be tricky to set up a web3 provider in any web3 project because it gets annoying to have to resetup everything everytime you refresh the page or even worse it gets super annoying to keep having to set up your provider each time when you have different files. And in React, your project can get big, with many files very quickily.
How are we going to solve this
we are going to be using a very popular library for setting up web3 providers and wallets called Web3React. this library was created by one of the uniswap frontent Devs Noah Zinsmeisterand its a really cool and easy to use library to allow you to let yousers connect and diconnect their wallets from your Dapp. This library is used in the top Dapps today like uniswap, pancakeswap, 1inch and many others so i consider it to be a best practice when coding Dapps. if your intrested to the repo, its open source of course and heres the link
https://github.com/NoahZinsmeister/web3-react
What are we Doing With This Example
In this post im going to explain how to make a connect button that once clicked asks users if they want to connect to our site with metamask. once they are connected the button will display the users wallet address. Once logged in, if a user clicks the button again, then they get disconnected from metamask and logged out. heres a little screenshot of the different button states we will be making
Pre-Requisites & Required Packages
In order to be able to use Web3React we need to add it as a dependancy to our project. this means we either have to npm
or yarn
install the library depednng on which package manager you are using. So to get going we will install two pacakges assocated with web3React
.
npm install @web3-react/core
//or if using yarn
yarn install @web3-react/core
so this library is the core library that we will be using and it contains all of the functions like activate()
and deactivate()
that we will use to actually set up our provider and connect to metamask. It also contains other useful vairiables such as active
which returns a boolean valued based on if were connected or not and library
which is essentially our web3 onject and allows us to query things like the balance of a user or the blocknumber solibrary.getBalance()
and library.getBlockNumber()
. the library is akin to the notation of web3.eth.
if using web3.js or ethers.
if using ethers.js.
we also need the metamask injected connector which is also part of the web3React library. to install this you can run the following commands for npm or yarn
npm install @web3-react/injected-connector
//or if using yarn
yarn install @web3-react/injected-connector
the last thing we need is the actuall web3 library we are going to use as our provider. I am going to choose ethers.js over web3.js just because i like it better. T onstall etehrs again, run the following commands in your terminal
npm install ethers
//or if using yarn
yarn install ethers
and finally we need to install styled-compoents. ill explain. these are used for making custom html elements and are the best way to style react. to isnatll run
npm install styled-components
//or if using yarn
yarn install styled-components
So now that we understand a little about web3React and what it provides we can continue on. There is actually two more libraries that we need to install before getting started. Those are the react styledComponents library and the react-router-dom library. Styled components are just reall useful ways of creating custom HTML elements. you will see below
Setting up Web3React
Once you have installed all of the packages above we can finally begin. But we need to first set up web3React. to do this we need to wrap our intire project inside the web3ReactProvider. this is because web3React is like a context, meaning that in order to use it we need to wrap our entire app with its provider. The reason we do this is so that we have access to the web3React state everywhere in our project out of the box. Context and shared globabl state is complex so thats all i will comment on as its not the important thing about this post. but if you intrested about how context works in React, read this post
https://www.loginradius.com/blog/engineering/react-context-api/#:~:text=The%20React%20Context%20API%20is,to%20state%20management%20using%20Redux.
So knowing this we first need to naviagte to our index.js
file and in that file we import both web3React and our metamaskConnector
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import './App.css';
import { Web3ReactProvider } from "@web3-react/core";
import { Web3Provider } from "@ethersproject/providers";
function getLibrary(provider) {
const library = new Web3Provider(provider);
library.pollingInterval = 8000;
return library;
}
ReactDOM.render(
<React.StrictMode>
<Web3ReactProvider getLibrary={getLibrary}>
<App />
</Web3ReactProvider>
</React.StrictMode>,
document.getElementById('root')
);
your index.js file should now look like this. You can see that we have imported
import { Web3ReactProvider } from "@web3-react/core";
import { Web3Provider } from "@ethersproject/providers";
from web3React and ethers and we have wrapperd our app compoennt in the Web3ReactProvider. However youmay notice one other function here called getLibrary. what is this function doing? wel the web3React library is very flexible and it lets us choose if we want out web3 library to be ethers or web3.js. so this function essentially just sets up our web3 object so that we can pass it into web3React. I prefer ethers.js myself so we will use that for this example. The getLibrary function simply just takesin our provider and used this to create a new web3 object using ethers and thats it. we then pass this function as a prop to the web3ReactProviders dependancy getLibrary. and once you done this, then hey presto your ready to start using web3React.
Setting Up our useWeb3.js hook
ok so now that we have all of the boilerplate setup we can finally begin to make our web3Auth file where we will handle things like connecting and siconnecting from metamask. So navigate to the src/
folder in your project and in here make a new folder called hooks. in this hooks folder make a file called useWeb3Auth.js. so the path to this file should look like src/hooks/useWeb3Auth.js
. In this file we will start by importing the useWeb3Reaact hook and also the web3React metamask connector. so your file should now look like this
useWeb3Auth.js
import React, { useEffect } from 'react';
import { useWeb3React, UnsupportedChainIdError } from "@web3-react/core"
import { InjectedConnector } from '@web3-react/injected-connector';
const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42, 1337, 43114],
})
the injected variable here is just a method that we use to make an instance of the metamask wallet connector. We use this connector to avtually connect to metamask from our react project and trigger the metamask popup which asks us if we want to connect to the site. The only parameter were passing in here is the chains we want out app to be able to support. This is one real cool thing about web3React and we can just pass in the Id of the chains we want and booom our app will work on this chains.
Next we need to make a function which will host our authentication logic. We will call it useWeb3Auth and it will have 3 main functions. the first is connect which allows the user to connect to metamask when they click some connect button. The second function is connectOnLoad() which we will use to automatically connect to metamask whenever the page is refreshed. and finally a disconnect() function, which we will use to log out the user and disconnect from metamsk.
Ok so lets begin with the function. the snipet below shows the first few pieces of logic
function useWeb3() {
var { active, account, library, activate, deactivate } = useWeb3React()
function connectOn() {
activate(injected, undefined, true).then(() => {
localStorage.setItem("provider", true)
})
.catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(injected.connector)
localStorage.setItem("provider", true)
} else {
disconnect()
localStorage.removeItem("provider")
}
})
}
const disconnect= () => {
try {
deactivate()
localStorage.removeItem("provider")
} catch (err) {
console.error(err)
}
}
the first thing to see here is the insitation of the useWeb3React
hook. From this hook we destructure the following vars
activate() => function that connects to metamask
deactivate() => function that disconnects from metamask
active => boolean value that returns true of flase based on if we are connected to metamask
library => the library we use to react blockchain functions (we set this up to be ethers in index.js)
account => holds the current user account (null if not connected)
so the first function that i define is a Connect function. This function uses the activate()
function provided by web3React to allow us to connect to metamask. We will call our Connect function through a button click event handler later. For now in this function we simple just call the activate() function and pass in our metamask connect injected variable which we defined above. This will allow us to connect to metamask. The other two params are not important here ad just pass them in the sam as i have. namely injected, undefined, true
.
So if our connection is successful then we set a connectorId item to locatstorage. This item will be used to deterimen if we currently have an active session. So what i mean is, by setting this connectorId to local storage, then it means we have an active session. therefore if we refresh the page in our connectOnLoad function (which we will code below) then we can check if the connectorId exists in local storage. If it does then we know we are in an active session and we can call activate()
on page load. However if there is no connectorId then it measn we dont have an active session so we do not connect on page refresh. Also every time we disconnect we will remove the connectorId from local storage.
The last part here is just a catch block to make sure we handle errros correctly. Basically we are checking if we have an unsprpported chainId error if we do we try to connect again, else we just disconnect.
Our disconnect function is even simpler. Alls we do is call the deactivate()
function and remove the connectorId from local storage. Ok so the last thing now is our connectOnLOad()
function. The code for this is given below
const connectOnLoad = () => {
var provider = localStorage.getItem("provider")
if ( provider == null) return
activate(injected, undefined, true).catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(injected)
} else {
disconnect()
localStorage.setItem("provider", true)
}
})
}
as you can see its very similar to our connect() function but the only difference is that we are making a check on entering the function that if we dont have a connectorId in local storage then it means we dont have an active session so we just return straight away out of the function. This will prevent us from connecting to metamask if the user has logged out. Ok but now you may be wondering, well where do we call this connectOnLoad() function. Well we do it in a useEffect hook. Doing so will run this bit of code every time we refresh our page. the code for the useEffect is given below
useEffect(() => {
if (!library) {
connectOnLoad()
}
}, [library, account])
note here that we are not jyst directly calling connectOnLOad, but ratehr we are waiting until the library is defined. We this is because sometimes we can get an error by calling ether activate() or deactivate() if the library is not defined. This is because we need to library to actually interact with blockchain functions. so a simple work around is to just wait until our web3 provider is defined and then call connect on load. (note on page refresh it only takes less than a second usually until the provder or library is defined)
and thats it thats the entire code fo rthis file. It may seem hard if your new to react but its actually super simple in comparsion if we were to do all of his manually. I will paste the entire file below so you can just copy an paste, but i wanted to break things down above one by one so ppl can undersatnd better what the code is doing
useWb3Auth.js
import React, { useEffect } from 'react';
import { useWeb3React, UnsupportedChainIdError } from "@web3-react/core"
import { InjectedConnector } from '@web3-react/injected-connector';
export const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42, 1337, 43114],
})
function useWeb3Auth() {
var { active, account, library, activate, deactivate } = useWeb3React()
function connectOn() {
activate(injected, undefined, true).then(() => {
localStorage.setItem("provider", true)
})
.catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(injected.connector)
localStorage.setItem("provider", true)
} else {
disconnect()
localStorage.removeItem("provider")
}
})
}
const disconnect= () => {
try {
deactivate()
localStorage.removeItem("provider")
} catch (err) {
console.error(err)
}
}
//use network pollinhg intervak to warn usr their offline
const connectOnLoad = () => {
var provider = localStorage.getItem("provider")
if ( provider == null) return
activate(injected, undefined, true).catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(injected)
} else {
disconnect()
localStorage.setItem("provider", true)
}
})
}
useEffect(() => {
if (!library) {
connectOnLoad()
}
}, [library, account])
return { connectOnLoad, disconnect, connectOn}
}
export default useWebAuth3
Setting ip our Connect Button
ok so youve finally made it to the last and easiest section in this post. here were going to actually make button which we will use to let the user connect and disconnect from metamask. so to do so we will navigate to App.js in this file we need to import styledComponets so we can make our custom button. We are also going to import the useWeb3React hook that we used in our auth file above and lastly we will also import out useWeb3Auth file that we just made so at the topp of app.js, add these imports
import React from 'react';
import styled from 'styled-components';
import useWeb3 from '../hoks/useWeb3';
import { useWeb3React } from '@web3-react/core';
next we are going to write the CSS four our connect button. the CSS is not important here fo rus as were focusiing on the logic rather than the styles. so just simply copy and paste the CSS i have provided below
export const ButtonWrapper = styled.div`
display: flex;
align-items: center;
margin-top: 30px;
justify-content: space-between;
`
export const NavButton2 = styled.div`
font-family: 'Open Sans', sans-serif;
display: flex;
align-items: center;
justify-content: center;
line-style: none;
background: rgb(13,94,209);
width: 200px;
border-radius: 18px;
height: 50px;
text-align: center;
line-height: 50px;
color: White;
font-size: 16px;
text-decoration: none;
&:hover {
cursor: pointer;
background: rgb(0,80,195);
}
`
ok so now that we have done this we can start to create our Button. To do so we will create a functional compoennt called ConnectButton. in this component we will destructure active and account the useWeb3React hook so that we can change the display state of the button depending on if the users is connected to metamask or not. The code for this compoennt looks as follows
const ConnectButton = ({ click, disconnect}) => {
const { active, account } = useWeb3React()
return (
<>
{ !active
? <NavButton2 onClick={click}>
Connect Wallet
</NavButton2>
: <NavButton2 onClick={disconnect}>
{account?.substring(0, 7)
+ "..." +
account?.substring(account?.length - 5)}
</NavButton2>
}
</>
)
}
ok so this may look very complicated if your not used to styled-components or react but its not i promise. so the function takes in the connect() and disconnect() functions that we created in our useWeb3Auth,js file. then we destructure the active and account vars from the useWeb3React hook. The reason we need the account var is so we can display it as the text in our button when its active. And the reason we need the active var is so we can use it to change the display state of our button.
so in this functional component we return the navbutton which we styled above. We are now going to use whats called a turnary operator to decide on which button state to display. a turnary operator is just a shorthand way of doing an if else statement and there are commonly used in react to handle state. so for example we can explain a turnary operator below
var greeting = false
greeting ? ""hello" : "goodbye"
this means that if our variable greeting is true, then return the string “hello” whereas if its false return the string “goodbye”. so if you still are having trouble the above statement is the exact saem thing as
var greet = false
if (greeting == true) {
return "hello"
} else {
return "goodbye"
}
so we will use this idea of a turnary operator to decide which text gets rendered in our button. Namely Connect Wallet or our wallet Address. And the boolean variable we will use to make this decision will be the active variable destructured from the useWeb3React hook. So lets look at it more closely
return (
<>
{ !active
? <NavButton2 onClick={click}>
Connect Wallet
</NavButton2>
: <NavButton2 onClick={disconnect}>
{account?.substring(0, 7)
+ "..." +
account?.substring(account?.length - 5)}
</NavButton2>
}
</>
)
so this means if active == false or in other words, if we are not connected to metamask then return a button which displays the text connect Wallet, however if active == true meaning that we ARE connected to metamask then return the same button only this time render the users wallet address. Dont worry about the crazy account notation, alls this is doing is taking the 64 length wallet address and shortening it into the form of 0x1234...5678
so that it can fit inside out button wthout overflowing.
So thats it. The last thing to note is that, if active == fasle and we are displaying the button with Connect Wallet, then we add an onllick handler which calls our Connect() function. What this will dois change the state of the active variable to now be active = true which will cause our button to now change to display the users account.
And when this version of the button is rendered then clicking it will disconnect from metamsk and the resvers thing will happen. active will go from active = true to active = false and thus casuse the first version on the button to get rendered againn.
And thats it all we have to do now is to take this compoent and put it inside the return statement in our **App ** compoent. so our final App.js file will now look like
import React, { useState } from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
import useWeb3 from '../hoks/useWeb3';
import { useWeb3React } from '@web3-react/core';
export const ButtonWrapper = styled.div`
display: flex;
align-items: center;
margin-top: 30px;
justify-content: space-between;
`
export const NavButton2 = styled.div`
font-family: 'Open Sans', sans-serif;
display: flex;
align-items: center;
justify-content: center;
line-style: none;
background: rgb(13,94,209);
width: 200px;
border-radius: 18px;
height: 50px;
text-align: center;
line-height: 50px;
color: White;
font-size: 16px;
text-decoration: none;
&:hover {
cursor: pointer;
background: rgb(0,80,195);
}
`
export const StyledHeader = styled.h1`
text-align: left;
max-width: 730px;
`
const ConnectButton = ({ click, disconnect}) => {
const { active, account } = useWeb3React()
return (
<>
{ !active
? <NavButton2 onClick={click}>
Connect Wallet
</NavButton2>
: <NavButton2 onClick={disconnect}>
{account?.substring(0, 7)
+ "..." +
account?.substring(account?.length - 5)}
</NavButton2>
}
</>
)
}
function App() {
const { connectOn, disconnect } = useWeb3()
return (
<>
<ButtonWrapper>
<ConnectButton click={connectOn} disconnect={disconnect}></ConnectButton>
</ButtonWrapper>
</>
);
};
export default App;
There is just one final thing to note. In App we destructure the Connect and disconnect functions we wrote and pass these at the props to the actual component. And boom thats it.
So if you enjoyed this post please let me know and if you have any issues let me know aswell ill gladly awnser.
@mcgrane5 good morning there. I did try that. Deconstructed account by adding account, next to library like in your example but I got an error in the browser saying something like no web3 provider. I’ll try it again and see what’s the deal. Amazing work by the way with the video and the tutorial you just posted. This will really help myself and anyone else who wants to dig deeper
no web 3 provider, this probably mans that you didnt wrap your app.j sin the correct web3Reatc provider. because it shere where you pass in ethers as your web3 provider
@mcgrane5 hey, so it took me a while but I am finally understanding this new setup with web3React - yeah it’s pretty cool - it was tricky for me since there are two contracts, the DEX and the ERC20 token contract so I was really confused at first but I think I am getting it now – thanks to you
here is a rough and dirty print out so I’ll keep working on this
p.s. - also, I got rid of the get info, get balance buttons - with useEffect, I just call them like you said
The following is my code for the Dex project. I tried to do the coding myself without peeking at the solutions first. I was largely successful, but there are few differences as follows.
- Contract names are different, just a cosmetic difference.
- In my version of the placeMarketOrder function, I update the opposing limit order book and remove each order as it is filled rather wait until the market order is finished being fulfilled and going back to remove filled limit orders.
- For market buy order, I check the ETH cost of the total order and verify there is an adequate buyer ETH balance, rather than check the ETH cost for each limit order. I implemented a view function called getBuyMarketOrderCost to do this.
- There may be a few other differences like using a while function where Filip would use a for function etc.
I have not created a front end yet for the project but I plan to. This course was fun and informative. I am now trying to decide between the front end course with Java script or the Ethereum smart contract security course for my next stop. I will do both either way. Thanks Filip and Team!
MercuryWallet.sol
pragma solidity 0.8.12;
import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
contract mercuryWallet is Ownable {
using SafeMath for uint256;
struct Token {
bytes32 ticker;
address tokenAddress;
}
mapping(bytes32 => Token) public tokenMapping;
bytes32[] public tokenList;
//mapping of user address to token symbol (bytes32) to token balance.
mapping(address => mapping(bytes32 => uint256)) public balance;
modifier tokenExists(bytes32 _ticker) {
require(tokenMapping[_ticker].tokenAddress != address(0), "Token does not exist");
_;
}
function addToken(bytes32 _ticker, address _tokenAddress) onlyOwner external {
tokenMapping[_ticker] = Token(_ticker, _tokenAddress);
tokenList.push(_ticker);
}
function deposit(uint _amount, bytes32 _ticker) tokenExists(_ticker) external {
IERC20(tokenMapping[_ticker].tokenAddress).transferFrom(msg.sender, address(this), _amount);
balance[msg.sender][_ticker] = balance[msg.sender][_ticker].add(_amount);
}
function withdraw(uint _amount, bytes32 _ticker) tokenExists(_ticker) external {
require(balance[msg.sender][_ticker] >= _amount, "Insufficient balance");
balance[msg.sender][_ticker] = balance[msg.sender][_ticker].sub(_amount);
IERC20(tokenMapping[_ticker].tokenAddress).transfer(msg.sender, _amount);
}
function depositEth() external payable {
balance[msg.sender][bytes32("ETH")] = balance[msg.sender][bytes32("ETH")].add(msg.value);
}
function withdrawEth(uint _amount) external payable {
require(balance[msg.sender][bytes32("ETH")] >= _amount, "Insufficient Eth balance");
balance[msg.sender][bytes32("ETH")] = balance[msg.sender][bytes32("ETH")].sub(_amount);
payable(msg.sender).transfer(_amount);
}
function getEthBalance() public view returns(uint){
return balance[msg.sender]["ETH"];
}
}
MercuryDex.sol
pragma solidity 0.8.12;
pragma experimental ABIEncoderV2;
import "./MercuryWallet.sol";
/*
No need to import SafeMath again, because it was imported in parent contract.
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol"
*/
contract mercuryDex is mercuryWallet {
using SafeMath for uint256;
enum orderSide {
BUY,
SELL
}
struct Order {
uint id;
address trader;
orderSide side;
bytes32 ticker;
uint amount;
uint price;
uint filled;
}
//Each order will have a unique id
uint nextOrderID;
//The mapping of the order book.
//mapping from asset ticker to buyside/sellside (0 or 1) orderbooks to array of orders in specified side
mapping(bytes32 => mapping(uint => Order[])) public orderBook;
function getOrderBook(bytes32 _ticker, orderSide _side) view public returns(Order[] memory) {
return orderBook[_ticker][uint(_side)];
}
function placeLimitOrder(orderSide _side, bytes32 _ticker, uint _amount, uint _price) public tokenExists(_ticker) {
if(_side == orderSide.BUY) {
require(balance[msg.sender]["ETH"] >= _amount.mul(_price), "Insufficient Eth Balance");
}
else if(_side == orderSide.SELL) {
require(balance[msg.sender][_ticker] >= _amount, "Insufficient token balance");
}
uint filled = 0;
Order memory newOrder = Order(
nextOrderID,
msg.sender,
_side,
_ticker,
_amount,
_price,
filled
);
//Creates direct reference to orderBook in storage
Order[] storage orders = orderBook[_ticker][uint(_side)];
orders.push(newOrder);
//Sort order book using modified bubble sort.
if(_side == orderSide.BUY) {
uint len = orders.length;
for (uint i = len - 1; i > 0; i--) {
if (orders[i].price > orders[i -1].price) {
Order memory temp = orders[i];
orders[i] = orders[i -1];
orders[i -1] = temp;
} else {
break;
}
}
}
else if (_side == orderSide.SELL) {
uint len = orders.length;
for (uint i = len - 1; i > 0; i--){
if (orders[i].price < orders[i - 1].price) {
Order memory temp = orders[i];
orders[i] = orders[i -1];
orders[i -1] = temp;
} else {
break;
}
}
}
nextOrderID++;
}
function placeMarketOrder(orderSide _side, bytes32 _ticker, uint _amount) public tokenExists(_ticker) {
//variable to track how much of market order is filled.
uint unfilled = _amount;
uint limUnfilled;
if (_side == orderSide.SELL) {
require(balance[msg.sender][_ticker] >= _amount, "Insufficient token balance for SELL market order.");
Order[] storage orders = orderBook[_ticker][uint(orderSide.BUY)];
//If order book is empty, end market order process by setting unfilled = 0.
if (orders.length == 0) {
unfilled = 0;
}
while(unfilled > 0) {
//Variable to track unfilled limit order
limUnfilled = orders[0].amount.sub(orders[0].filled);
if (unfilled >= limUnfilled) {
//Update balances for seller
balance[msg.sender][_ticker] = balance[msg.sender][_ticker].sub(limUnfilled);
balance[msg.sender]["ETH"] = balance[msg.sender]["ETH"].add(limUnfilled.mul(orders[0].price));
//Update balances for buyer
balance[orders[0].trader][_ticker] = balance[orders[0].trader][_ticker].add(limUnfilled);
balance[orders[0].trader]["ETH"] = balance[orders[0].trader]["ETH"].sub(limUnfilled.mul(orders[0].price));
//Update order status
orders[0].filled = orders[0].filled.add(limUnfilled);
unfilled = unfilled.sub(limUnfilled);
//rearrange orderBook and remove filled order slot
for (uint j = 0; j < orders.length-1; j++) {
orders[j] = orders[j+1];
}
orders.pop();
//If order book is empty, end market order process by setting unfilled = 0.
if (orders.length == 0) {
unfilled = 0;
}
} else if (unfilled < limUnfilled) {
//update balances for seller
balance[msg.sender][_ticker] = balance[msg.sender][_ticker].sub(unfilled);
balance[msg.sender]["ETH"] = balance[msg.sender]["ETH"].add(unfilled.mul(orders[0].price));
//Update balances for buyer
balance[orders[0].trader][_ticker] = balance[orders[0].trader][_ticker].add(unfilled);
balance[orders[0].trader]["ETH"] = balance[orders[0].trader]["ETH"].sub(unfilled.mul(orders[0].price));
//Update order status
orders[0].filled = orders[0].filled.add(unfilled);
unfilled = unfilled.sub(unfilled);
}
}
} else if (_side == orderSide.BUY) {
Order[] storage orders = orderBook[_ticker][uint(orderSide.SELL)];
//If order book is empty, end market order process by setting unfilled = 0.
if (orders.length == 0) {
unfilled = 0;
}
//Verify buyer has enough ETH.
require(getBuyMarketOrderCost(_ticker,_amount) <= balance[msg.sender]["ETH"], "Insufficient ETH for BUY market order.");
while (unfilled > 0) {
//Variable to track unfilled limit order
limUnfilled = orders[0].amount.sub(orders[0].filled);
if (unfilled >= limUnfilled) {
//update balances for buyer
balance[msg.sender]["ETH"] = balance[msg.sender]["ETH"].sub(limUnfilled.mul(orders[0].price));
balance[msg.sender][_ticker] = balance[msg.sender][_ticker].add(limUnfilled);
//update balances for seller
balance[orders[0].trader]["ETH"] = balance[orders[0].trader]["ETH"].add(limUnfilled.mul(orders[0].price));
balance[orders[0].trader][_ticker] = balance[orders[0].trader][_ticker].sub(limUnfilled);
//update order status
orders[0].filled = orders[0].filled.add(limUnfilled);
unfilled = unfilled.sub(limUnfilled);
//rearrange orderBook and remove filled order slot
for (uint j = 0; j < orders.length-1; j++) {
orders[j] = orders[j+1];
}
orders.pop();
//If order book is empty, end market order process by setting unfilled = 0.
if (orders.length == 0) {
unfilled = 0;
}
} else if (unfilled < limUnfilled) {
//update balances for buyer
balance[msg.sender]["ETH"] = balance[msg.sender]["ETH"].sub(unfilled.mul(orders[0].price));
balance[msg.sender][_ticker] = balance[msg.sender][_ticker].add(unfilled);
//update balances for seller
balance[orders[0].trader]["ETH"] = balance[orders[0].trader]["ETH"].add(unfilled.mul(orders[0].price));
balance[orders[0].trader][_ticker] = balance[orders[0].trader][_ticker].sub(unfilled);
//update order status
orders[0].filled = orders[0].filled.add(unfilled);
unfilled = unfilled.sub(unfilled);
}
}
}
}
function getBuyMarketOrderCost(bytes32 _ticker, uint _amount) internal view returns(uint) {
//Pulls SELL order book for _ticker, and checks total Eth cost to fulfill BUY market order.
uint ethCost;
uint unfilled = _amount;
Order[] storage orders = orderBook[_ticker][uint(orderSide.SELL)];
//require(orders.length > 0, "No sellers available.");
for (uint i = 0; i < orders.length && unfilled > 0; i++) {
uint limUnfilled = orders[i].amount.sub(orders[i].filled);
if(unfilled > limUnfilled) {
ethCost = ethCost.add(limUnfilled.mul(orders[i].price));
unfilled = unfilled.sub(limUnfilled);
} else if(unfilled < limUnfilled){
ethCost = ethCost.add(unfilled.mul(orders[i].price));
unfilled = unfilled.sub(unfilled);
}
}
return ethCost;
}
}
TestToken.sol
pragma solidity 0.8.12;
import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract testToken is ERC20 {
constructor() ERC20("Test_Token", "TSTT") public {
_mint(msg.sender, 10000);
}
}
MercWalletTest.js
pragma solidity 0.8.12;
import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract testToken is ERC20 {
constructor() ERC20("Test_Token", "TSTT") public {
_mint(msg.sender, 10000);
}
}
LimitOrderTest.js
const mercuryDex = artifacts.require("mercuryDex");
const testToken = artifacts.require("testToken");
const truffleAssert = require('truffle-assertions');
contract.skip("mercuryDex", accounts => {
//The user should have enough Eth to cover any buy order.
it("should handle inadequate Eth for limit BUY order correctly", async () => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
await mercDex.addToken(web3.utils.fromUtf8("TSTT"), testa.address);
await truffleAssert.reverts(
//side, ticker, amount, price
mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 10, 10)
);
await mercDex.depositEth({value: 1000});
//Show eth balance in console.
console.log(
(await mercDex.balance(accounts[0], web3.utils.toHex("ETH"))).toNumber()
);
await truffleAssert.passes(
//side, ticker, amount, price
mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 10, 10)
);
})
//The user should have enough tokens to satisfy any sell order.
it("should handle inadequate tokens for limit SELL order correctly", async () => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
await truffleAssert.reverts(
//side, ticker, amount, price
mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 10, 10)
);
await testa.approve(mercDex.address, 500);
await mercDex.deposit(500, web3.utils.fromUtf8("TSTT"));
await truffleAssert.passes(
//side, ticker, amount, price
mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 500, 10)
);
})
//The buy orderbook should be arranged from highest to lowest price, with highest price at index [0].
it("should arrange BUY orderbook from highest to lowest price", async () => {
let mercDex = await mercuryDex.deployed();
await mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 10, 1);
await mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 10, 3);
await mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 10, 2);
let buyOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 0);
assert(buyOrderBook.length > 0, "OrderBook has not been created");
for (let i = 0; i < buyOrderBook.length - 1; i++) {
assert(parseFloat(buyOrderBook[i].price) >= parseFloat(buyOrderBook[i + 1].price), "Incorrect order in buy orderBook.");
}
})
//The sell orderbook should be arranged from lowest to highest price, with lowest price at index [0].
it("should arrange SELL orderbook from lowest to highest price", async () => {
let mercDex = await mercuryDex.deployed();
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 10, 1);
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 10, 3);
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 10, 2);
let sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
assert(sellOrderBook.length > 0, "OrderBook has not been created");
for (let i = 1; i < sellOrderBook.length; i++) {
assert(parseFloat(sellOrderBook[i].price) >= parseFloat(sellOrderBook[i-1].price), "Incorrect order in sell orderBook.");
}
})
})
MarketOrderTest.js
const mercuryDex = artifacts.require("mercuryDex");
const testToken = artifacts.require("testToken");
const truffleAssert = require('truffle-assertions');
contract.skip("mercuryDex", accounts => {
//should error if seller does not have enough tokens for SELL market order.
it("should error if seller does not have enough tokens for SELL market order.", async() => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
await mercDex.addToken(web3.utils.fromUtf8("TSTT"), testa.address);
let balance = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
assert(balance == 0, "Initial token balance is not 0");
await truffleAssert.reverts(
//side, ticker, amount
mercDex.placeMarketOrder(1, web3.utils.toHex("TSTT"), 100)
)
})
//should error if buyer does not have enough ETH for BUY market order.
it("should error if buyer does not have enough ETH for BUY market order.", async() => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//deposit tokens to create sell limit orders
await testa.approve(mercDex.address,100);
await mercDex.deposit(100, web3.utils.fromUtf8("TSTT"));
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"),100, 1);
//Confirm accounts[4] has 0 ETH balance.
let balance = (await mercDex.balance(accounts[4], web3.utils.toHex("ETH"))).toNumber();
assert(balance == 0, "Initial token balance is not 0");
//Verify accounts[4] cannot place BUY market order due to inadequate ETH.
await truffleAssert.reverts(
//side, ticker, amount
mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 100, {from:accounts[4]})
)
//Deposit ETH from accounts[4].
await mercDex.depositEth({from:accounts[4], value: 100});
//Verify accounts[4] can now place BUY market order with adequate ETH.
await truffleAssert.passes(
//side, ticker, amount
mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 100, {from:accounts[4]})
)
})
//should allow submission of market orders even if order book is empty.
it("should allow submission of market orders even if the order book is empty.", async() => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
let buyOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 0);
let sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
assert(buyOrderBook.length == 0, "BuyOrderBook is not empty");
assert(sellOrderBook.length == 0, "SellOrderBook is not empty");
//Test BUY market order.
await mercDex.depositEth({value:100});
await truffleAssert.passes(
//side, ticker, amount, suggested price
mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 100)
)
//Test SELL market order.
await testa.approve(mercDex.address,100);
await mercDex.deposit(100, web3.utils.fromUtf8("TSTT"));
await truffleAssert.passes(
//side, ticker, amount, suggested price
mercDex.placeMarketOrder(1, web3.utils.toHex("TSTT"), 100)
)
})
//should allow fulfilment of market orders until the order book is empty or order is 100% filled.
it("should fulfill market orders until order is 100% filled if there is enough liquidity.", async() => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Verify empty order book.
let sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
assert(sellOrderBook.length == 0, "SellOrderBook is not empty");
//Transfer TSTT to accts 1, 2, and 3.
await testa.transfer(accounts[1], 50);
await testa.transfer(accounts[2], 50);
await testa.transfer(accounts[3], 50);
//Approve DEX to handle TSTT for accts 1, 2, and 3.
await testa.approve(mercDex.address, 50, {from: accounts[1]});
await testa.approve(mercDex.address, 50, {from: accounts[2]});
await testa.approve(mercDex.address, 50, {from: accounts[3]});
//Deposit TSTT to DEX from accts 1, 2, and 3.
await mercDex.deposit(50, web3.utils.toHex("TSTT"), {from:accounts[1]});
await mercDex.deposit(50, web3.utils.toHex("TSTT"), {from:accounts[2]});
await mercDex.deposit(50, web3.utils.toHex("TSTT"), {from:accounts[3]});
//Place SELL limit orders of TSTT for accts 1, 2, and 3.
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 50, 2, {from:accounts[1]});
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 50, 1, {from:accounts[2]});
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 50, 4, {from:accounts[3]});
sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
assert(sellOrderBook.length == 3, "SellOrderBook length is not 3 as expected.");
//Store TSTT balance of buyer before BUY market order.
let balance0before = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
//create BUY market order.
await mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 110);
//Store TSTT balance of buyer after BUY market order.
let balance0after = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
//Verify market BUY oder has been filled and SELL orderBook is updated.
assert(balance0after == balance0before + 110, "Market BUY order is not filled correctly.");
assert(sellOrderBook.length == 1, "SellOrderBook length is not 1 as expected.");
assert(parseFloat(sellOrderBook[0].filled) == 10, "Remaining limit sell order filled incorrectly.");
})
//should allow fulfillment of market orders until order book is empty leaving rest of order unfulfilled.
it("should fulfill market order until order book is empty if there is not enough liquidity, leaving remaining order unfulfilled.", async () => {
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Transfer TSTT to accts 1, 2, and 3.
await testa.transfer(accounts[1], 50);
await testa.transfer(accounts[2], 50);
await testa.transfer(accounts[3], 50);
//Approve DEX to handle TSTT for accts 1, 2, and 3.
await testa.approve(mercDex.address, 50, {from: accounts[1]});
await testa.approve(mercDex.address, 50, {from: accounts[2]});
await testa.approve(mercDex.address, 50, {from: accounts[3]});
//Deposit TSTT to DEX from accts 1, 2, and 3.
await mercDex.deposit(50, web3.utils.toHex("TSTT"), {from:accounts[1]});
await mercDex.deposit(50, web3.utils.toHex("TSTT"), {from:accounts[2]});
await mercDex.deposit(50, web3.utils.toHex("TSTT"), {from:accounts[3]});
//Place limit SELL orders.
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 50, 2, {from:accounts[1]});
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 50, 1, {from:accounts[2]});
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 50, 4, {from:accounts[3]});
//Verify current state of SELL orderBook.
sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
assert(sellOrderBook.length == 4, "SellOrderBook does not have expected number of entries.");
//Store TSTT balance of buyer before BUY market order.
balanance0before = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
//Place market BUY order.
await mercDex.depositEth({value:10000});
await(mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 200));
//Store TSTT balance of buyer after BUY market order and verify market BUY order filled correctly.
let balanance0after = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
assert(balanance0after == balanance0before + 190, "Market BUY order filled incorrectly.");
//Verify SELL orderBook updated correctly.
sellOrderBook = await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 1);
assert(sellOrderBook.length == 0, "SellOrderBook is not empty");
})
//should update ETH balances correctly for BUY market orders.
it("should update ETH balances correctly for BUY market orders.", async () =>{
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Transfer TSTT to accts 4 then send TSTT to mercDex and place limit SELL order
await testa.transfer(accounts[4], 100);
await testa.approve(mercDex.address, 100, {from:accounts[4]});
await mercDex.deposit(100, web3.utils.toHex("TSTT"), {from:accounts[4]});
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 100, 1, {from:accounts[4]});
//Store initial ETH balances.
balance0before = (await mercDex.balance(accounts[0], web3.utils.toHex("ETH"))).toNumber();
let balance4before = (await mercDex.balance(accounts[4], web3.utils.toHex("ETH"))).toNumber();
//Place market BUY order.
await mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 100);
//Store buyer ETH balance after market BUY order and verify balance updates.
balance0after = (await mercDex.balance(accounts[0], web3.utils.toHex("ETH"))).toNumber();
let balance4after = (await mercDex.balance(accounts[4], web3.utils.toHex("ETH"))).toNumber();
assert(balance0after == balance0before - 100, "ETH balance not decreased correctly.");
assert(balance4after == balance4before + 100, "ETH balance not increased correctly.");
})
//should update token balances correctly for BUY market orders.
it("should update token balances correctly for BUY market orders.", async () =>{
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Transfer TSTT to accts 4 then send TSTT to mercDex and place limit SELL order
await testa.transfer(accounts[4], 100);
await testa.approve(mercDex.address, 100, {from:accounts[4]});
await mercDex.deposit(100, web3.utils.toHex("TSTT"), {from:accounts[4]});
await mercDex.placeLimitOrder(1, web3.utils.toHex("TSTT"), 100, 1, {from:accounts[4]});
//Store initial token balances.
balance0before = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
balance4before = (await mercDex.balance(accounts[4], web3.utils.toHex("TSTT"))).toNumber();
//Place market BUY order.
await mercDex.placeMarketOrder(0, web3.utils.toHex("TSTT"), 100);
//Store token balances after market BUY order and verify balance updates.
balance0after = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
balance4after = (await mercDex.balance(accounts[4], web3.utils.toHex("TSTT"))).toNumber();
assert(balance0after == balance0before + 100, "TSTT balance not increased correctly.");
assert(balance4after == balance4before - 100, "TSTT balance not decreased correctly.");
})
//should update ETH balances correctly for market SELL orders.
it("should update ETH balances correctly for SELL market orders.", async () =>{
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Transfer TSTT to accts 4 then send TSTT to mercDex and place limit BUY order
await testa.transfer(accounts[4], 100);
await testa.approve(mercDex.address, 100, {from:accounts[4]});
await mercDex.deposit(100, web3.utils.toHex("TSTT"), {from:accounts[4]});
await mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 100, 1);
//Store initial ETH balances.
balance0before = (await mercDex.balance(accounts[0], web3.utils.toHex("ETH"))).toNumber();
balance4before = (await mercDex.balance(accounts[4], web3.utils.toHex("ETH"))).toNumber();
//Place market SELL order.
await mercDex.placeMarketOrder(1, web3.utils.toHex("TSTT"), 100, {from: accounts[4]});
//Store buyer ETH balance after market BUY order and verify balance updates.
balance0after = (await mercDex.balance(accounts[0], web3.utils.toHex("ETH"))).toNumber();
balance4after = (await mercDex.balance(accounts[4], web3.utils.toHex("ETH"))).toNumber();
assert(balance0after == balance0before - 100, "ETH balance not decreased correctly.");
assert(balance4after == balance4before + 100, "ETH balance not increased correctly.");
})
//should update token balances correctly for market SELL orders.
it("should update token balances correctly for SELL market orders.", async () =>{
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Transfer TSTT to accts 4 then send TSTT to mercDex and place limit BUY order
await testa.transfer(accounts[4], 100);
await testa.approve(mercDex.address, 100, {from:accounts[4]});
await mercDex.deposit(100, web3.utils.toHex("TSTT"), {from:accounts[4]});
await mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 100, 1);
//Store initial ETH balances.
balance0before = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
balance4before = (await mercDex.balance(accounts[4], web3.utils.toHex("TSTT"))).toNumber();
//Place market SELL order.
await mercDex.placeMarketOrder(1, web3.utils.toHex("TSTT"), 100, {from: accounts[4]});
//Store buyer ETH balance after market BUY order and verify balance updates.
balance0after = (await mercDex.balance(accounts[0], web3.utils.toHex("TSTT"))).toNumber();
balance4after = (await mercDex.balance(accounts[4], web3.utils.toHex("TSTT"))).toNumber();
assert(balance0after == balance0before + 100, "Token balance not increased correctly.");
assert(balance4after == balance4before - 100, "Token balance not decreased correctly.");
})
//should remove filled limit orders from the orderBook.
it("should remove filled limit orders from the orderBook.", async () =>{
let mercDex = await mercuryDex.deployed();
let testa = await testToken.deployed();
//Transfer TSTT to accts 4 then send TSTT to mercDex and place limit BUY order
await testa.transfer(accounts[4], 100);
await testa.approve(mercDex.address, 100, {from:accounts[4]});
await mercDex.deposit(100, web3.utils.toHex("TSTT"), {from:accounts[4]});
await mercDex.placeLimitOrder(0, web3.utils.toHex("TSTT"), 100, 1);
//Store initial orderBook length.
let buyOrderBookLenIni = (await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 0)).length;
//Place market SELL order.
await mercDex.placeMarketOrder(1, web3.utils.toHex("TSTT"), 100, {from: accounts[4]});
//Store final orderBook length and verify filled order is removed.
let buyOrderBookLenFin = (await mercDex.getOrderBook(web3.utils.toHex("TSTT"), 0)).length;
assert(buyOrderBookLenIni - buyOrderBookLenFin == 1, "Filled order was not removed from orderBook.")
})
})
@mcgrane5 hey Evan, I finally kind of finished the refactoring of the DEX. The web3-React worked really well by the way.
I just wanted to wrap it up and move on but I did move the logic into smaller components but it also got kind of messy as far the useEffect re-renders too many times. I was able to control it in some cases but not in others. I was not able to make all the changes you suggested as the useEffect problem took up most of my time.
Anyways, here is a link to a youTube video I made for the DEX, that too can be improved but for now I just want keep learning and make new projects. Thank you for your help.
DEX video link:
https://youtu.be/-y-ChTrvy-g
In case you want to check out the new code but don’t have to if you are busy…the changes are in not in the master but in the second branch called “DexFeatures1”
https://github.com/brlojam4932/dex2-app.git
**plz HELP TO SLOVE THIS ERROR **
WALLET TEST COSE
const Dex = artifacts.require(“Dex”)
const Link = artifacts.require(“Link”)
const truffleAssert = require(‘truffle-assertions’);
contract(“Dex”, accounts => {
it(“should only be possible for owner to add tokens”, async () => {
let dex = await Dex.deployed();
let link = await Link.deployed();
await truffleAssert.passes(
dex.addToken(web3.utils.fromUtf8(“LINK”), link.address, { from: accounts[0] })
)
})
})
Error: Cannot find module ‘babel-runtime/core-js/object/entries’
Require stack:
- /usr/local/lib/node_modules/truffle/node_modules/reselect-tree/index.js
- /usr/local/lib/node_modules/truffle/node_modules/@truffle/debugger/dist/debugger.js
- /usr/local/lib/node_modules/truffle/build/consoleChild.bundled.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object. (/usr/local/lib/node_modules/truffle/node_modules/reselect-tree/external “babel-runtime/core-js/object/entries”:1:1)
at webpack_require (/usr/local/lib/node_modules/truffle/node_modules/reselect-tree/webpack/bootstrap e958f07978aa008e523d:19:1)
at Object. (/usr/local/lib/node_modules/truffle/node_modules/reselect-tree/index.js:87:16)
at webpack_require (/usr/local/lib/node_modules/truffle/node_modules/reselect-tree/webpack/bootstrap e958f07978aa008e523d:19:1)
at /usr/local/lib/node_modules/truffle/node_modules/reselect-tree/webpack/bootstrap e958f07978aa008e523d:62:1
at /usr/local/lib/node_modules/truffle/node_modules/reselect-tree/index.js:76:10
at webpackUniversalModuleDefinition (/usr/local/lib/node_modules/truffle/node_modules/reselect-tree/webpack/universalModuleDefinition:3:1)
at Object. (/usr/local/lib/node_modules/truffle/node_modules/reselect-tree/webpack/universalModuleDefinition:10:1)
at Module._compile (node:internal/modules/cjs/loader:1101:14)
at Object.Module._extensions…js (node:internal/modules/cjs/loader:1153:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
truffle(develop)> strong text
Hi,
I don’t understand relation to webpack here, but I would highly suggest to remove your node_modules
folder and then run npm i
. Unfortunately there is not enough information about your problem. Could you please format your message with special blocks for code? And describe please the commands you’re running.
Cheers