Project - DEX Final Code

Hey @mcgrane5, sorry mate ive been away from my laptop all weekend, just back on now. Wow, you really went to town trying to help me, appreciate that Ewan. Haha, yeh i did the same thing, copied everything from my original workspace to a new one to see if that would make the package.json less convoluted but didn’t work either.

Ahhh yes that makes sense. Thank you @dan-i, I will implement the solution now. Don’t know what i would do without this forum. It’s a god send.

2 Likes

Hey guys, me again :nerd_face:

So i am trying to add tokens to my DEX and it is not showing LINK and VET tokens that i am trying to add.

I have created new token contracts

1st is ADA:

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract ADA is ERC20 {

    constructor() ERC20("Cardano", "ADA") {
        //mint to msg.sender(us)
        _mint(msg.sender, 100000);
    }

}  

2nd is LINK:

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract LINK is ERC20 {

    constructor() ERC20("Chainlink", "LINK") {
        //mint to msg.sender(us)
        _mint(msg.sender, 100000);
    }

}  

3rd is VET:

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract VET is ERC20 {

    constructor() ERC20("VeChain", "VET") {
        //mint to msg.sender(us)
        _mint(msg.sender, 100000);
    }

}  

Then i made 3 migration files:

3_ada_migration.js

const ADA = artifacts.require("ADA");
const Dex = artifacts.require("Dex");

module.exports = async function (deployer, network, accounts) {
  await deployer.deploy(ADA);
  let ada = await ADA.deployed();
  let dex = await Dex.deployed();
  
  dex.addToken(web3.utils.fromUtf8("ADA"), ada.address, {from: accounts[0]});
  await ada.approve(dex.address, 100000);
  await dex.deposit(1000, web3.utils.fromUtf8("ADA"));
  let balanceOfADA = await dex.balances(accounts[0], web3.utils.fromUtf8("ADA"));
  console.log(balanceOfADA);


};

4_link_migration.js

const LINK = artifacts.require("LINK");
const Dex = artifacts.require("Dex");

module.exports = async function (deployer, network, accounts) {
    await deployer.deploy(LINK); 
    let link = await LINK.deployed();
    let dex = await Dex.deployed();
    
    dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]});
    await link.approve(dex.address, 100000);
    await dex.deposit(1000, web3.utils.fromUtf8("LINK"));
  
    let balanceOfLINK = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));
    console.log(balanceOfLINK);
  

  };
  

5_vet_migration.js

const VET = artifacts.require("VET");
const Dex = artifacts.require("Dex");

module.exports = async function (deployer, network, accounts) {
    await deployer.deploy(VET); 
    let vet = await VET.deployed();
    let dex = await Dex.deployed();
    
    dex.addToken(web3.utils.fromUtf8("VET"), vet.address, {from: accounts[0]});
    await vet.approve(dex.address, 100000);
    await dex.deposit(1000, web3.utils.fromUtf8("VET"));
  
    let balanceOfVET = await dex.balances(accounts[0], web3.utils.fromUtf8("VET"));
    console.log(balanceOfVET);
  

  };
  

As you can see the webpage is only showing ADA and not the rest.
Screenshot 2021-08-06 at 14.55.30

Is there something i am missing here?
Do i need to add anything to the index.js file?

index.js:


// var web3 = new Web3(givenProvider);
var web3 = new Web3(Web3.givenProvider || "ws://localhost:8545");


var contractAddress = "0x66B3D3dc9c94f1344DD60684D541CDb81a89532A";

$(document).ready(function (){
    window.ethereum.enable().then(async function(accounts){
        dex = await new web3.eth.Contract(abi, contractAddress, {from: accounts[0]})
        showTokenList();
        showETHBalance();
        showTokenBalance();
        showOrderbookBuy();
        showOrderbookSell();
    })




$("#btndepositEth").click(depositEth);
$("#btnwithdrawEth").click(withdrawEth);
$("#btnLimitOrder").click(placeLimitOrder);
$("#btnMarketOrder").click(placeMarketOrder);
$("#btnOrderbook").click(reloadPage);
$("#btnTokenDeposit").click(depositTokens);
$("#btnTokenWithdraw").click(withdrawTokens);

async function depositTokens(){
    let amount = $("#depositTokens").val();
    await dex.methods.deposit(amount, web3.utils.fromUtf8("LINK")).send();
}
async function withdrawTokens(){
    let amount = $("#withdrawTokens").val();
    await dex.methods.withdraw(amount, web3.utils.fromUtf8("LINK")).send();
}

async function showTokenBalance(){
    let tokenList = await dex.methods.getTokenList().call();
    for(let i =0; i< tokenList.length; i++){
        let token = await dex.methods.TokenList(i).call();
        let balance = await dex.methods.balances(ethereum.selectedAddress, token).call();
        console.log("Balance of " + web3.utils.toUtf8(token) + " is: " + balance);
        $('<span />').text(web3.utils.toUtf8(token) + ": "+ balance).appendTo("#tokenBal");
    }
}

async function showETHBalance(){
    let address = ethereum.selectedAddress;
    let currentETHBalance = await dex.methods.balances(address, web3.utils.fromUtf8("ETH")).call();
    console.log(web3.utils.fromWei(currentETHBalance));
    $("<span />").text(" " + web3.utils.fromWei(currentETHBalance)).appendTo("#eth-balance");
    $("<span />").text(" " + currentETHBalance).appendTo("#wei-balance");

}

async function showTokenList(){ 
    let list = await dex.methods.getTokenList().call();
    for(let i=0; i < list.length; i++){
        let tokenList = await dex.methods.TokenList(i).call();
        console.log(tokenList);
        $('<p />').text("Ticker: " + web3.utils.toUtf8(tokenList) + ", ").appendTo('.listOfTokens');
    }
}

async function showOrderbookBuy(){ 
    let orderbook = dex.methods.getOrderBook(web3.utils.fromUtf8("ADA"), 0).call();
    console.log(orderbook);

    for(let i = 0; i < orderbook.length; i++){
        let ticker = orderbook[i]["ticker"];
        let amount = orderbook[i]["amount"];
        let price = web3.utils.fromWei(orderbook[i]["price"]);
        console.log(ticker);
        console.log(amount);
        console.log(price);

        $("<tr />").appendTo("#BuyOrders");
        $("<td />").text("Ticker: " + web3.utils.fromUtf8(ticker).toString()).appendTo("#BuyOrders");
        $("<td />").text("Amount: " + amount).appendTo("#BuyOrders");
        $("<td />").text("Price (in Wei): " + web3.utils.fromWei(price).toString()).appendTo("#BuyOrders");
    }
}

async function showOrderbookSell(){
    let orderbook = dex.methods.getOrderBook(web3.utils.fromUtf8("ADA"), 1).call();
    for(let i = 0; i<orderbook.length; i++){
        let ticker = orderbook[i]["ticker"];
        let amount = orderbook[i]["amount"];
        let price = web3.utils.fromWei(orderbook[i]["price"]);
        $("<tr >").appendTo("#SellOrders");
        $("<td />").text("Ticker: " + web3.utils.fromUtf8(ticker).toString()).appendTo("#SellOrders");
        $("<td />").text("Amount: " + amount).appendTo("#SellOrders");
        $("<td />").text("Price (in Wei): " + web3.utils.fromWei(price).toString()).appendTo("#SellOrders");
    }
}

function reloadPage(){
    location.reload();
}


async function placeLimitOrder(){
    let side = $("#typeL").val();
    console.log(side);
    let ticker = $("#tickerL").val();
    console.log(ticker);

    let amount = $("#amountL").val();
    console.log(amount);

    let price = $("#priceL").val();
    console.log(price);

    await dex.methods.createLimitOrder(side, web3.utils.fromUtf8(ticker), amount, price).send();
    reloadPage();
}

async function placeMarketOrder(){
    let side = $("#typeM").val();
    console.log(side)
    let ticker = $("#tickerM").val();
    console.log(ticker);
    let amount = $("#amountM").val();
    console.log(amount);
    await dex.methods.createMarketOrder(side, web3.utils.fromUtf8(ticker), amount).send();
    alert("Your Market Order Has Been Placed");
    reloadPage()
}


async function withdrawEth(){
    let amount = $("#withdrawEther").val();
    console.log(amount);
    let address = ethereum.selectedAddress;
    console.log(address);
    let balanceBefore = await dex.methods.balances(address, web3.utils.fromUtf8("ETH")).call();
    console.log(balanceBefore);
    await dex.methods.withdrawEth(amount).send({from: ethereum.selectedAddress});
    let balanceAfter = await dex.methods.balances(ethereum.selectedAddress, web3.utils.fromUtf8("ETH")).call();
    console.log(balanceAfter);
    
    reloadPage()
    
}

async function depositEth (){
    let amount = $("#depositEther").val();
    console.log(amount);
    let address = ethereum.selectedAddress;
    console.log(address);
    let balance = await dex.methods.balances(address, web3.utils.fromUtf8("ETH")).call();
    console.log(balance);
    await dex.methods.depositEth().send({value: web3.utils.toWei(amount, "ether")});
    balance = await dex.methods.balances(address, web3.utils.fromUtf8("ETH")).call();
    console.log(balance);
    
    reloadPage();
    
}

});

