Search code examples
reactjssolidityweb3jsethers.jsdecentralized-applications

How to get the returned data from a smart contract function using ethers.js?


I'm trying to consume a function from a smart contract using ethers.js. The function retrieve the info of a user logged before (with the help of other function). This is the function snippet.

function getUser(address _userAddress)
        public
        onlyAuthCaller
        returns (
            string memory name,
            string memory info,
            string memory role,
        )
    {
        User memory tmpData = userDetails[_userAddress];
        return (
            tmpData.name,
            tmpData.info,
            tmpData.role
        );
    }

With React, I'm rendering a button to get user info, as follow:

const GetUser = () => {
    const askUser = async () => {
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const account = await window.ethereum.request({
            method: "eth_requestAccounts",
        });
        const signer = provider.getSigner();
        const erc20 = new ethers.Contract(
            ContractAddress,
            ContractABI.abi,
            signer
        );

        try {
            const user = await erc20.getUser(account[0]);
            console.log(user);
        } catch (error) {
            console.log("ERROR AT GETTING USER: ", error);
        }
    };
    return (
        <div>
            <Button type="submit" variant="contained" onClick={askUser}>
                GET USER
            </Button>
        </div>
    );
};

I wonder why I'm not getting the return result of the smart contract getUser function, I expected that info at const user after awaiting the function. Instead on const user, I'm having the transaction metadata, as follow:

{hash: '0x24818569ec29d328b66f58736750a420a5a3bd8e28a72a6a0f72fd8ba5e088d8', type: 2, accessList: null, blockHash: null, blockNumber: null, …}
accessList: null
blockHash: null
blockNumber: null
chainId: 0
confirmations: 0
creates: null
data: "0x6f77926b00000000000000000000000086b2b772014a87730928c7e54f4762d2c09ea4e5"
from: "0x86b2b772014A87730928c7e54F4762d2c09eA4e5"
gasLimit: BigNumber {_hex: '0xd15f', _isBigNumber: true}
gasPrice: BigNumber {_hex: '0x73a20d0c', _isBigNumber: true}
hash: "0x24818569ec29d328b66f58736750a420a5a3bd8e28a72a6a0f72fd8ba5e088d8"
maxFeePerGas: BigNumber {_hex: '0x73a20d0c', _isBigNumber: true}
maxPriorityFeePerGas: BigNumber {_hex: '0x73a20d00', _isBigNumber: true}
nonce: 5
r: "0x6a8fed76397e03a2fc564d18e1ec12abdf39a38fbe825df990f744bb50fc4a8b"
s: "0x66e9b4513047b65aac724dc6fb07d069967f6ca6fd8cd5fe85f6dbe495864765"
to: "0x9719E9dC77A7eDD3825844c77a68c896d4a7BB2b"
transactionIndex: null
type: 2
v: 0
value: BigNumber {_hex: '0x00', _isBigNumber: true}
wait: confirmations => {…}
length: 1
name: ""
arguments: (…)
caller: (…)
[[FunctionLocation]]: index.ts:336
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[4]
[[Prototype]]: Object

When I tried my contract's functions on Remix IDE, all worked as expected. For instance, at Remix I get this answer, in which the data retrieved by the function is on decoded output.

status  true Transaction mined and execution succeed
transaction hash    0x206af46a0f8e6bcc04ae632c85da005c901d8fc82f650e8d40a445f6988adcc2
from    0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
to  SupplychainUser.getUser(address) 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B
gas 61639 gas
transaction cost    53599 gas 
execution cost  53599 gas 
input   0x6f7...35cb2
decoded input   {
    "address _userAddress": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"
}
decoded output  {
    "0": "string: name Carl Bertz",
    "1": "string: info 0987654321",
    "2": "string: role processor",
}
logs    []
val 0 wei

I would like the same but with React, so how can I have the returned data from the contract getUser function?


Solution

  • The reason you are getting the transaction metadata when calling getUser is because the getUser function is not a view function. Not being a view function causes the blockchain to create a transaction for that specific function call which has to be validated by the blockchain and is not available at the moment you execute your getUser function.

    For that type of situation, the recommended approach is to use events i.e. emitting an event with the information you need and, on the react side, listen for the event.

    Consider making the getUser function a view function, as it does not change the state of the contract:

    function getUser(address _userAddress)
            public
            view
            onlyAuthCaller
            returns (
                string memory name,
                string memory info,
                string memory role,
            )
        {
            User memory tmpData = userDetails[_userAddress];
            return (
                tmpData.name,
                tmpData.info,
                tmpData.role
            );
        }