Post
Share your knowledge.
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
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
1The 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.
Do you know the answer?
Please log in and share it.
Solidity is an object-oriented, high-level language for implementing smart contracts. It is a curly-bracket language designed to target the Ethereum Virtual Machine (EVM).
- My ERC721 contract successfully deploys, but I can't verify the contract's source code with hardhat21
- Solidity and ethers.js Compute Different Addresses from the Same Signature21
- can't understand what are the locations(uint256)22
- How to reverse keccak256 in solidity22
- Clarification on Gas Refunds and Comparison Between "require" and "revert" in Smart Contracts21