Here is my Github repo: https://github.com/olfrank/Simple_DEX_Dapp

Forgive me if you are tired of seeing my name pop up asking for help haha.

Thank you all.

1 Like

hey @ol_frank. i could look into your code in more detail later on my machine.but from skimming over it now i could suggest a different approach to display all of the erc20 token balances.

From looking at your wallet.sol contact you currently have no function which displays the erc20 token balances. i suggest that you make a function called getTokenBalance() or something that takes in the token ticker as an argument and then displays the token balance of that ticker for msg.sender. something like this

function getTokenBalance(bytes32 ticker) public view returns (uint) {
    return balances[ticker][msg.sender];
}

then what you could do in your frontend code is to make maybe like a dropdown menu that has all of your erc20 tokens. When you select a token you could set in your main.js some val that is equal to the ticker value of the current selected item in the dropdown menu. And then you could pass this value into the getTokenBalance() function and call it as usual and display the token balance this way. Then each time you change token in the dropdown menu it will change the ticker argument your your tokenBalance call and thus display the correct balances accordingly.

Just to reiterate i would have the drpdown menu and then and actual button called get balance which when clicked calls the tokenBalancefunction.

I know i have not provided any actual code for how to do this but i could play around with it for you later this eve

3 Likes

I finally finished this one. It took me quite a while to work through all my errors.

My logic is similar to Filip’s but I created a few extra functions to make the code a bit cleaner.

these were :
executeOrder: which executed the changes in balances
checkBalance : which contains the require-stament that checks whether there is enough h of a certain token for the trade

In my createLimitOrder function I added the ability to execute the order if it matches an order on the opposite side and then only adding the remaining unfilled amount as a limit order.

I thought it strange that all limit orders would only be executed by market orders.

So here’s my code : I would love some feedback on the extra functions I created and also whether I used public/private/internal correctly for all my functions. Sometimes I’m still confused by which to use.

Thank you for a great course!

DEX.sol

// SPDX-License-Identifier: Undefined

pragma solidity 0.8.6;
pragma experimental ABIEncoderV2;

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";
import "../contracts/Wallet.sol";

contract Dex is Wallet {
	using SafeMath for uint256;

	enum side {
		BUY,
		SELL
	}

	struct Order {
		uint256 id;
		address traderAddress;
		side orderSide;
		bytes32 ticker;
		uint256 amount;
		uint256 price;
		uint256 filled;
	}

	mapping(bytes32 => mapping(uint256 => Order[])) Orderbook;

	uint256 nextOrderId = 0;

	function getOrderbook(bytes32 _ticker, side orderSide) public view returns (Order[] memory) {
		return Orderbook[_ticker][uint256(orderSide)];
	}

	function checkBalance(
		address _trader,
		side orderSide,
		bytes32 _ticker,
		uint256 _price,
		uint256 _amount
	) public view {
		if (orderSide == side.BUY) {
			require(balances[_trader]["ETH"] >= _amount.mul(_price), "Your ETH Balance is too low");
		} else if (orderSide == side.SELL) {
			require(balances[_trader][_ticker] >= _amount, " Your ERC20 Token Balance too low");
		}
	}

	function createLimitOrder(
		side orderSide,
		bytes32 _ticker,
		uint256 _price,
		uint256 _amount
	) public {
		checkBalance(msg.sender, orderSide, _ticker, _price, _amount); // check that there is enough balance to place the limit order

		uint256 marketSide; // create variable to use to get opposite side orderbook

		if (orderSide == side.BUY) {
			marketSide = 1;
		} else {
			marketSide = 0;
		}

		Order[] storage oppositeOrderBook = Orderbook[_ticker][marketSide];

		uint256 i = 0;
		uint256 totalFilled = 0;
		uint256 leftToFill = _amount;

		//  execute order if it matches the opposite orderbook

		while (oppositeOrderBook.length != 0 && i < oppositeOrderBook.length - 1 && oppositeOrderBook[i].price == _price && totalFilled < _amount) {
			leftToFill = _amount.sub(totalFilled);
			uint256 availableToFill = oppositeOrderBook[i].amount.sub(oppositeOrderBook[i].filled);
			uint256 filled;

			if (leftToFill < availableToFill) {
				filled = leftToFill;
			} else {
				filled = availableToFill;
			}

			oppositeOrderBook[i].filled = oppositeOrderBook[i].filled.add(filled);
			totalFilled = totalFilled.add(filled);

			executeOrder(orderSide, _ticker, oppositeOrderBook[i].traderAddress, filled, oppositeOrderBook[i].price);
			i++;
		}

		while (oppositeOrderBook.length > 0 && oppositeOrderBook[0].filled == oppositeOrderBook[0].amount) {
			// if the order is filled remove the order for the orderbook

			for (uint256 j = 0; j < oppositeOrderBook.length - 1; j++) {
				oppositeOrderBook[j] = oppositeOrderBook[j + 1];
			}

			oppositeOrderBook.pop();
		}

		if (leftToFill != 0) {
			//  if either the new limit order does not match or partly match a current order then add the limit order to the opposite orderbook

			Order[] storage orders = Orderbook[_ticker][uint256(orderSide)];

			orders.push(Order(nextOrderId, msg.sender, orderSide, _ticker, leftToFill, _price, 0));
			i = orders.length > 0 ? orders.length - 1 : 0;

			if (orderSide == side.BUY) {
				while (i > 0) {
					if (orders[i].price < orders[i - 1].price) {
						break;
					}
					Order memory orderToMove = orders[i];
					orders[i] = orders[i - 1];
					orders[i - 1] = orderToMove;
					i--;
				}
			} else if (orderSide == side.SELL) {
				while (i > 0) {
					if (orders[i - 1].price < orders[i].price) {
						break;
					}
					Order memory orderToMove = orders[i - 1];
					orders[i - 1] = orders[i];
					orders[i] = orderToMove;
					i--;
				}
			}
			nextOrderId++;
		}
	}

	function executeOrder(
		side orderSide,
		bytes32 _ticker,
		address traderAddress,
		uint256 _amount,
		uint256 price
	) private {
		uint256 cost = _amount.mul(price);

		if (orderSide == side.BUY) {
			checkBalance(msg.sender, orderSide, _ticker, price, _amount);
			balances[traderAddress][_ticker] = balances[traderAddress][_ticker].sub(_amount); // remove ERC20 Token from  order on sell side address
			balances[traderAddress]["ETH"] = balances[traderAddress]["ETH"].add(cost); //  add ETH to seller address
			balances[msg.sender][_ticker] = balances[msg.sender][_ticker].add(_amount); // add ERC20 Token to buyer address
			balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(cost); // remove ETH from  buyer side address
		}
		if (orderSide == side.SELL) {
			checkBalance(msg.sender, orderSide, _ticker, price, _amount);
			balances[traderAddress]["ETH"] = balances[traderAddress]["ETH"].sub(cost); // remove ETH from  order on buyer side address
			balances[traderAddress][_ticker] = balances[traderAddress][_ticker].add(_amount); //  add erc20 Token to buyer address
			balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(cost); // add ETH to seller address
			balances[msg.sender][_ticker] = balances[msg.sender][_ticker].sub(_amount); // remove erc20 Token from   side address
		}
	}

	function marketOrder(
		side orderSide,
		bytes32 _ticker,
		uint256 _amount
	) public {
		uint256 marketOrderSide;
		uint256 totalFilled = 0;
		Order[] storage orders;

		if (orderSide == side.BUY) {
			marketOrderSide = 1;
		} else {
			// if sell order
			require(balances[msg.sender][_ticker] >= _amount, "ERC20 Token balance too low!!");
			marketOrderSide = 0;
		}
		orders = Orderbook[_ticker][marketOrderSide];
		totalFilled = 0;

		for (uint256 i = 0; i < orders.length && totalFilled < _amount; i++) {
			uint256 leftToFill = _amount.sub(totalFilled);
			uint256 availableToFill = orders[i].amount.sub(orders[i].filled);
			uint256 filled;

			if (leftToFill < availableToFill) {
				filled = leftToFill;
			} else {
				filled = availableToFill;
			}

			orders[i].filled = orders[i].filled.add(filled);
			totalFilled = totalFilled.add(filled);

			executeOrder(orderSide, _ticker, orders[i].traderAddress, filled, orders[i].price);
		}

		while (orders.length > 0 && orders[0].filled == orders[0].amount) {
			// if the order is filled remove the order for the orderbook

			for (uint256 j = 0; j < orders.length - 1; j++) {
				orders[j] = orders[j + 1];
			}

			orders.pop();
		}
	}
}

