Solidity.

Post

Share your knowledge.

Christian O'Connor.
May 26, 2023
Expert Q&A

Why does Solidity produce a different address from a private key than ether.js does?

I have this function in my solidity contract:

function requestRandomNFT(
        address minter,
        uint256 nonce,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint256 randomUint256
    ) external payable {
        require(block.timestamp <= deadline, "Request has expired");
        require(nonce == _nonces[minter].current(), "Nonce does not match expected value");

        bytes32 structHash = keccak256(
            abi.encode(
                _REQUEST_RANDOM_NFT_TYPEHASH,
                keccak256(
                    abi.encode(
                        minter,
                        nonce,
                        deadline
                    )
                )
            )
        );

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        emit Verified(signer, nonce, minter, r, s, v, signer);
        console.log("below this is signer");
        console.log(signer);
        console.log("below this is aithorizedAccount");
        console.log(authorizedAccount);
        require(signer == authorizedAccount, ERR_INVALID_SIGNER);

        bytes32 requestId = keccak256(abi.encodePacked(signer, nonce));

        _nonces[minter].increment();

        emit RequestedRandom(requestId);

        // Auto mint NFT
        mintNFT(minter, requestId, randomUint256);
    }

I used import "hardhat/console.sol"; to write logs to the console.

This is my minting code:

import { ethers } from 'ethers';
import { useAccount } from "wagmi";
import { useContractWrite, useWaitForTransaction, usePrepareContractWrite } from 'wagmi';
import RandomReachDebug5Local from "../app/contracts/RandomReachDebug5Local.json";
import { useState, useEffect, useCallback } from 'react';
import { parseGwei } from 'viem'

// Function to get a signature for the random NFT request
async function signRandomNFTRequest(account, nonce, deadline) {
    const privateKey = "0x REST OF MY PRIVATE KEY";
    const wallet = new ethers.Wallet(privateKey);

    const domain = {
        name: "RandomReachDebug5Local",
        version: "1",
        chainId: 31337, // chainId for local node (Ganache or Hardhat)
        verifyingContract: "0x8464135c8F25Da09e49BC8782676a84730C318bC", // replace with your contract address
    };
    const types = {
        RequestRandomNFT: [
            { name: 'minter', type: 'address' },
            { name: 'nonce', type: 'uint256' },
            { name: 'deadline', type: 'uint256' },
        ],
    };
    const value = {
        minter: account,
        nonce: nonce,
        deadline: deadline,
    };

    const signature = await wallet._signTypedData(domain, types, value);
    const sig = ethers.utils.splitSignature(signature);
    console.log("Signature: ", sig);
    return sig;
}

