Smart Contract Interaction – How to Use without Metamask

javascriptweb3js

I'm writing an ethereum application that I want to use for an IoT demonstrator. I want to use MQTT to subscribe to an topic and as soon as a signal is transmitted to that topic I want to write something on the ethereum blockchain. The problem I'm facing is that I struggle with actually -writing- something on the blockchain.
I wrote the following contract on remix:

pragma solidity ^0.5.12;

contract NewContract {

    struct Location {
        string JSON;
        string comment;
    }

    Location[] locations;

    function createLocation(string memory _json) public {
        locations.push(Location(_json, 'no comment'));
    }

    function addComment(string memory _comment, uint _id) public {
        locations[_id].comment = _comment;
    }

    function viewComment(uint _id) public view returns(string memory) {
        string memory result = locations[_id].comment;
        return result;
    }

    function viewNewestComment() public view returns(string memory) {
        uint _id = locations.length - 1;
        string memory result = locations[_id].comment;
        return result;
    }


}

What I want to do: I want to save data (called "JSON") inside a struct and add a comment to that. I tested the contract on remix and there are no errors. I sent it to the kovan testnet with my metamask provider. The contract address is: "0x5b720C7f5063FcD04a21a6F4F25c76e32e7C64cc" and the abi is:

[
    {
        "constant": false,
        "inputs": [
            {
                "internalType": "string",
                "name": "_comment",
                "type": "string"
            },
            {
                "internalType": "uint256",
                "name": "_id",
                "type": "uint256"
            }
        ],
        "name": "addComment",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "internalType": "string",
                "name": "_json",
                "type": "string"
            },
            {
                "internalType": "string",
                "name": "_comment",
                "type": "string"
            }
        ],
        "name": "createLocation",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_id",
                "type": "uint256"
            }
        ],
        "name": "viewComment",
        "outputs": [
            {
                "internalType": "string",
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_id",
                "type": "uint256"
            }
        ],
        "name": "viewLocation",
        "outputs": [
            {
                "internalType": "string",
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "viewNewestComment",
        "outputs": [
            {
                "internalType": "string",
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }
]

I now want to start my app.js file in my vscode console to access that smart contract and call the functions of the contract: "createLocation", "addComment", "viewComment", "viewNewestComment". I left out the MQTT script because that isn't causing the problem.

const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("https://kovan.infura.io/v3/---infura TOKEN---"));
var address = "0x5b720C7f5063FcD04a21a6F4F25c76e32e7C64cc";
var abi = [---abi I mentioned above---];
var contract = new web3.eth.Contract(abi, address);



contract.methods.viewNewestComment().call().then(console.log); //this command is working
contract.methods.createLocation("test-location4", "comment4").call().then(console.log); // this command isn't working 

The error I receive is:

comment3
Result {}

I created some "test-locations" and gave them comments: "comment3" is the latest comment but when I try to create "test-location4" I get nothing as a result. As far as I understand, I need to provide an address with ether (test ether because I'm using kovan) on it to actually change the storage on the blockchain and write something new.

How do I inject an account with ether on it to perform a -write- command?

@mods: I hope it is okay to start a new topic with an clearer question.
My old topic is here Executing an contract via code and without metamask / link an account to JS code to automatically wirte on Ethereum
The user "goodvibration" did a great job trying to help me, but I think I posted confusing information and I'm a beginner so I probably didn't quite understand his solution.
But I think I didn't point out my problem correctly and therefore confusing anyone who read it.

Best Answer

Here:

contract.methods.viewNewestComment().call().then(console.log);
contract.methods.createLocation("test-location4", "comment4").call().then(console.log);

Your two ABI-encoded objects are:

  1. contract.methods.viewNewestComment()
  2. contract.methods.createLocation("test-location4", "comment4")

If the encoded function changes the state of the contract, then the object is considered a transaction, and you should run object.send(options) in order to execute this transaction.

If the encoded function does not change the state of the contract, then the object is not considered a transaction, and you should run object.call() in order to get the return-value of that function.

In web3.js v1.x, object.send takes the following options:

{
    to      : object._parent._address,
    data    : object.encodeABI(),
    gas     : await object.estimateGas({from: Your-Account-Address}),
    gasPrice: Whatever-Gas-Price-You-Want,
    value   : value // amount of wei-ether that you want to send to the 'to' address
}

Of course, your account must hold at least gas * gasPrice + value wei-ether.

Note that you will need to unlock your account on the node which you're communicating with, in order for the above to complete successfully (the answer that you've linked in your question explains how to do this without unlocking your account beforehand).


At this point, the only part remaining unanswered in your question is:

How do I inject an account with ether on it to perform a -write- command?

You can ask someone else to transfer ether to your account, or you can mine ether yourself.

I'll focus on the first option, because the second option is out-of-context IMO:

You can ask someone else to transfer ether to your account

If you're running on mainnet, then that someone else will most likely ask you to give them something in return, a convention more commonly known as trading (buying and selling), which has been used in every non-charity monetary system that we know of since the dawn of humanity.

If you're running on a testnet, then there are various public websites which will give you a certain amount of ether per day per address per IP (i.e., they will gray-list you based on each one of these criteria in order to limit the amount of ether that you can ask for).

For example, this ropsten faucet will give you 1 ether a day.

Related Topic