wallet.sol

// SPDX-License-Identifier: Undefined

pragma solidity >=0.4.22 <0.9.0;

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 Wallet is Ownable {
	using SafeMath for uint256;

	struct Token {
		bytes32 ticker;
		address tokenAddress;
	}

	mapping(bytes32 => Token) public tokenMapping;
	bytes32[] public Tokenlist;

	mapping(address => mapping(bytes32 => uint256)) public balances;

	modifier tokenExists(bytes32 _ticker) {
		require((tokenMapping[_ticker].tokenAddress) != address(0), "Token does not exist");
		_;
	}

	function _addToken(bytes32 _ticker, address _tokenAddress) internal onlyOwner {
		tokenMapping[_ticker] = Token(_ticker, _tokenAddress);
		Tokenlist.push(_ticker);
	}

	function addToken(bytes32 _ticker, address _tokenAddress) external onlyOwner {
		_addToken(_ticker, _tokenAddress);
	}

	function addEthToken() external onlyOwner {
		_addToken("ETH", msg.sender);
	}

	function deposit(bytes32 _ticker, uint256 amount) external tokenExists(_ticker) {
		balances[msg.sender][_ticker] = balances[msg.sender][_ticker].add(amount);
		IERC20(tokenMapping[_ticker].tokenAddress).transferFrom((msg.sender), address(this), amount);
	}

	function depositETH() public payable returns (uint256) {
		// require (msg.sender.balance >= msg.value);
		balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(msg.value);
		return balances[msg.sender]["ETH"];
	}

	// function withdrawETH() public payable returns (uint)  {
	//         balance[msg.sender] = balance[msg.sender].add(msg.value);
	//         return balance[msg.sender];

	function withdrawal(bytes32 _ticker, uint256 amount) external tokenExists(_ticker) {
		require(balances[msg.sender][_ticker] >= amount, " Balance not sufficient ");

		balances[msg.sender][_ticker] = balances[msg.sender][_ticker].sub(amount);

		IERC20(tokenMapping[_ticker].tokenAddress).transfer(payable(msg.sender), amount);
	}
}

migrations.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Migrations {
  address public owner = msg.sender;
  uint public last_completed_migration;

  modifier restricted() {
    require(
      msg.sender == owner,
      "This function is restricted to the contract's owner"
    );
    _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }
}

link.sol

// SPDX-License-Identifier: Undefined

pragma solidity >=0.4.22 <0.9.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Link is ERC20 {
  constructor() ERC20("Chainlink", "LINK") {
    _mint(msg.sender, 10000);
  }
}

1 Like

Hi @filip and Team

Please whats the best practice to code token reward for token holders

Hey @chim4us

There isn’t a “best practice”, you can share your code and we can check it out to see if the logic works :slight_smile:
Also feel free to look for other solutions on google.

Okay i have googled did not find solution

Below is my code

RGNToken.sol

// SPDX-License-Identifier: MIT
//pragma solidity >=0.4.22 <0.9.0;
pragma solidity ^0.8.0;

import "./ERC20.sol";
import "./AccessControl.sol";
import "./SafeMath.sol";

interface Tkn{
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract RagnafiToken is ERC20, AccessControl {
  using SafeMath for uint256;


  bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
  bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
  address contractOwner;
  uint256 burnLimit;

  event TknWithDraw(address indexed Spender, uint Amount, address contractAdr);

  modifier onlyOwner (){
    require(hasRole(GOVERNANCE_ROLE, msg.sender), "Caller is not an admin");
    _;
  }

  modifier onlyAdmin (){
    require(hasRole(GOVERNANCE_ROLE, msg.sender), "Caller is not an admin");
    _;
  }

  constructor(address payable routerAddress) ERC20("Ragnafi", "RGN",payable(routerAddress)) {

    _mint(_msgSender(), 1000000000000 * 10**18);
    burnLimit = 100000000 * 10**18;
    contractOwner = _msgSender();

    _isExcludedFromFee[_msgSender()] = true;
    _isExcludedFromFee[address(this)] = true;
    _setupRole(BURNER_ROLE, _msgSender());
    _setupRole(GOVERNANCE_ROLE, _msgSender());
    _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
  }
  
  function returnsHolder() public view returns (address[] memory){
      return(holders);
  }
  
  function ReturnFee(uint256 amount) public pure returns(uint256){
        uint256 fee = amount * 10;
        fee = fee / 100;
        uint256 Tamount = amount + fee;
        return(Tamount);
  }
    
  function excludeFromFeeView(address account) public view returns(bool){
      return(_isExcludedFromFee[account]);
  }
  
  function WBNBFn(address routerAddress) public pure returns(address){
      IPancakeRouter02 pancakeRouter = IPancakeRouter02(routerAddress);
      
      return(pancakeRouter.WETH());
  }

  function excludeFromFee(address account) public onlyOwner{
      _isExcludedFromFee[account] = true;
  }

  function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {
      swapAndLiquifyEnabled = _enabled;
  }

  function addNewGovernance(address newGov) public onlyAdmin{
    grantRole(GOVERNANCE_ROLE, newGov);
  }

  function removeGovernance(address newGov) public onlyAdmin{
    revokeRole(GOVERNANCE_ROLE, newGov);
  }

  function addNewBurner(address newBurner) public onlyOwner{
    grantRole(BURNER_ROLE, newBurner);
  }

  function removeCurrentBurner(address rejectedBurner) public onlyAdmin{
    revokeRole(BURNER_ROLE, rejectedBurner);
  }

  function burnTokens(uint256 amount) public {
    require(hasRole(BURNER_ROLE, _msgSender()), "Caller is not a burner");
    require(_msgSender() != address(0), "unknown address");
    require(totalSupply() > burnLimit, "you cant burn any further");
    amount = (amount) * (10 ** uint256(18));
    burn(amount);
  }

  function burn(uint256 amount) internal virtual {
    super._burn(contractOwner, amount);
  }

  function SetComWallets(
    address payable __companyWallet,
    address payable __companyPoolWallet,
    address payable __companyBuyBackBurn,
    address payable __companyRewardHolders) public onlyOwner {
    companyRewardHolders = __companyRewardHolders;
    companyBuyBackBurn = __companyBuyBackBurn;
    companyPoolWallet = __companyPoolWallet;
    companyWallet = __companyWallet;
  }

  function getComWallets() public view onlyOwner returns(address __companyWallet,
    address __companyPoolWallet,
    address __companyBuyBackBurn,
    address __companyRewardHolders){
      return(companyWallet,companyPoolWallet,companyBuyBackBurn,companyRewardHolders);
  }

    function withdrawBNB() public onlyOwner returns(uint){
        uint conBal = address(this).balance;
        payable(msg.sender).transfer(conBal);

        emit TknWithDraw(msg.sender,conBal, address(this));

        return conBal;
    }

    function withdrawERC(uint amt, address contractAdr) public onlyOwner {
        Tkn Token = Tkn(contractAdr);

        uint256 sndtk = (amt) * (10 ** uint256(18 ));

        Token.transfer(msg.sender,sndtk);

        emit TknWithDraw(msg.sender,sndtk, contractAdr);

    }

    receive() external payable{

    }

    fallback() external payable{

    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./IERC20Metadata.sol";
import "./Context.sol";
import "./SafeMath.sol";
import "./IPancakeRouter02.sol";
import "./Utils.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */

contract ERC20 is Context, IERC20, IERC20Metadata {
    using SafeMath for uint256;
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;
    
    address[] public holders;

    mapping(address => bool) internal _isExcludedFromFee;

    string private _name;
    string private _symbol;

    IPancakeRouter02 public immutable pancakeRouter;
    address public immutable pancakePair;

    uint256 minTokenNumberToSell = 1000000 * (10 ** 18);

    address payable internal companyWallet;
    address payable internal companyPoolWallet;
    address payable internal companyBuyBackBurn;
    address payable internal companyRewardHolders;

    address internal _owner;

    bool public swapAndLiquifyEnabled = false; // should be true

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_, address payable routerAddress) {
        _name = name_;
        _symbol = symbol_;
        
        _isExcludedFromFee[address(this)] = true;

        address msgSender = _msgSender();
        _owner = msgSender;

          IPancakeRouter02 _pancakeRouter = IPancakeRouter02(routerAddress);
         //Create a pancake pair for this new token
          pancakePair = IPancakeFactory(_pancakeRouter.factory())
          .createPair(address(this), _pancakeRouter.WETH());

        // set the rest of the contract variables
          pancakeRouter = _pancakeRouter;
    }


    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function ownerFn() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `sender` to `recipient`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "BEP20: transfer from the zero address");
        require(recipient != address(0), "BEP20: transfer to the zero address");

        uint256 senderBalance = _balances[sender];

        if(balanceOf(companyRewardHolders) > 0){
            RewardHolders();
        }

        if(!_isExcludedFromFee[sender]){
            
          _beforeTokenTransfer(sender, recipient, amount);
          uint256 fee = amount * 10;
          fee = fee / 100;
          uint256 Tamount = amount + fee;
          
          require(senderBalance >= Tamount, "BEP20: transfer amount exceeds balance plus fees");
          Tamount = 0;
          unchecked {
            _balances[sender] = senderBalance - fee;
          }
          _balances[address(this)] += fee;
          senderBalance = _balances[sender];
          require(senderBalance >= amount, "BEP20: transfer amount exceeds balance");
          unchecked {
            _balances[sender] = senderBalance - amount;
          }

          _balances[recipient] += amount;

          emit Transfer(sender, recipient, amount);
          emit Transfer(sender, address(this), fee);
          _afterTokenTransfer(sender, recipient, amount);

        }else{
          _beforeTokenTransfer(sender, recipient, amount);

          senderBalance = _balances[sender];
          require(senderBalance >= amount, "BEP20: transfer amount exceeds balance");

          unchecked {
            _balances[sender] = senderBalance - amount;
          }

          _balances[recipient] += amount;
          emit Transfer(sender, recipient, amount);
          _afterTokenTransfer(sender, recipient, amount);
        }
        swapAndLiquify(sender,recipient);
        //swapAndLiquify();

    }
    
    
    