export function useMintNFT() {
    const { address: connectedAddrs, provider } = useAccount();
    const [contract, setContract] = useState(null);
    const [sigData, setSigData] = useState(null);
    const [args, setArgs] = useState([]);
    const [fetchNonceError, setFetchNonceError] = useState(null);
    const randomReachDebug5Address = "0x8464135c8F25Da09e49BC8782676a84730C318bC"; // replace with your contract address

    // Instantiate the contract
    const initializeContract = useCallback(async () => {
        if (window.ethereum && randomReachDebug5Address) {
            console.log("initContract is running");

            // If you use MetaMask
            // const provider = new ethers.providers.Web3Provider(window.ethereum);
            // If you use a local node (e.g., Ganache)
            const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545');
            const balance = await provider.getBalance(connectedAddrs);
            console.log("Balance: ", ethers.utils.formatEther(balance));

            const contractInstance = new ethers.Contract(randomReachDebug5Address, RandomReachDebug5Local.abi, provider.getSigner());
            console.log(`contract is set here: ${contractInstance}`);
            if (!sigData) {
                setContract(contractInstance);
                try {
                    const nonce = await contractInstance.nonces(connectedAddrs);
                    console.log("Nonce fetched: ", nonce);
                    const deadline = Math.floor(Date.now() / 1000) + 120; // 2 minutes from now
                    const sig = await signRandomNFTRequest(connectedAddrs, nonce, deadline);
                    setSigData(sig);
                    const randomUint256 = ethers.utils.hexlify(ethers.BigNumber.from(ethers.utils.randomBytes(32))); // generating random uint256

                    const argsInUseEffect = [connectedAddrs, nonce.toString(), deadline, sig.v, sig.r, sig.s, randomUint256];
                    console.log("Args in use effect: ", argsInUseEffect);
                    setArgs(argsInUseEffect);
                } catch (error) {
                    console.error("Error fetching nonce: ", error);
                    setFetchNonceError(error);
                }
            }
            console.log(`sig is here: ${sigData}`);
            console.log(`args is here: ${args}`);
        }
    }, [args, connectedAddrs, randomReachDebug5Address, setContract]);

    useEffect(() => {
        initializeContract();
    }, [initializeContract]);

    console.log(`args outside of use effect ${args}`);

    const { config, prepareError, isPrepareError } = usePrepareContractWrite({
        address: randomReachDebug5Address,
        abi: RandomReachDebug5Local.abi,
        functionName: 'requestRandomNFT',
        args: args,
        value: '10000000000000000', // This is 0.01 Ether represented in Wei
        gas: 300000n, // Set your desired gas limit here
        gasPrice: parseGwei('50'),
    });
    console.log(`this is config: ${config}`);

    const { data, error, isError, write } = useContractWrite(config || {});
    console.log(`this is the error ${error} and this is the error bool ${isError}`);

    const { isLoading, isSuccess } = useWaitForTransaction({hash: data?.hash});

    const mintNFT = () => {
        if (sigData) {
            console.log('Minting...');
            write?.();
        } else {
            console.error('sigData is not defined.');
        }
    };

    return {
        mintNFT,
        isLoading,
        isSuccess,
        isError,
        error,
        data,
        write,
        isPrepareError,
        prepareError,
        fetchNonceError
    };
}

So here's the weird thing... When I ran the minting code within a next.js app, I got this in my console for the hardhat node:

console.log:
    below this is signer
    0x2d0b2215d4e00807a6982877a9762ef41541ecef
    below this is aithorizedAccount
    0x1ab26702a8068a247bd33a9555dfef791d2bd68d

But the private key I used gives me the address 0x1AB26702A8068a247BD33a9555dfEf791d2BD68D when I test it in javascript with ethers.js, but solidity gives me 0x2d0b2215d4e00807a6982877a9762ef41541ecef ???

I used this script with const privateKey = "0x REST OF MY PRIVATE KEY":

const ethers = require('ethers');

function getAddressFromPrivateKey(privateKey) {
    const wallet = new ethers.Wallet(privateKey);
    return wallet.address;
}

const privateKey = "0x REST OF MY PRIVATE KEY";
console.log(getAddressFromPrivateKey(privateKey)); // This should log your expected address

This code gives me 0x1AB26702A8068a247BD33a9555dfEf791d2BD68D, but my solidity code gives me 0x2d0b2215d4e00807a6982877a9762ef41541ecef. That doesn't make any sense. Why is this happening?

  • Smart Contract
  • Solidity
1
1
Share
Comments
.
Sergey Ilin.
Jun 2 2023, 04:50

By looking at the code I don't see any issues. Can you share the value for `_REQUEST_RANDOM_NFT_TYPEHASH` ?

Christian O'Connor.
Jun 2 2023, 05:32

I was actually able to figure out the problem. I just answered it. Thanks for following up though!

Answers

1
Christian O'Connor.
Jun 2 2023, 05:28

The problem was 1 little naming inconsistency. It took me a really a long time to figure it out.

This is types in my minting code:

const types = {
    Request: [
        { name: 'minter', type: 'address' },
        { name: 'nonce', type: 'uint256' },
        { name: 'deadline', type: 'uint256' },
    ],
};

I didn't display _REQUEST_RANDOM_NFT_TYPEHASH in the smart contract, but it looks like this:

bytes32 internal constant REQUEST_TYPEHASH = keccak256(bytes("RequestRandom(address minter,uint256 nonce,uint256 deadline)"));

Notice in _REQUEST_RANDOM_NFT_TYPEHASH, the first word is RequestRandom, and in types (within the minting code), the first word is Request. That word needs to be the same. I made them both Request and the addresses matched.

0
Best Answer
Comments
.

Do you know the answer?

Please log in and share it.

We use cookies to ensure you get the best experience on our website.
More info