warashibe / nextdapp

(D)App Development Kit powered by Next.js and Facebook Recoil
https://nextdapp.org
MIT License
45 stars 3 forks source link

Can't get contract function to work #134

Closed ing-norante closed 3 years ago

ing-norante commented 3 years ago

Hello, I'm playing around with next-dapp and I'm trying to call a custom function on a contract deployed on Ropsten, so following this example, I wrote:

import { useEffect } from "react";
import { bind, Tracker } from "nd";
import { fromWei, contract } from "nd/web3";

const TokenCounter = bind(
  ({ set, init, tokenCount }) => {
    const { initWeb3 } = init();
    useEffect(() => {
      initWeb3();
    }, []);
    return (
      <div>
          <span>{tokenCount}</span>
        <Tracker
          name="getTokenCount"
          watch={["web3_address", "web3_updated"]}
          func={async ({ get, set }) => {
            const uniswapAddr = "0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351";
            const factoryABI = [
              {
                name: "NewExchange",
                inputs: [
                  { type: "address", name: "token", indexed: true },
                  { type: "address", name: "exchange", indexed: true },
                ],
                anonymous: false,
                type: "event",
              },
              {
                name: "initializeFactory",
                outputs: [],
                inputs: [{ type: "address", name: "template" }],
                constant: false,
                payable: false,
                type: "function",
                gas: 35725,
              },
              {
                name: "createExchange",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "address", name: "token" }],
                constant: false,
                payable: false,
                type: "function",
                gas: 187911,
              },
              {
                name: "getExchange",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "address", name: "token" }],
                constant: true,
                payable: false,
                type: "function",
                gas: 715,
              },
              {
                name: "getToken",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "address", name: "exchange" }],
                constant: true,
                payable: false,
                type: "function",
                gas: 745,
              },
              {
                name: "getTokenWithId",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "uint256", name: "token_id" }],
                constant: true,
                payable: false,
                type: "function",
                gas: 736,
              },
              {
                name: "exchangeTemplate",
                outputs: [{ type: "address", name: "out" }],
                inputs: [],
                constant: true,
                payable: false,
                type: "function",
                gas: 633,
              },
              {
                name: "tokenCount",
                outputs: [{ type: "uint256", name: "out" }],
                inputs: [],
                constant: true,
                payable: false,
                type: "function",
                gas: 663,
              },
            ];

            const uniswap = contract({
              abi: factoryABI,
              address: uniswapAddr,
            });
            console.log("Uniswap", uniswap);
            const [err, receipt] = await uniswap.tokenCount(
              get("web3_address")
            );
            console.log("Err: ", err);
            console.log("Receipt: ", receipt);
            set(fromWei(receipt), "tokenCount");
          }}
        />
      </div>
    );
  },
  ["erc20", "initWeb3", { tokenCount: 0 }]
);

I'm pretty sure about the ABI correctness because I've just copy-pasted from Uniswap official documentation, the same applies to the contract address.

I don't get any error on the console, but on the frontend, I always get TypeError: Cannot read property 'abi' of undefined

Cannot read property 'abi' of undefine

Any hint on what am I doing wrong?

Cheers! 👍🏻

ocrybit commented 3 years ago

You could rewrite it like the following. It worked for me.

import { useEffect } from "react";
import { bind, Tracker } from "nd";
import { fromWei } from "nd/web3";

const TokenCounter = bind(
  ({ set, init, tokenCount }) => {
    const fn = init(["initWeb3", "erc20", "contract"] );
    useEffect(() => {
      fn.initWeb3();
    }, []);
    return (
      <div>
          <span>{tokenCount}</span>
        <Tracker
          name="getTokenCount"
          watch={["web3_address", "web3_updated"]}
          func={async ({ get, set }) => {
            const uniswapAddr = "0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351";
            const factoryABI = [
              {
                name: "NewExchange",
                inputs: [
                  { type: "address", name: "token", indexed: true },
                  { type: "address", name: "exchange", indexed: true },
                ],
                anonymous: false,
                type: "event",
              },
              {
                name: "initializeFactory",
                outputs: [],
                inputs: [{ type: "address", name: "template" }],
                constant: false,
                payable: false,
                type: "function",
                gas: 35725,
              },
              {
                name: "createExchange",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "address", name: "token" }],
                constant: false,
                payable: false,
                type: "function",
                gas: 187911,
              },
              {
                name: "getExchange",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "address", name: "token" }],
                constant: true,
                payable: false,
                type: "function",
                gas: 715,
              },
              {
                name: "getToken",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "address", name: "exchange" }],
                constant: true,
                payable: false,
                type: "function",
                gas: 745,
              },
              {
                name: "getTokenWithId",
                outputs: [{ type: "address", name: "out" }],
                inputs: [{ type: "uint256", name: "token_id" }],
                constant: true,
                payable: false,
                type: "function",
                gas: 736,
              },
              {
                name: "exchangeTemplate",
                outputs: [{ type: "address", name: "out" }],
                inputs: [],
                constant: true,
                payable: false,
                type: "function",
                gas: 633,
              },
              {
                name: "tokenCount",
                outputs: [{ type: "uint256", name: "out" }],
                inputs: [],
                constant: true,
                payable: false,
                type: "function",
                gas: 663,
              },
            ];

            const uniswap = fn.contract({
              abi: factoryABI,
              address: uniswapAddr,
            });
            console.log("Uniswap", uniswap);
            const receipt = await uniswap.tokenCount();
            console.log("Receipt: ", receipt);
            set(receipt, "tokenCount");
          }}
        />
      </div>
    );
  },
  [{ tokenCount: 0 }]
);

There are some points to note here.

  1. const fn = init(["initWeb3", "erc20", "contract"] ) is a convenient way to not write the function names twice.
  2. contract shouldn't be imported directly but should be used as a global function.
  3. non-transaction calls don't return err, so const receipt = await uniswap.tokenCount()
  4. uniswap.tokenCount has no argument.
  5. the return value is not wei. so set(receipt, "tokenCount"); will suffice.

Would this help?

ing-norante commented 3 years ago

That's wonderful! Thank you very much! You definitely cleared the doubts I had and now it's working perfectly. I'll close the issue and keep experimenting with next-dapp 👍🏻

Kudos for this project because it's sick!