Search code examples
javascriptethereumsolidityweb3js

How can I define my Solidity contract as a javascript object?


I'm making a simple app to act as a front end for a smart contract. The contract has a public string attribute message which I expected to be able to access through newContract.methods.message().call().

I defined an onclick call to a showMessage() function which should log the message attribute to the console, however when I click the button with the onclick event, I receive the error messages at the bottom of this post.

The checks below the instantiation of the new contract object indicate that the type is not undefined, but I still receive an undefined error anyway.

Edit:
Removed extraneous ABI parts and added smart contract source code.

The issue appears to be with the scope of the newContract object. Even though it's declared with var, it isn't accessible through the global window object inside the showMessage() function. What is the correct scope?

<!DOCTYPE html>
<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.3.5/web3.min.js" integrity="sha512-S/O+gH5szs/+/dUylm15Jp/JZJsIoWlpSVMwT6yAS4Rh7kazaRUxSzFBwnqE2/jBphcr7xovTQJaopiEZAzi+A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
        
        <script type="text/javascript">
            
            let web3js;

            function startApp() {            
                var address = "...";
                const jsoninterface = [
                
                    {
                        "inputs": [
                            {
                                "internalType": "string",
                                "name": "_newMessage",
                                "type": "string"
                            }
                        ],
                        "name": "setMessage",
                        "outputs": [],
                        "stateMutability": "nonpayable",
                        "type": "function"
                    },
                   
                    {
                        "inputs": [],
                        "name": "message",
                        "outputs": [
                            {
                                "internalType": "string",
                                "name": "",
                                "type": "string"
                            }
                        ],
                        "stateMutability": "view",
                        "type": "function"
                    }
                ]
                var newContract = new web3js.eth.Contract(jsoninterface, address);
                if (typeof newContract === 'object' && typeof newContract !== 'undefined' && newContract !== null){
                    console.log("contract created")
                } else {
                    console.log("contract not created")
                }
            }
            window.addEventListener('load', function() {

                // Checking if Web3 has been injected by the browser (Mist/MetaMask)
                if (typeof web3 !== 'undefined') {
                // Use Mist/MetaMask's provider
                web3js = new Web3(web3.currentProvider);
                } else {
                    alert("Please install Metamask to continue");
                // Handle the case where the user doesn't have Metamask installed
                // Probably show them a message prompting them to install Metamask
                }

                // Now you can start your app & access web3 freely:
                startApp();
                console.log('startapp finished')
            })

            function showMessage(){
                var retval = window.newContract.methods.message().call();
                console.log(retval);
            }

        </script>
    </head>
    <body>
        <!-- the content goes here -->
        <button onclick="showMessage()">click here</button>
    </body>
</html>

Here is the output to the console:

contract created
startapp finished
Uncaught TypeError: Cannot read property 'message' of undefined
    at showMessage ((index):132)
    at HTMLButtonElement.onclick ((index):140)
showMessage @ (index):132
onclick @ (index):140

Below is the smart contract code:

pragma solidity >=0.7.0 <0.9.0;


contract HelloWorld {
    string public message = "Hello World";
    uint public counter = 0;
    
    function setMessage(string calldata _newMessage) public {
        message = _newMessage;
    }
    
    function incrementCounter() public {
        counter++;
    }
}

Solution

  • The issue did turn out to be the scope of the contract variable, making the two changes indicated by arrows below allowed me to solve my issue.

    <!DOCTYPE html>
    <html>
        <head>
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.3.5/web3.min.js" integrity="sha512-S/O+gH5szs/+/dUylm15Jp/JZJsIoWlpSVMwT6yAS4Rh7kazaRUxSzFBwnqE2/jBphcr7xovTQJaopiEZAzi+A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
            
            <script type="text/javascript">
                
                let web3js;
     ---------> var newContract;
                function startApp() {            
                    var address = "...";
                    const jsoninterface = [
                    
                        {
                            "inputs": [
                                {
                                    "internalType": "string",
                                    "name": "_newMessage",
                                    "type": "string"
                                }
                            ],
                            "name": "setMessage",
                            "outputs": [],
                            "stateMutability": "nonpayable",
                            "type": "function"
                        },
                       
                        {
                            "inputs": [],
                            "name": "message",
                            "outputs": [
                                {
                                    "internalType": "string",
                                    "name": "",
                                    "type": "string"
                                }
                            ],
                            "stateMutability": "view",
                            "type": "function"
                        }
                    ]
      ---------> window.newContract = new web3js.eth.Contract(jsoninterface, address);
                    if (typeof newContract === 'object' && typeof newContract !== 'undefined' && newContract !== null){
                        console.log("contract created")
                    } else {
                        console.log("contract not created")
                    }
                }
                window.addEventListener('load', function() {
    
                    // Checking if Web3 has been injected by the browser (Mist/MetaMask)
                    if (typeof web3 !== 'undefined') {
                    // Use Mist/MetaMask's provider
                    web3js = new Web3(web3.currentProvider);
                    } else {
                        alert("Please install Metamask to continue");
                    // Handle the case where the user doesn't have Metamask installed
                    // Probably show them a message prompting them to install Metamask
                    }
    
                    // Now you can start your app & access web3 freely:
                    startApp();
                    console.log('startapp finished')
                })
    
                function showMessage(){
                    var retval = window.newContract.methods.message().call();
                    console.log(retval);
                }
    
            </script>
        </head>
        <body>
            <!-- the content goes here -->
            <button onclick="showMessage()">click here</button>
        </body>
    </html>