    function __transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal {
        require(sender != address(0), "BEP20: transfer from the zero address");
        require(recipient != address(0), "BEP20: transfer to the zero address");
        uint256 senderBalance = _balances[sender];
        
        _beforeTokenTransfer(sender, recipient, amount);

          senderBalance = _balances[sender];
          require(senderBalance >= amount, "BEP20: transfer amount exceeds balance");

          unchecked {
            _balances[sender] = senderBalance - amount;
          }

          _balances[recipient] += amount;
          emit Transfer(sender, recipient, amount);
          _afterTokenTransfer(sender, recipient, amount);
    }
    
    function RewardHolders() private{
        uint256 walletAmt;
        uint256 percentage;
        uint256 amtToSend;
        for(uint i = 0; i < holders.length -1; i++){
            walletAmt = balanceOf(holders[i]);
          
            percentage = ((walletAmt / totalSupply() ) * (100));
          
            amtToSend =   balanceOf(companyRewardHolders) * percentage;
          
            __transfer(companyRewardHolders,holders[i],amtToSend);
        }
    }

    //function swapAndLiquify() private {
    function swapAndLiquify(address from, address to ) private {

      uint256 contractTokenBalance = balanceOf(address(this));

      if (contractTokenBalance >= _totalSupply) {
          contractTokenBalance = _totalSupply;
      }

      bool shouldSell = contractTokenBalance >= minTokenNumberToSell;

      if (
        shouldSell &&
        from != pancakePair &&
        swapAndLiquifyEnabled
         && !(from == address(this) && to == address(pancakePair)) // swap 1 time
      ){
        // only sell for minTokenNumberToSell, decouple from _maxTxAmount
        //contractTokenBalance = minTokenNumberToSell;
        uint256 tenP = contractTokenBalance * 10;
        tenP = tenP / 100;
        uint256 fourtyP = contractTokenBalance * 40;
        fourtyP = fourtyP / 100;

        uint256 _companyWallet = tenP;
        uint256 _companyPoolWallet = fourtyP;
        uint256 _companyBuyBackBurn = tenP;
        uint256 _companyRewardHolders = fourtyP;
        
        _burn(address(this), _companyBuyBackBurn);

        __transfer(address(this), companyWallet, _companyWallet);
        __transfer(address(this), companyRewardHolders, _companyRewardHolders);
        //__transfer(address(this), companyBuyBackBurn, _companyBuyBackBurn);
        __transfer(address(this), companyPoolWallet, _companyPoolWallet);
        
        //RewardHolders();

        //uint256 Piece = _companyPoolWallet.div(2);
        
        //uint256 initialBalance = address(this).balance;
        
        //Utils.swapTokensForEth(address(pancakeRouter), Piece);
        
        //uint256 deltaBalance = address(this).balance.sub(initialBalance);
        
        //uint256 bnbToBeAddedToLiquidity = deltaBalance;
        
        //Utils.addLiquidity(address(pancakeRouter), ownerFn(), Piece, bnbToBeAddedToLiquidity);
      }


    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        uint256 index = 0;
        if( _balances[to] == amount ){
            holders.push(to);
        }
        
        if(_balances[from] == 0 ){
            
            ///Get index of address array
            for(uint i = 0; i < holders.length -1; i++){
                if(holders[i] == from){
                    index = i;
                    i = holders.length -1;
                }
            }
            
            ///Remove address from the array
            holders[index] = holders[holders.length -1];
            holders.pop();
        }
    }
}

1 Like

hey @chim4us. I have never actually done something like this before but from reading your post above i would like to propose a path you should take in order to solve a problem like this, namely a problem that you have never done and dont immediately know the solution for.

So if we break down your problem we know that you want to devise a way to reward your token holders. Presumable after some conditions have been met. Now obviously the easiest and most hopeful thing you can do is to go onto gogle and type in “how to reward token hoolders” or something along those lines and hope that someone has posted some solution solutuon somewhere be it on stack exchange or some other forum or website. However the question you are asking is not very specific and is actually very broad. There are defintely a milion different ways and scenarios that exists that someone would want to do rewards and the solutions to all probably differ a lot and depend on various things that are unique to your actual intentions.

So what i suggest is to break down the problem into more bitesize pieces that you will be able to find the solutuon for on google. For example. How could we actually go about rewarding token holders. What do we need. Well the way i see it we ned two things. We need some criteria that has to be met in order for a token holder to qualify for a reward, and also we need a way to track all token holders in order to actually be able to reward them.

