0

I am currently following the tutorial in Dapp University YouTube channel where I am coding my own ERC-20 token https://www.youtube.com/watch?v=XdKv5uwEk5A&t=21611s. At 5:51:00 onwards you can see that in his webpage "Your Account" is displaying the address of Ganache(local blockchain). But in my code Account is displaying NULL as you can see printed in console. Also my webpage is always at the loading stage so content.show() never works. And then this error pops up as you can see from the screenshot:

Uncaught (in promise) TypeError: Cannot convert undefined or null to object
at Function.keys (<anonymous>)
at Object.merge (truffle-contract.min.js:1)
at TruffleContract.balanceOf (truffle-contract.min.js:1)
at app.js:106

enter image description here

This is my app.js code:

App = {
  web3Provider: null,
  contracts: {},
  account: '0x0',
  loading: false,
  tokenPrice: 1000000000000000,
  tokensSold: 0,
  tokensAvailable: 750000,

  init: function() {
    console.log("App initialized...")
    return App.initWeb3();
  },

  initWeb3: function() {
    if (typeof web3 !== 'undefined') {
      // If a web3 instance is already provided by Meta Mask.
      App.web3Provider = web3.currentProvider;
      web3 = new Web3(web3.currentProvider);
    } else {
      // Specify default instance if no web3 instance provided
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
      web3 = new Web3(App.web3Provider);
    }

    return App.initContracts();
  },

  initContracts: function() {
    $.getJSON("RTokenSale.json", function(RTokenSale) {
      App.contracts.RTokenSale = TruffleContract(RTokenSale);
      App.contracts.RTokenSale.setProvider(App.web3Provider);
      App.contracts.RTokenSale.deployed().then(function(RTokenSale) {
        console.log("RToken Sale Address:", RTokenSale.address);
      });
    }).done(function() {
      $.getJSON("RToken.json", function(RToken) {
        App.contracts.RToken = TruffleContract(RToken);
        App.contracts.RToken.setProvider(App.web3Provider);
        App.contracts.RToken.deployed().then(function(RToken) {
          console.log("RToken Address:", RToken.address);
        });

        App.listenForEvents();
        return App.render();
      });
    })
  },

  // Listen for events emitted from the contract
  listenForEvents: function() {
    App.contracts.RTokenSale.deployed().then(function(instance) {
      instance.Sell({}, {
        fromBlock: 0,
        toBlock: 'latest',
      }).watch(function(error, event) {
        console.log("event triggered", event);
        App.render();
      })
    })
  },

  render: function() {
    if (App.loading) {
      return;
    }
    App.loading = true;

    var loader  = $('#loader');
    var content = $('#content');

    loader.show();
    content.hide();

    // Load account data
    web3.eth.getCoinbase(function(err, account) {
      // if(account == null)
      //   account = {};
      if(err === null) {
        App.account = account;
        $('#accountAddress').html("Your Account: " + account);
      }
    })

  // Load token sale contract
    App.contracts.RTokenSale.deployed().then(function(instance) {
      RTokenSaleInstance = instance;
      return RTokenSaleInstance.tokenPrice();
    }).then(function(tokenPrice) {
      App.tokenPrice = tokenPrice;
      $('.token-price').html(web3.fromWei(App.tokenPrice, "ether").toNumber());
      return RTokenSaleInstance.tokensSold();
    }).then(function(tokensSold) {
      App.tokensSold = tokensSold.toNumber();
      $('.tokens-sold').html(App.tokensSold);
      $('.tokens-available').html(App.tokensAvailable);

      var progressPercent = (Math.ceil(App.tokensSold) / App.tokensAvailable) * 100;
      $('#progress').css('width', progressPercent + '%');

      // Load token contract
      App.contracts.RToken.deployed().then(function(instance) {
        console.log("Instance: ", instance);
        RTokenInstance = instance;
        console.log("Account: ", App.account);
        return RTokenInstance.balanceOf(App.account);
      }).then(function(balance) {
        $('.RToken-balance').html(balance.toNumber());
        App.loading = false;
        loader.hide();
        content.show();
      })
    });
  },

  buyTokens: function() {
    $('#content').hide();
    $('#loader').show();
    var numberOfTokens = $('#numberOfTokens').val();
    App.contracts.RTokenSale.deployed().then(function(instance) {
      return instance.buyTokens(numberOfTokens, {
        from: App.account,
        value: numberOfTokens * App.tokenPrice,
        gas: 500000 // Gas limit
      });
    }).then(function(result) {
      console.log("Tokens bought...")
      $('form').trigger('reset') // reset number of tokens in form
      // Wait for Sell event
    });
  }
}

