Why does Solidity produce a different address from a private key than ether.js does?
- undefined
- undefined Solidity
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?
- By looking at the code I don't see any issues. Can you share the value for `_REQUEST_RANDOM_NFT_TYPEHASH`
- I was actually able to figure out the problem. I just answered it. Thanks for following up though
Answers 1
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.