So ok we know that we need certain criteria to be met, thats not to bad just code some function that defines this logic. But how in hell would we get a list of al token holders so that we can filter this to see which holders meet our criteria. Well this is where google comes in. After doing a uick google i found this link. I suggest you read it https://ethereum.stackexchange.com/questions/48414/getting-list-of-token-holders-in-real-time. It is to do with using events on mint to be able to query the BC to find all token holders. (note this is the first thing i clicked there could well be better ways) After this you will now be slighly more knowleage on that path you coud follow to solve this problem. There will probably be many more road blocks down the line but the idea is to solve these problems as they arise.

The purpose of this post was not for me to actually try and suggest a way for you to slve your problem, but rather i wrote this post to show you how to go about solving problems you dont know how to solve. You need to always lay out the main obstacle and break it down into small pieces and tackle these smaller pieces one by one. This makes the whole process so much easier. You will find that by doing this you can start to construct a solution much easier. It will often happend that you will be making progress and then find that something you coded now is not compaitibale with some new code you have written and then your back to the drawing board. But this is just part of the process and eventualy by trying dofferent things you will arive at a solution. It doesnt have to be perfect but once you have it prototyped you can worry about cleaning up the code.

Also just from thinking on your question. You should aslo try to figure out where the funds come from to actually cover the payouts of rewarding tokens. Do you just want to mint tokens every time you want to reward someone or perhaps you could write a sort of “treasuary” smart contract that acts like a reserve for these type of evetns where funds are locked in and can only be dished out whenever someone meets the critera for a reward. Never the less by breaking down the problem and actually giving it a stab instead of trying to solev the bigger picture all at once you will find you will make much more progress.

1 Like

Thanks for your advise. The fund comes from charges i placed on transfer on the token. once any address makes transfer the contract will charge the address.

I have also figured out a way to get all token holders, I did it by storing any address that have more than zero balance on an array

where am having issue is crediting the address holders, as solidity doesn’t have decimal operator, It becomes very difficult to calculate percentage amount each address is holding. thats why i asked for your assistance on best way to reward token holders onchain.

This is because i want to reward holders onchain once charged token charge reach certen amount.

1 Like

