Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot read property '_hex' of undefined #1237

Closed
EvilJordan opened this issue Jan 13, 2021 · 17 comments
Closed

Cannot read property '_hex' of undefined #1237

EvilJordan opened this issue Jan 13, 2021 · 17 comments
Labels
discussion Questions, feedback and general information.

Comments

@EvilJordan
Copy link

I'm attempting to use a provider/signer obtained from web3Modal and WalletConnect, and then call a Contract method.

const providerOptions = {
	walletconnect: {
		package: window.WalletConnectProvider.default,
		options: {
			infuraId: INFURAID,
		}
	}
};
web3Modal = new window.Web3Modal.default({
	cacheProvider: false,
	providerOptions,
	disableInjectedProvider: false
});
web3Provider = await web3Modal.connect();
provider = new ethers.providers.Web3Provider(web3Provider).provider;
signer = new ethers.providers.Web3Provider(web3Provider).getSigner();

contract = new ethers.Contract(CONTRACTADDRESS, CONTRACTABI, signer);

The next line throws an error:
tx = await contract.myMethod(PARAM1, ethers.utils.formatBytes32String(STRINGPARAM2));
Uncaught TypeError: Cannot read property '_hex' of undefined

This all works just fine when NOT using web3Modal/WalletConnect as a provider, and instead relying on injected MetaMask. I'm not sure what to do or even how to start debugging this. Any help would be wildly appreciated!

@ricmoo
Copy link
Member

ricmoo commented Jan 13, 2021

Can you include the ABI and the values you are passing in?

@EvilJordan
Copy link
Author

EvilJordan commented Jan 13, 2021

contract = new ethers.Contract('0x1319b0457c4e39a259F8e18d1e2Dc316A941aE20', ['function createToken(address tokenOwner, bytes32 ticket) public'], signer);
tx = await contract.createToken('0xd4e01f608982ff53022e8c3ff43e145a192a9c4a', ethers.utils.formatBytes32String('123'));

Contract is on Rinkeby if you want to run it yourself. Can also pass any valid eth address as param1.

@yuetloo
Copy link
Collaborator

yuetloo commented Jan 13, 2021

@EvilJordan
I was able to create a token successfully using your contract on Rinkeby. Here's the transaction hash

I also created a sample using ethers.js based on the original WalletConnect vanilla javascript example.

The code to call a contract using ethers.js is here:

https://github.com/yuetloo/web3modal-vanilla-js-example/blob/main/example.js#L176-L206

I used your contract on Rinkeby in the example. So, the Create Token section only shows up if you select to connect to the Rinkeby network.

@yuetloo
Copy link
Collaborator

yuetloo commented Jan 13, 2021

Note: the createToken function reverts if a token was already created for the same address and ticket number. This causes ethers to throw BigNumber conversion error in estimateGas for the InfuraProvider. Will create an issue for this later.

@EvilJordan
Copy link
Author

@yuetloo This is amazing! So something has gone very wrong in my own front-end Web3Modal/WalletConnect provider code if you've been able to make it work. Thank you!!

@EvilJordan
Copy link
Author

I can confirm this works! Now I'm working on normalizing the provider/wallet/signer interface when window.ethereum is present, so there isn't forking code depending on availability.

@EvilJordan
Copy link
Author

For posterity, this is now completely working! Couldn't have done it without @yuetloo. Bless you!!
Here's the code that allows for either window.ethereum or web3modal/walletconnect providing the provider:

var publicAddress, provider, wallet;

if (window.ethereum) {
	await window.ethereum.enable();
	provider = new ethers.providers.Web3Provider(window.ethereum);
	publicAddress = window.ethereum.selectedAddress;
	wallet = provider;
} else {
	const providerOptions = {
		walletconnect: {
			package: window.WalletConnectProvider.default,
			options: {
				infuraId: INFURAID,
			}
		}
	};
	web3Modal = new window.Web3Modal.default({
		cacheProvider: false,
		providerOptions,
		disableInjectedProvider: false
	});
	provider = await web3Modal.connect();
	publicAddress = provider.accounts[0];
	wallet = new ethers.providers.Web3Provider(provider);
}
const signer = wallet.getSigner();
const contractSigner = new ethers.Contract(CONTRACTADDRESS, CONTRACTABI, signer);
tx = await contractSigner.method(PARAM1, ethers.utils.formatBytes32String(STRINGPARAM));