$(function() {
  $(window).load(function() {
    App.init();
  })
});

This is my index.html code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>RToken ICO Sale</title>
    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container" style="width: 650px;">
      <div class="row">
        <div class="col-lg-12">
            <h1 class="text-center">RToken ICO SALE</h1>
            <hr/>
            <br/>
        </div>
        <div id="loader">
          <p class="text-center">Loading...</p>
        </div>
        <div id="content" class="text-center" style="display: none;">
          <p>
            Introducing "RToken" (RCoin)!
            Token price is <span class="token-price"></span> Ether. You currently have <span class="RToken-balance"></span>&nbsp;RCoin.
          </p>
          <br/>
          <form onSubmit="App.buyTokens(); return false;" role="form">
            <div class="form-group">
              <div class="input-group">
                <input id="numberOfTokens" class="form-control input-lg" type="number" name="number" value="1" min="1" pattern="[0-9]">
                </input>
                <span class="input-group-btn">
                  <button type="submit" class="btn btn-primary btn-lg">Buy Tokens</button>
                </span>
              </div>
            </div>
          </form>
          <br>
           <div class="progress">
              <div id="progress" class="progress-bar progress-bar-striped active" aria-valuemin="0" aria-valuemax="100">
              </div>
            </div>
          <p><span class="tokens-sold"></span> / <span class="tokens-available"></span> tokens sold</p>
          <hr>
          <p id="accountAddress"></p>
        </div>
      </div>
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/web3.min.js"></script>
    <script src="js/truffle-contract.min.js"></script>
    <script src="js/app.js"></script>
  </body>
</html>

0

From your screenshot there's no account

Account: null

So App.account is null and the contract fails because of that.

Now Metamask requires user approval before returning the user's address

From their docs you have to add a button and call

ethereum.enable()

Metamask will save the user decision so the user will be asked only once.

Due to the async nature of the API you have to wait until the user decides before making any query that requires the account.

// Load account data
web3.eth.getCoinbase(function(err, account) {
    App.account = account;
    ..
})

// Load token sale contract
App.contracts.RTokenSale.deployed().then(function(instance) {
    // Code here might execute before App.account is initialized
})

For example something like this will work

async function enableUser() {
    const accounts = await ethereum.enable();
    const account = accounts[0];
    App.account = account;
    loadContracts();
}

async function loadContracts() {
    App.contracts.RToken.deployed().then(function(instance) {
        console.log("Instance: ", instance);
        RTokenInstance = instance;
        console.log("Account: ", App.account);
        return RTokenInstance.balanceOf(App.account);
    });
}
| improve this answer | |
  • I tried your solution and I got many errors as I did not know where exactly to put that async function on the code. Thank You very much for answering. Actually, I am a novice when it comes to full stack development in blockchain so it would be better if you can post the entire code of the app.js that I should be writing. – Ridwan Chowdhury Apr 6 at 1:31
  • My console is displaying this error: app.js:99 Uncaught (in promise) TypeError: web3.fromWei(...).toNumber is not a function at app.js:99 After I click enable button a metamask warning pops up saying inpage.js:1 MetaMask: MetaMask will soon stop reloading pages on network change. If you rely upon this behavior, add a 'networkChanged' event handler to trigger the reload manually: metamask.github.io/metamask-docs/API_Reference/… – Ridwan Chowdhury Apr 6 at 1:43
  • @RidwanChowdhury Sorry but I'm busy right now and I can't write a full example. Your second problem seems unrelated it appears you are mixing code for web3 v0.20 and v1.2. In web3 v1.2 it is web3.utils.fromWei(), also using .toNumber() might cause problems if the number you are handling are large beause javascript lacks of support. – Ismael Apr 6 at 19:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.