hey @chim4us. great that you found a way to get all token holders but i think using an array like this could slow your contract down over time as more people strat to hold tokens. You should be firing way more events and using them to extract infromation. You would be suprised how much data structures and what not that you can get rid of once you start querying events. Also on the point of the percentage this is not too difficult. Like if you were for example bale yo write your code such that you coukd fire an event when someone qualifies for a token reward then in we3.js all you would need is the paticular event using something like

 var tokenHolders = contractInstance.getPastEvents(
    "NameOfYourEventThatGivesTokenHolderInfo",
    {fromBlock: 0} // can filter where teh results start and finish
  ).then(function(result) {
  
   //for example result.returnValues will return you an array of all the arguments 
   //of your evert, so right there you have user info without thr need for a data 
   //structure. You just have to be careful and smart with you events 
   console.log(result.returnValues)
   ///do cool stuff
    }

In solidity yeah there is no fixed point math but you can manipulate numbers in a avariety of ways using mathemtical tricks. see this post here it is part 3 of a 4 part series i recommend u read them all Math in Solidity (Part 3: Percents and Proportions) | by Mikhail Vladimirov | Coinmonks | Medium.

Howeever for calculating percentages we can use what called a basis point which is defined as 100th of a percent. So say i want to make a function that calaculates a fee o say 1.5% of the amount i pass in then all i would need to do is this

function calculateFee(uint256 _amount) public pure returns(uint256) {

    //using simple math tricks and manipulation we can get the 1.5 percebtage 
    //fee easily. the forumla is the amount / the basis point all multiplied by the 
    ///percentage
    return (amount / 10000) * 150
}
1 Like

My DEX

Summary
// SPDX-License-Identifier: MIT

pragma solidity >=0.5.16 < 0.9.0;

import "../contracts/wallet.sol";

contract Dex is Wallet {

    

    ///* BUY = 0; SELL = 1 */

    enum Category {  

        BUY,  

        SELL 

    }

    using SafeMath for uint256;

    struct Order {

        uint id;

        address trader;

        bytes32 ticker;

        Category orderType;

        uint amount;

        uint price;

        uint filled;

    }

    uint public transactionID = 0;

    mapping(bytes32 => mapping(uint => Order[])) public orderBook;

    function getOrderBook(bytes32 ticker, Category orderType) public view returns(Order[] memory) {

        return orderBook[ticker][uint(orderType)];

    }

    

    function createLimitOrder(bytes32 ticker, Category orderType, uint amount, uint price) public {

        uint filled = 0;

        if(orderType == Category.BUY) {

            require(balances[msg.sender]["ETH"] >= amount.mul(price), "You dont have enough eth for this buy order!");

            Order[] storage buys = orderBook[ticker][uint(orderType)];

            buys.push(Order(transactionID, msg.sender, ticker, orderType, amount, price, filled));

            //bubblesort for buys array - highest price first

            uint i = buys.length > 0 ? buys.length - 1 : 0;

            for(i; i > 0; i--) {

                if(buys[i - 1].price < buys[i].price) { //compares last 2 objects; 

                    Order memory moveBuy = buys[i - 1];

                    buys[i - 1] = buys[i];

                    buys[i] = moveBuy;

                }

            } 

        } 

        else if(orderType == Category.SELL) {

            require(balances[msg.sender][ticker] >= amount, "You don't have enough tokens for the sell order!");

            Order[] storage sells = orderBook[ticker][uint(orderType)];

            sells.push(Order(transactionID, msg.sender, ticker, orderType, amount, price, filled));

        

            //bubblesort for sells array- lowest price first

            uint j = sells.length > 0 ? sells.length - 1 : 0;

            for(j; j > 0; j--) {

                if(sells[j - 1].price > sells[j].price) {

                    Order memory moveSell = sells[j - 1];

                    sells[j - 1] = sells[j];

                    sells[j] = moveSell;

                }   

            }

        }

        transactionID++;

    }

    

    //when creating a SELL market order, the seller needs to have enough tokens for the trade 

    //When creating a BUY market order, the buyer needs to have enough ETH for the trade 

    //Market orders can be submitted even if the order book is empty 

    //Market orders should be filled until the order book is empty or the market order is 100% filled 

    //The eth balance of the buyer should decrease with the filled amount 

    //The token balances of the sellers should decrease with the filled amounts 

    //Filled limit orders should be removed from the orderbook

    //**TAKES UNTIL FILLED, NOT ARRANGED BY PRICE */

    /** Works like bargaining */

    /** Assert line 121 WAY better than loop */

    /** */

    

    function createMarketOrder(bytes32 ticker, Category orderType, uint amount) public {

        uint totalFilled = 0;  //individually for sell or buy before

        uint256 toFill = amount;  

        if(orderType == Category.BUY)  {

            

            Order[] storage sells = orderBook[ticker][1]; //use memory with one object, with more than 1 (like array) storage

            uint256 balanceTracker = balances[msg.sender]["ETH"];

            uint256 buyCost = 0;

            

            for(uint256 i = 0; i < sells.length && amount > totalFilled; i++) {//i will get bigger, until it is -1 smaller than length or amount = total filled

            //*ex- wants to stop @50, have to tell it to stop @ # of orders or it will foul

                if(toFill > sells[i].amount) { //conscerned about other party

                    sells[i].filled = sells[i].filled.add(sells[i].amount);

                    totalFilled = totalFilled.add(sells[i].amount);

                    balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(sells[i].amount.mul(sells[i].price));

                    balances[sells[i].trader]["ETH"] = balances[sells[i].trader]["ETH"].add(sells[i].amount.mul(sells[i].price));

                    balances[sells[i].trader][ticker] = balances[sells[i].trader][ticker].sub(sells[i].amount);

                    balances[msg.sender][ticker] = balances[msg.sender][ticker].add(sells[i].amount);

                    toFill = toFill.sub(sells[i].amount);

                    buyCost = buyCost.add(sells[i].amount.mul(sells[i].price));

                    //most of this adding and updating

                

                } else  { //will be amount left over

                    sells[i].filled = sells[i].filled.add(toFill);

                    totalFilled = totalFilled.add(toFill);

                    balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(toFill.mul(sells[i].price));

                    balances[sells[i].trader]["ETH"] = balances[sells[i].trader]["ETH"].add(toFill.mul(sells[i].price));

                    balances[sells[i].trader][ticker] = balances[sells[i].trader][ticker].sub(toFill);

                    balances[msg.sender][ticker] = balances[msg.sender][ticker].add(toFill);

                    buyCost = buyCost.add(toFill.mul(sells[i].price));

                }

                assert(balanceTracker >= buyCost);

            }

            while(sells.length > 0 && sells[0].amount == sells[0].filled) {

                if(sells.length > 1) {

                    for(uint256 buyCounter = 0; buyCounter < sells.length -1; buyCounter++) {

                        Order memory moveSell = sells[buyCounter];

                        sells[buyCounter] = sells[buyCounter + 1];

                        sells[buyCounter + 1] = moveSell;

                    }

                }

                sells.pop();

            }

        } else if (orderType == Category.SELL) { //sell at any price because desperate for money

                                                    //price is first from limit order

            require(balances[msg.sender][ticker] >= amount, "You are trying to sell more tokens than you own!");

            Order[] storage buys = orderBook[ticker][0];

        

            for(uint256 i = 0; i < buys.length && amount > totalFilled; i++) {

                if(toFill > buys[i].amount) {

                    buys[i].filled = buys[i].filled.add(buys[i].amount);

                    totalFilled = totalFilled.add(buys[i].amount);

                    balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(buys[i].amount);

                    balances[buys[i].trader][ticker] = balances[buys[i].trader][ticker].add(buys[i].amount);

                    balances[buys[i].trader]["ETH"] = balances[buys[i].trader]["ETH"].sub(buys[i].amount.mul(buys[i].price));

                    balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(buys[i].amount.mul(buys[i].price));

                    toFill = toFill.sub(buys[i].amount);

                    buys[i].amount = buys[i].amount.sub(buys[i].amount);

                } else { //

                    buys[i].filled = buys[i].filled.add(toFill);       

                    totalFilled = totalFilled.add(toFill);

                    balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(toFill);           

                    balances[buys[i].trader][ticker] = balances[buys[i].trader][ticker].add(toFill);   

                    balances[buys[i].trader]["ETH"] = balances[buys[i].trader]["ETH"].sub(toFill.mul(buys[i].price)); 

                    balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(toFill.mul(buys[i].price));

                    buys[i].amount = buys[i].amount.sub(toFill);

                }

            }

            while (buys.length > 0 && buys[0].amount == buys[0].filled) {

                if(buys.length > 1) {

                    for(uint256 moveCounter = 0; moveCounter < buys.length - 1; moveCounter++) {

                        Order memory moveBuy = buys[moveCounter];

                        buys[moveCounter] = buys[moveCounter + 1];

                        buys[moveCounter + 1] = moveBuy;

                    }

                }

                buys.pop();

            }

        }

    } 

    

}

I must say that i learned more than i could have imagined taking this course. I started of not knowing what a command line was and now im writing websites. Im now studying linux and python and in general diving into the dev world. However i learn in a diff way than most. I understand all the material but in order for it to really sink in i have to do other things that reference what ive learned here and connect the dots. This is how i come to understand things, not just commit individual facts to memory. With that said i didnt actually write the limit and market order functions. I know i need to know this stuff but at the moment i know how to use them and im more concerned with building my dex than writing wvery single line of code. Correct me if im wrong but isnt that the point of github?. Im going to move on to other courses and then come back and master the limit and market order functions. If i could afford premium them im sure you guys will have practice excersises but atm this will have to do. I hope im not disappointing those who helped me along the way. Im not giving up but im not going to stubbornly stick to this when i know that if i continue i will eventually come to understand it naturally as i put other pieces of the puzzle together

2 Likes

DEX Repo

hey guys,
I’ve come back to this project to debug my code and im a little stuck on appending new order book entries. My issue is explained below:::

So what i am trying to achieve is this :arrow_down: where every order is appended properly below each other.
Screenshot 2021-09-01 at 17.29.34

However what i currently have is this: :arrow_down: It seems to duplicate the orders and im not sure why.
Screenshot 2021-09-01 at 17.28.42

Here is my HTML section for the order book:

<div class = "orderbook container shadows">
        <h2>orderbook. <i class="fas fa-book"></i></h2>
            <div id = "buyside">
               <h4>buy side.</h4>
               <table class = "buy-orders-side orders-table" id = "BuyOrders">
      
                      <tr>
                        <th>ticker</th>
                        <th>amount</th>
                        <th>price (in Wei)</th>
                      </tr>
                    
               </table>
            </div>


            <div id = "sellside">
                <h4>sell side.</h4>
                <table class = "sell-orders-side orders-table" id = "SellOrders">
                    
                        <tr>
                          <th>ticker</th>
                          <th>amount</th>
                          <th>price (in Wei)</th>
                        </tr>
                        
                </table>
            </div>

        <button  class = " btn btn-dark" type= "button" id = " btnOrderbook">refresh orderbook</button>
    </div>

And here is the JS that it is reading:

async function showOrderbookSell(){
    let orderbookSell = await dex.methods.getOrderBook(web3.utils.fromAscii("ADA"), 1).call();
    console.log("orderbook sell side: "+orderbookSell);

    for(let i = 0; i < orderbookSell.length; i++){
        let ticker = orderbookSell[i]["ticker"];
        let amount = orderbookSell[i]["amount"];
        let price = web3.utils.toWei(orderbookSell[i]["price"]);
        console.log("orderbook sell ticker: "+web3.utils.toUtf8(ticker));
        console.log("orderbook sell amount: "+amount);
        console.log("orderbook sell price: "+price);

        $("<tr />").appendTo(".sell-orders-side").addClass("new-row")
        $("<td />").text(web3.utils.toUtf8(ticker)).appendTo(".new-row");
        $("<td />").text(amount).appendTo(".new-row");
        $("<td />").text(web3.utils.fromWei(price).toString()).appendTo(".new-row");
    }
}

function reloadPage(){
    location.reload();
}

THANKS FOR ANY HELP YOU CAN OFFER!

2 Likes

hey @ol_frank. I wave been working on a dapp that uses dynamic HTML tables. your right they can be such a struggle at the start. Im just about to eat dinner. Ill write you a detailed post now in about an hour explaining the best way to go about this.

1 Like

hey @ol_frank so before i get into the code we should look at the problems with dynamic HTML tables. Since we are creating new table rows in the javascript file, then each time we reload the page we loose all of the data that we append because the inital table state that is in the HTML, aka, the table header gets reloaded and we loose all of our appended data. So we have to think of a way to overcome this fact. One thing that many people do to solve for this is to append the data to “localStorage”. Web storage objects localStorage and sessionStorage allow to save key/value pairs in the browser. What’s interesting about them is that the data survives a page refresh (for sessionStorage ) and even a full browser restart (for localStorage ).

If we were using traditional methods, this would probably be the best way to go. Howver this is a blockchain app, so why rely on traditional databases when we can just query the blockchain whenevr possible. Thus the way to solve this problem efficiently is to one, append new data to the table on some function call, and then two, query the blockhain on page load to load the table at its current initial state.

So what i suggest is that you scrap the function that youve written above and append new data to the table each time you create a limit order. I have not actually tested this in your code but the below code will work but you may have to do a little adapting. So lets re-organise your createLimitOrder function

Also structuring the code the way i have below will let you get rid of your orderBook refresh function as there will be no need to everthing will update in real time using this code

//likewise we acess your sell order table like so
const limitOrderBuyTable = document.getElementById("table")
const limitOrderBuyTable = document.getElementById("table")

//this is your function which lets the user place a limit order
//each time we call this function we will updte the latest row in your table
async function placeLimitOrder(){

  //here i make a table which depending on the side that the user inputs will
  //take the value og one our tables defined above
  var table;
  let side = $("#type-of-order").val();
  let ticker = "ETH" //$("#ticker").val();
  let amount = $("#amount").val();
  let price = $("#amount").val()
  var priceInWei = web3.utils.toWei(price.toString(), "ether")
  let price = $("#price").val();

  //here we use an if statement to let our table var from above decide what table we are appending to
  //if side == 2 then we append to the sell table
  if(side == 1) {
    table = limitOrderSellTable
  }
  //if side == 0 then we append to the buy table
  else if(side == 0) {
    table = limitOrderBuyTbale
  }
  await dex.methods.createLimitOrder(side, ticker, amount, price).send().on("receipt", function(receipt) {

    //here we simple just apend a new row to our table using this notation
    table.innerHTML += `
    <tr>
        <td>${ticker}</td>
        <td>${amount}</td>
        <td >${priceInWei}</td>
    </tr>`

  }).on("error", function(error) {
      console.log("user denied transaction");
  })

}

The above code will dynamically add the information for the limit order to you table each time it is called. However this seems simple but remember we loose this data each time the page reloads. So we need to write a function that quereys the orderbook and loops through it constructing the table from scratch ever time the page loads. So that means we will call this function every time the page loads. Below is the code for this loadLimitOrderData function

$(document).ready(function(){
    window.ethereum.enable().then(async function(accounts){
        dex = await new web3.eth.Contract(abi.window, contractAddress, {from: accounts[0]})
        showETHBalance();
        showOrderbookBuy();
        showOrderbookSell();
        showTokenList();
        showTokenBalance();

        //call the load limitOrdertables for both the buy and sell side
        loadLimitOrderTable("ETH", 0)
        loadLimitOrderTable("ETH", 1)
       
    })


//so we can access your buy order table like so
const limitOrderBuyTable = document.getElementById("limit-order-sell-table")


//likewise we acess your sell order table like so
const limitOrderBuyTable = document.getElementById("limit-order-sbuy-table")


//this is your function which lets the user place a limit order
//each time we call this function we will updte the latest row in your table
async function placeLimitOrder(){

  //here i make a table which depending on the side that the user inputs will
  //take the value og one our tables defined above
  var table;
  let side = $("#type-of-order").val();
  let ticker = "ETH" //$("#ticker").val();
  let amount = $("#amount").val();
  let price = $("#amount").val()
  var priceInWei = web3.utils.toWei(price.toString(), "ether")
  let price = $("#price").val();

  //here we use an if statement to let our table var from above decide what table we are appending to
  //if side == 2 then we append to the sell table
  if(side == 1) {
    table = limitOrderSellTable
  }
  //if side == 0 then we append to the buy table
  else if(side == 0) {
    table = limitOrderBuyTbale
  }
  await dex.methods.createLimitOrder(side, ticker, amount, price).send().on("receipt", function(receipt) {

    //here we simple just apend a new row to our table using this notation
    table.innerHTML += `
    <tr>
        <td>${ticker}</td>
        <td>${amount}</td>
        <td >${priceInWei}</td>
    </tr>`

  }).on("error", function(error) {
      console.log("user denied transaction");
  })

}

//this function is called on page load and will loop through the order book and populate 
//our table per each elemnt in the orderbook
async function loadLimitOrderTable(ticker, side) {

  //what we do here is we call the getOrderBook function twice and populate both tables for each side

  //here i hardode the ticker to be ETH to make things easier for you but you should pass 
  //in ticker into the get orderBook function
  const orderBook = await contractInstance.methods.getOrderBook(web3.utils.fromAscii("ETH"), 0).call().then(function(result) {
    for (let i = 0; i < result.length; i++) {
      let amountInWei = web3.utils.toWei(result[i].amount.toString(), "ether")
      limitOrderBuyTable.innerHTML += `
          <tr "class="tablerow">
              <td>${result[i].ticker}</td>
              <td>${result[i].amount}</td>
              <td><span id ="${result[i].price}">Remove</span></td>
          </tr>`  
    }
  })

  //populate sell side
  const orderBook = await contractInstance.methods.getOrderBook(web3.utils.fromAscii("ETH"), 1).call().then(function(result) {
    for (let i = 0; i < result.length; i++) {
      let amountInWei = web3.utils.toWei(result[i].amount.toString(), "ether")
      limitOrderSellTable.innerHTML += `
          <tr "class="tablerow">
              <td>${result[i].ticker}</td>
              <td>${result[i].amount}</td>
              <td><span id ="${result[i].price}">Remove</span></td>
          </tr>`  
    }
  })
}

So the thing to note about the above code is that i hardcoded in the ticker as ETH. The best way to make this versatile so that you only have to code two tables to cater for al your functions is to figure out a way before hand which ticker your page is loaded in with. For this i suggest making a dropdown menu where each of your dropdown elements has an ID equal to your ticker. The create an onclick function that gloably sets a ticker varaible equal to that ID. It is a little more complicated than that though as you will need to set the global ticker var to localStorage (localStorage.set(...)) so that you dont loose its value each time the page loads. But i wont over complicate this post just yet. If you can get your tables updating correctlu by hardcoding the ticker as ETH, then i will tell you a good method to do this which uses localStorage

Modified version of the code above
I am not a fan of loading in the contratc instance using jquery and document.ready so below is a modified version of that initialisation using vaniall js. Use this if you want

//Import our Dex ABi directly. Make sure the spelling for Dex is correct
import data from './build/contracts/Dex.json' assert { type: "json" };

var account = "";
var contractInstance = "";

//uses metamask to conect toethereum provider this code is standard
//for every project
async function loadWeb3() {
  if (window.ethereum) {
    window.web3 = new Web3(window.ethereum)
    await window.ethereum.enable()
  }
  else if (window.web3) {
    window.web3 = new Web3(window.web3.currentProvider)
  }
  else {
    window.alert('Non-Ethereum browser detected. You should consider trying MetaMask!')
  }
}

//this function loads the blocckhain data and connects to solidity backend
//both this and the above function are called on page load. See the end of this snippet
//where we call both
async function loadBlockchainData() {

  const web3 = window.web3

  //gets all user accounts and displays the current user on the UI (navbar)
  var accounts = await web3.eth.getAccounts()
  account = accounts[0]   
  
  //gets the current network ID (e.g ropsten, kovan, mainnet) and uses the contract abi imported at the
  //top of this file to make a new contract instamce using web3.js new contract function. 
  const networkId = await web3.eth.net.getId()
  const networkData = data.networks[networkId]
  if(networkData) {
    contractInstance = new web3.eth.Contract(data.abi, networkData.address, {from: acc})
    console.log("the smart contract is " + networkData.address);
    console.log(contractInstance)
      
  } else {
    window.alert('contract not deployed to detected network.')
  }

  //here we call out loadTable function for both sides of the orderbook
  loadLimitOrderTable("ETH", 0)
  loadLimitOrderTable("ETH", 1)
}


//so we can access your buy order table like so
const limitOrderBuyTable = document.getElementById("table")
const limitOrderBuyTable = document.getElementById("table")

//likewise we acess your sell order table like so
const limitOrderBuyTable = document.getElementById("table")
const limitOrderBuyTable = document.getElementById("table")

//this is your function which lets the user place a limit order
//each time we call this function we will updte the latest row in your table
async function placeLimitOrder(){

  //here i make a table which depending on the side that the user inputs will
  //take the value og one our tables defined above
  var table;
  let side = $("#type-of-order").val();
  let ticker = "ETH" //$("#ticker").val();
  let amount = $("#amount").val();
  let price = $("#amount").val()
  var priceInWei = web3.utils.toWei(price.toString(), "ether")
  let price = $("#price").val();

  //here we use an if statement to let our table var from above decide what table we are appending to
  //if side == 2 then we append to the sell table
  if(side == 1) {
    table = limitOrderSellTable
  }
  //if side == 0 then we append to the buy table
  else if(side == 0) {
    table = limitOrderBuyTbale
  }
  await dex.methods.createLimitOrder(side, ticker, amount, price).send().on("receipt", function(receipt) {

    //here we simple just apend a new row to our table using this notation
    table.innerHTML += `
    <tr>
        <td>${ticker}</td>
        <td>${amount}</td>
        <td >${priceInWei}</td>
    </tr>`

  }).on("error", function(error) {
      console.log("user denied transaction");
  })

}

//this function is called on page load and will loop through the order book and populate 
//our table per each elemnt in the orderbook
async function loadLimitOrderTable(ticker, side) {

  //what we do here is we call the getOrderBook function twice and populate both tables for each side

  //here i hardode the ticker to be ETH to make things easier for you but you should pass 
  //in ticker into the get orderBook function
  const orderBook = await contractInstance.methods.getOrderBook(web3.utils.fromAscii("ETH"), 0).call().then(function(result) {
    for (let i = 0; i < result.length; i++) {
      let amountInWei = web3.utils.toWei(result[i].amount.toString(), "ether")
      limitOrderBuyTable.innerHTML += `
          <tr "class="tablerow">
              <td>${result[i].ticker}</td>
              <td>${result[i].amount}</td>
              <td><span id ="${result[i].price}">Remove</span></td>
          </tr>`  
    }
  })

  //populate sell side
  const orderBook = await contractInstance.methods.getOrderBook(web3.utils.fromAscii("ETH"), 1).call().then(function(result) {
    for (let i = 0; i < result.length; i++) {
      let amountInWei = web3.utils.toWei(result[i].amount.toString(), "ether")
      limitOrderSellTable.innerHTML += `
          <tr "class="tablerow">
              <td>${result[i].ticker}</td>
              <td>${result[i].amount}</td>
              <td><span id ="${result[i].price}">Remove</span></td>
          </tr>`  
    }
  })
}

loadWeb3()
loadBlockchainData()

Ok s that s it. both code snippets will allow you to load your table data corectly. Note that i made this code up on the fly i have not able to test it but there will only be one or two small bugs max. But 95% of it is there you will be able to make the fixes if any easily enough. Let me know how you get on and i will tell you how to solve yout icker and localstorage problem

1 Like

Woah, you really know your stuff Evan, that’s so ideal thanks a bunch mate. Will implement this now and let you know how i get on today.

:space_invader::space_invader::space_invader::space_invader:

1 Like

no worries @ol_frank. you may have to change a few things to make it work specifically for your code but it shouldnt be too difficult

1 Like

Yo, it works like a dream Ev, love the detailed walkthrough that you provided, helped a lot. I am trying to implement the dropdown menu now but by the sounds of it, it will be a little tricky. Will let you know how i get on.

Screenshot 2021-09-02 at 13.23.49

Screenshot 2021-09-02 at 13.24.09

Gracias

1 Like

brilliant @ol_frank . Glad i could help. Now i have a solution for your drop down menu. Not sure if you want a navbar but the solution i propose is to include a navbar. You could code your own dropdown menu yourself just as eaisly off osme youtube tutorial or whatever but i have a working implementation of thing sin one of my projects. Its a bootstrap navbar with a dropdown included.

<nav id="nav" class="navbar navbar-expand-lg navbar-light bg-light menu shadow-lg sticky-top">
        <a class="navbar-brand" href="#">MultiSig Wallet</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNavDropdown">
          <ul class="navbar-nav">
                <li class="nav-item active">
                <a class="nav-link" href="#"><span class="sr-only"></span></a>
                </li>
              
                <li class="nav-item dropdown">
                    <a id="ERC20-token-menu" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                      
                    </a>
                    <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                      <a  id="ETH" class="dropdown-item" href="#">ETH</a>
                      <a  id="LINK" class="dropdown-item" href="#">LINK</a>
                      <a  id="UNI" class="dropdown-item" href="#">UNI</a>
                      <a  id="VET" class="dropdown-item" href="#">VET</a>
                      <a id="BNB" class="dropdown-item" href="#">BNB</a>
                    </div>
                  </li>
            </ul>
        </div>
        <div class="collapse navbar-collapse justify-content-end balances" id="navbarNavDropdown">
            <ul class="navbar-nav">
            <li class="nav-item active navbar-item">
                <a class="nav-link" id="display-balance" href="#">Balance: </a>
            </li>
            <!-- <li class="nav-item active navbar-item">
                <div class="card">
                    <svg class="card-img-top" data-jdenticon-value="tedir" height="20px" width="20px"></svg>
                </div>
            </li> -->
            <li class="nav-item active navbar-item" id="acc">
                
                <div>
                    <a class="nav-link pt-md-2" id="display-address" href="#">Account</a></span></a>
                </div>
                
                
            </li>
            </ul>
        </div>       
    </nav>

Here is the code. Now im not a fan of bootstrap because i prefer writing my own styles its actually easier i think because it can be hard to overwrite bootstrap styles sometimes. Howeever i always use the bootstrap navbaars as there so quick to spin up. So the drop down menus for this example is provided by deafult by the bootstrap navbar example code. And then i just moified some of the IDs. Her is the HTML

Now one thing that we will need to implement to get this to work is to use local storage and set our current chose seletcted ticker to localstorage so that when the page reloads it remains the same instead of reseting. Thus everytime we choose a new ticker we simply update the value for it stored in local storage. So lets dive in

lets first look at the dropdown portion of the navbar. Notice how i have assigned an ID to each which is just the ticker. This way in index.js we can set up an onclick function to each of the dropdown items we can call a function which sets that ticker to local storage.

<li class="nav-item dropdown">
                    <a id="ERC20-token-menu" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                      
                    </a>
                    <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                      <a  id="ETH" class="dropdown-item" href="#">ETH</a>
                      <a  id="LINK" class="dropdown-item" href="#">LINK</a>
                      <a  id="UNI" class="dropdown-item" href="#">UNI</a>
                      <a  id="VET" class="dropdown-item" href="#">VET</a>
                      <a id="BNB" class="dropdown-item" href="#">BNB</a>
                    </div>
                  </li>

so in our index.js we grab each of our dropdown elements with document.getElementById(). We also need to define a global variable which we will call currentSelectedToken which will hold the current ticker. We will use this current token then as the ticker argument for all of our smart contract functions that require the ticker to execute

var currentSelectedToken //dont set this to anything initiall just declare it
const ETH = document.getElementById("ETH")
const LINK = document.getElementById("ETH")
const UNI = document.getElementById("ETH")

Then we can call a function that gets executed whenever we clic on one of our tickers

const ETH = document.getElementById("ETH")
const LINK = document.getElementById("LINK")
const UNI = document.getElementById("UNI")

ETH.onclick = function() {
  console.log("clicked ETH")
  ERC20TokenMenu.innerHTML = "ETH"
  testObject = { 'token': "ETH"};
  localStorage.setItem('testObject', JSON.stringify(testObject));
  retrievedObject = localStorage.getItem('testObject');
  currentSelectedToken = JSON.parse(retrievedObject).token
  displayBalance()
}
LINK.onclick = function() {
  ERC20TokenMenu.innerHTML = "LINK"
  testObject = { 'token': "LINK"};
  localStorage.setItem('testObject', JSON.stringify(testObject));
  retrievedObject = localStorage.getItem('testObject');
  currentSelectedToken = JSON.parse(retrievedObject).token
}
UNI.onclick = function() {
  ERC20TokenMenu.innerHTML = "UNI"
  testObject = { 'token': "UNI"};
  localStorage.setItem('testObject', JSON.stringify(testObject));
  retrievedObject = localStorage.getItem('testObject');
  currentSelectedToken = JSON.parse(retrievedObject).token
}

So here it can get tricky if your not used to how local storage works. Baswically to set an item to local storage we can declare some object in json fomat which will map the item we want to set to some value. This is done with the test object in each of the functions above. To set the item to local storage we uselocalStorage.setItem() and pass in in single quotes our test object as the first argument and then JSON.strinigy('testobject') as the second argument. The reason we need to stringify it is to get the jsonesque data in the correct form. We do this anytime we set somwthing to local storage.

Once we have set the ticker to local storage we can now query the local storage using localStorage.getItem() again passing in out testObject as the argument. Set this to some variable name. Lastly we can manipulate the data into a useable form by using JSON.parse(), and isolating the token value by specifiying JSON.parse('testObject).token. Then we just assign this to our currentSelected token and walla our current selected token now equals the current item in our dropdown menu and we can now pass this into any required function.

Hope all of this makes sense it can be a little tricky at the start but hopfully this guide will make it a bit clear

EDIT
One thing that you may notice above is the repeated code that i use each time to set the ticker. It would be desirable to create a general function that takes the ticker ID and passes that in to set the local storage. It would save us to only have to write the function once. I am aware of this but for some reason if you make onclick functions in javascript you cannot pass in any arguments if you do the function wont work so i had to do this as an alternative. If you can figure out some way to get aroundvthis do share

1 Like