@ricmoo ricmoo added the discussion Questions, feedback and general information. label Jan 16, 2021
@ricmoo
Copy link
Member

ricmoo commented Jan 19, 2021

Closing this now. Tracking better error messages can be found in #1243.

Please feel free to re-open if I'm mistaken that this has been resolved.

Thanks! :)

@ricmoo ricmoo closed this as completed Jan 19, 2021
@EvilJordan
Copy link
Author

I'm piggy-backing off this because of the awesome example @yuetloo wrote.
I changed it a bit to strip out all the Web3Modal stuff (also removed from the index.html <script>s) and only use ethers to demonstrate a new(?) problem when using the MetaMask browser extension: The error thrown by the contract's require statement (an expected error forced in testing), is

  1. Not catchable when coming direct from MM (no matter what it outputs the error to the console, but perhaps this is MM's fault)
  2. Not well-formed when caught by javascript/ethers (it's a weird mix of string + JSON that isn't parseable)

This is the error that's being caught:

Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"code":-32603,"message":"execution reverted: ERC721: token already minted","data":{"originalError":{"code":3,"data":"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c4552433732313a20746f6b656e20616c7265616479206d696e74656400000000","message":"execution reverted: ERC721: token already minted"}}}, method="estimateGas", transaction={"from":"0xD4e01f608982FF53022E8C3fF43E145A192a9c4a","to":"0x1319b0457c4e39a259F8e18d1e2Dc316A941aE20","data":"0x6368d3aa000000000000000000000000d4e01f608982ff53022e8c3ff43e145a192a9c4a3132330000000000000000000000000000000000000000000000000000000000"}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.0.19)

Here's the modified example.js code so you can see it for yourself:

"use strict";

/**
 * Example JavaScript code that interacts with the page and Web3 wallets
 */

// Unpkg imports
const ethers = window.ethers;

// Chosen wallet provider given by the dialog window
let provider;

// token contract address
const contractAddress = "0x1319b0457c4e39a259F8e18d1e2Dc316A941aE20";
// contract abi
const contractAbi = [
    "function createToken(address tokenOwner, bytes32 ticket) public",
];

function getInfuraId() {
    const provider = new ethers.providers.InfuraProvider();
    return provider.apiKey;
}

/**
 * Setup the orchestra
 */
function init() {
    console.log("Initializing example");
    console.log(
        "window.ethers is",
        ethers,
        "window.ethereum is",
        window.ethereum
    );
}

/**
 * Kick in the UI action after Web3modal dialog has chosen a provider
 */
async function fetchAccountData() {
	const wallet = new ethers.providers.Web3Provider(window.ethereum);
    const network = await wallet.getNetwork();
    document.querySelector("#network-name").textContent = network.name;

    // token contract only exists on rinkeby
    document.querySelector("#create-token").style.display =
        network.name === "rinkeby" ? "block" : "none";

    // Get account of the connected wallet
	const signer = wallet.getSigner();
    const selectedAccount = await signer.getAddress();
    document.querySelector("#selected-account").textContent = selectedAccount;

    // set the token recipient address to the signer address
    document.getElementById("token-recipient").value = selectedAccount;

    // Get account balance
    const balance = await signer.getBalance();
    document.querySelector(
        "#account-balance"
    ).textContent = ethers.utils.formatEther(balance);

    // Display fully loaded UI for wallet data
    document.querySelector("#prepare").style.display = "none";
    document.querySelector("#connected").style.display = "block";
    document.getElementById("btn-create-token").removeAttribute("disabled");
}

/**
 * Fetch account data for UI when
 * - User switches accounts in wallet
 * - User switches networks in wallet
 * - User connects wallet initially
 */
async function refreshAccountData() {
    // If any current data is displayed when
    // the user is switching acounts in the wallet
    // immediate hide this data
    document.querySelector("#connected").style.display = "none";
    document.querySelector("#prepare").style.display = "block";

    // hide the createToken result on page refresh, only displays the result
    // when result is returned from the contract
    document.getElementById("status").textContent = "";

    // Disable button while UI is loading.
    // fetchAccountData() will take a while as it communicates
    // with Ethereum node via JSON-RPC and loads chain data
    // over an API call.
    document.querySelector("#btn-connect").setAttribute("disabled", "disabled");
    await fetchAccountData();
    document.querySelector("#btn-connect").removeAttribute("disabled");
}

/**
 * Connect wallet button pressed.
 */
