Web3js – How to Interact with Smart Contracts on Ropsten

erc-20infuratokensweb3js

I've looked for hours online for how to interact with contracts but I've yet to find an efficient way to do so. My contract is deployed at 0x09B81faA7fB51E5Af79b0241243e58297aC84158 on the ropsten network. I just want to know how to call my method

    function getChallengeNumber() public view returns (bytes32) {
        return challengeNumber;
    }

and

function mint(uint256 nonce, bytes32 challenge_digest) public returns (bool success) {

Obviously I can interact with ropsten but I want to set up a script that can find solutions for the PoW method "mint" and then push solution when it find them.

I've found a bunch of fragmented explanations but nothing that comes close to comprehensive. I can interact with it via remix. I figured out how to call methods that do not alter the state of the network with python but I haven't yet to find any way to interact with my contract in a sustainable way.

Any guidance would be appreciated.


Building off @goodvibrations answer. Because I only have a JSON file I'm working with, I've changed the update method somewhat.

async function deploy(web3, account, gasPrice, contractName, contractArgs) {
    const json_file  = fs.readFileSync(ARTIFACTS_DIR + contractName + ".json", {encoding: "utf8"});
    const json_obj = JSON.parse(json_file);
    const abi = json_obj['abi'];
    const bin = json_obj['bytecode'];
    const contract = new web3.eth.Contract(JSON.parse(abi));
    const options = {data: "0x" + bin}  //arguments: contractArgs}; <-- I got rid of this because my constructor doesn't accept any arguments
    const transaction = contract.deploy(options);
    const receipt = await send(web3, account, gasPrice, transaction);
    return new web3.eth.Contract(JSON.parse(abi), receipt.contractAddress);
}

But now I get a new error.

(node:5672) UnhandledPromiseRejectionWarning: Error: Provider not set or invalid
    at Object.InvalidProvider (~/node_modules/web3/node_modules/web3-core-helpers/src/errors.js:38:16)
    at RequestManager.send (~/node_modules/web3/node_modules/web3-core-requestmanager/src/index.js:128:32)
    at sendRequest (~/node_modules/web3/node_modules/web3-core-method/src/index.js:705:42)
    at Eth.send [as getGasPrice] (/home/lnielsen/node_modules/web3/node_modules/web3-core-method/src/index.js:726:13)

Am I misunderstanding the implementation?

Best Answer

You can do something like this:

const fs   = require("fs");
const Web3 = require("web3");

const NODE_ADDRESS  = process.argv[2];
const PRIVATE_KEY   = process.argv[3];
const CONTRACT_NAME = process.argv[4];

const ARTIFACTS_DIR = __dirname + "<path to your bin/abi folder relative to this script>";
const MIN_GAS_LIMIT = 100000; // increase this if necessary

async function scan(message) {
    process.stdout.write(message);
    return await new Promise(function(resolve, reject) {
        process.stdin.resume();
        process.stdin.once("data", function(data) {
            process.stdin.pause();
            resolve(data.toString().trim());
        });
    });
}

async function getGasPrice(web3) {
    while (true) {
        const nodeGasPrice = await web3.eth.getGasPrice();
        const userGasPrice = await scan(`Enter gas-price or leave empty to use ${nodeGasPrice}: `);
        if (/^\d+$/.test(userGasPrice))
            return userGasPrice;
        if (userGasPrice == "")
            return nodeGasPrice;
        console.log("Illegal gas-price");
    }
}

async function getTransactionReceipt(web3) {
    while (true) {
        const hash = await scan("Enter transaction-hash or leave empty to retry: ");
        if (/^0x([0-9A-Fa-f]{64})$/.test(hash)) {
            const receipt = await web3.eth.getTransactionReceipt(hash);
            if (receipt)
                return receipt;
            console.log("Invalid transaction-hash");
        }
        else if (hash) {
            console.log("Illegal transaction-hash");
        }
        else {
            return null;
        }
    }
}

async function send(web3, account, gasPrice, transaction, value = 0) {
    while (true) {
        try {
            const options = {
                to      : transaction._parent._address,
                data    : transaction.encodeABI(),
                gas     : Math.max(await transaction.estimateGas({from: account.address}), MIN_GAS_LIMIT),
                gasPrice: gasPrice ? gasPrice : await getGasPrice(web3),
                value   : value,
            };
            const signed  = await web3.eth.accounts.signTransaction(options, account.privateKey);
            const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);
            return receipt;
        }
        catch (error) {
            console.log(error.message);
            const receipt = await getTransactionReceipt(web3);
            if (receipt)
                return receipt;
        }
    }
}

async function deploy(web3, account, gasPrice, contractName, contractArgs) {
    const abi = fs.readFileSync(ARTIFACTS_DIR + contractName + ".abi", {encoding: "utf8"});
    const bin = fs.readFileSync(ARTIFACTS_DIR + contractName + ".bin", {encoding: "utf8"});
    const contract = new web3.eth.Contract(JSON.parse(abi));
    const options = {data: "0x" + bin, arguments: contractArgs};
    const transaction = contract.deploy(options);
    const receipt = await send(web3, account, gasPrice, transaction);
    return new web3.eth.Contract(JSON.parse(abi), receipt.contractAddress);
}

async function run() {
    const web3 = new Web3(NODE_ADDRESS);

    const gasPrice = await getGasPrice(web3);
    const account  = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY);

    const myContract = await deploy(web3, account, gasPrice, CONTRACT_NAME, [arg1, arg2]);
    console.log(CONTRACT_NAME, "deployed at", myContract._address);

    const receipt = await send(web3, account, gasPrice, myContract.mint(arg3, arg4));
    console.log("Mint receipt:", JSON.stringify(receipt, null, 4));

    const challengeNumber = await myContract.methods.getChallengeNumber().call();
    console.log(Web3.utils.hexToAscii(challengeNumber));

    if (web3.currentProvider.constructor.name == "WebsocketProvider")
        web3.currentProvider.connection.close();
}

run();

And then call it from command-line, for example:

node MyScript.js https://ropsten.infura.io/v3/<MyProjectId> <MyPrivateKey> <MyContractName>

Tested with:

  • NodeJS v10.16.0
  • Web3.js v1.2.1

Note that:

  • I've used arg1/arg2 for arguments passed to your contract's constructor as an example, because you haven't specified how many there are and what values you want to set them to
  • I've used arg3/arg4 for arguments passed to your contract's mint function as an example, because you haven't specified what values you want to set them to

Referring to your updated question:

const abi = json_obj['abi'];
const bin = json_obj['bytecode'];
const contract = new web3.eth.Contract(JSON.parse(abi));
const options = {data: "0x" + bin}; // , arguments: contractArgs};
// I got rid of this because my constructor doesn't accept any arguments
  1. Cosmetic: in Javascript, you can do json_obj.abi and json_obj.bytecode
  2. Optional: instead of ommiting arguments altogether, you can use arguments: []
  3. Your problem: json_obj.bytecode already starts with 0x, so change "0x" + bin to bin
Related Topic