async function onConnect() {
    try {
		provider = window.ethereum;
		await window.ethereum.enable()
    } catch (e) {
        console.log("Could not get a wallet connection", e);
        return;
    }

    // Subscribe to accounts change
    provider.on("accountsChanged", (accounts) => {
        fetchAccountData();
    });

    // Subscribe to chainId change
    provider.on("chainChanged", (chainId) => {
        fetchAccountData();
    });

    // Subscribe to networkId change
    provider.on("networkChanged", (networkId) => {
        fetchAccountData();
    });

    await refreshAccountData();
}

/**
 * Disconnect wallet button pressed.
 */
async function onDisconnect() {
    window.location = window.location;
}

async function onCreateToken() {
    // disable the button while waiting for result
    const createTokenButton = document.getElementById("btn-create-token");
    createTokenButton.setAttribute("disabled", "disabled");

    // clear
    const status = document.getElementById("status");
    status.textContent =
        "processing... please approve the transaction from the wallet";

    try {
        const wallet = new ethers.providers.Web3Provider(provider);
        const signer = wallet.getSigner();
        const contract = new ethers.Contract(contractAddress, contractAbi, signer);

        const recipientAddress = document.getElementById("token-recipient").value;
        const ticketValue = document.getElementById("ticket").value;

        const ticketByte32String = ethers.utils.formatBytes32String(ticketValue);
        const tx = await contract.createToken(recipientAddress, ticketByte32String);

        console.log("tx", tx);
        // show the transaction hash
        status.textContent = "transaction hash: " + tx.hash;
    } catch (err) {
        console.log(err);
        status.textContent = err;
    }

    createTokenButton.removeAttribute("disabled");
}

/**
 * Main entry point.
 */
window.addEventListener("load", async () => {
    init();
    document.querySelector("#btn-connect").addEventListener("click", onConnect);
    document
        .querySelector("#btn-disconnect")
        .addEventListener("click", onDisconnect);

    document
        .querySelector("#btn-create-token")
        .addEventListener("click", onCreateToken);
});

@EvilJordan
Copy link
Author

Trawling through the logger package's makError method, and it seems like this output format is completely intended. If that's the case, how is one expected to parse it?

@ricmoo
Copy link
Member

ricmoo commented Jan 22, 2021

@EvilJordan Not sure what you mean by the makeError logic; here it creates the error, with the message and all the properties included in the string:

https://github.com/ethers-io/ethers.js/blob/master/packages/logger/src.ts/index.ts#L205

@EvilJordan
Copy link
Author

You're right. Not sure that's coming into play here. In my provided example, the thrown error is being caught, then output to the console and screen. In both cases, it's just a string with no ability to reach error.code or error.message:

try {
        const wallet = new ethers.providers.Web3Provider(provider);
        const signer = wallet.getSigner();
        const contract = new ethers.Contract(contractAddress, contractAbi, signer);

        const recipientAddress = document.getElementById("token-recipient").value;
        const ticketValue = document.getElementById("ticket").value;

        const ticketByte32String = ethers.utils.formatBytes32String(ticketValue);
        const tx = await contract.createToken(recipientAddress, ticketByte32String);

        console.log("tx", tx);
        // show the transaction hash
        status.textContent = "transaction hash: " + tx.hash;
    } catch (err) {
        console.log(err); // <------- this is a string
        status.textContent = err;
    }

@DanontheMoon69
Copy link

Hey all - I'm getting this error and it's pointing to the 2nd line of this code.

const sdk = new ThirdwebSDK(
new ethers.Wallet(
process.env.NEXT_PUBLIC_METAMASK_KEY,
ethers.getDefaultProvider(
""
)
)
);

@ricmoo
Copy link
Member

ricmoo commented Mar 3, 2022

Are you sure that environment variable is defined? Also, You shouldn’t be passing in the empty string as a network; try either nothing, a network name or chain ID.

@DanontheMoon69
Copy link

DanontheMoon69 commented Mar 3, 2022 via email

@ricmoo
Copy link
Member

ricmoo commented Mar 3, 2022

I’m not sure what ThirdwebSDK is; the error could be coming from that, perhaps?

Also, can you console.log the environment variable, to make sure the env file is being picked up. It’s a good idea to dump everything to the console to double check things are working.

@DanontheMoon69
Copy link

DanontheMoon69 commented Mar 3, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information.
Projects
None yet
Development

No branches or pull requests

4 participants