It's fairly easy to get ethers to get a metamask signature of a given piece of text, but how to confirm the signer onchain seems to be a bigger challenge than I thought. On a client side app with metamask installed I can call:
let provider = new ethers.providers.Web3Provider(window.ethereum);
let signer = provider.getSigner();
let signature = await signer.signMessage("Please confirm this: 12.315")
Metamask will pop the signing verification dialog with the message , and return a signature after the user approves.
You can pretty easily validate the address that produced the signature off-chain:
let signerAddress = ethers.utils.verifyMessage(ethers.utils.verifyMessage('Please confirm this: 12.315', sig);
But if you want to send the signature to a smart contract, and confirm the address that produced the signature, its not so easy.
The ECDSA package from OpenZeppelin has a recover() function but it only takes a hash as the "message', not a string. You can hash the string, but that doesn't return the right account:
function getSigner(string memory message, bytes sig) public view returns(address) {
bytes32 messageHash = keccak256(abi.encodePacked(message);
address signer = ECDSA.recover(messageHash, sig);
return signer;
}
Nor does
address signer = ECDSA.recover(ECDSA.toEthSignedMessageHash(message, signature);
So how can validate you validate on-chain that the signature of a message produced off-chain by ethers is in fact the expected signer address?
After poking around for a while in various posts and videos, I was able to figure out that metamask adds a prefix to the string being signed before the message signature is generated. The prefix added to the message is `\x19Ethereum Signed Message:\n$' + the length of the message.
So the message used by metamask to produce the signature is actually:
let signedMessage = `\x19Ethereum Signed Message:\n${signedMessage.length}${message}`;
So if your on chain function looks like:
function getSigner(string memory message, bytes memory sig) public view returns(address) {
bytes32 messageHash = keccak256(abi.encodePacked(message));
address signer = ECDSA.recover(messageHash, sig);
return signer;
}
then you should call it like this:
let prefixedMessage = `\x19Ethereum Signed Message:\n${message.length}${message}`
address = await mycontract.getSigner(prefixedMessage, signature);
Perhaps this will be helpful to somebody
Related
I'm trying to read total supply from this smart contract, I have checked the ABI and the read does not require arguments. Why am I getting this error?
Imported the ABI from Etherscan with a valid API key
Connected Infura node with valid api.
Please help. I'm new!
Error:
Error: call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] (method="TOTAL_SUPPLY()", data="0x", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.7.0)
reason: null,
code: 'CALL_EXCEPTION',
method: 'TOTAL_SUPPLY()',
data: '0x',
errorArgs: null,
errorName: null,
errorSignature: null,
address: '0xFc7a5BD22dFc48565D6f04698E566Dd0C71d3155',
args: [],
transaction: {
data: '0x902d55a5',
to: '0xFc7a5BD22dFc48565D6f04698E566Dd0C71d3155',
from: '0xa9519a76981489Cf44a6b1023C53EA674B9365fB'
}
}
CODE:
const ethers = require("ethers");
async function main() {
// make an API call to the ABIs endpoint
const response = await fetch('https://api.etherscan.io/api?module=contract&action=getabi&address=0x31Cd7378715174e2c5Bd7EDF9984c9bC2A9209Eb&apikey=xxx');
const data = await response.json();
let abi = data.result;
/*console.log(abi);*/
// creating a new Provider, and passing in our node URL
const node = "https://mainnet.infura.io/v3/xxx";
const provider = new ethers.providers.JsonRpcProvider(node);
// initiating a new Wallet, passing in our private key to sign transactions
let privatekey = "xxx";
let wallet = new ethers.Wallet(privatekey, provider);
// print the wallet address
console.log("Using wallet address " + wallet.address);
// specifying the deployed contract address
let contractaddress = "0xFc7a5BD22dFc48565D6f04698E566Dd0C71d3155";
// initiating a new Contract
let contract = new ethers.Contract(contractaddress, abi, wallet);
console.log("Using contract address " + contract.address);
// calling the "retrieve" function to read the stored value
let supply = await contract.TOTAL_SUPPLY();
console.log("Value stored in contract is " + supply.toString());
You're retrieving ABI of address 0x31Cd7378715174e2c5Bd7EDF9984c9bC2A9209Eb on the Ethereum mainnet.
Your ethers provider correctly connects to the Ethereum mainnet.
But then it's trying to interact (call function TOTAL_SUPPLY()) with contract on address 0xFc7a5BD22dFc48565D6f04698E566Dd0C71d3155.
Since there's no contract on the 0xFc... address on the Ethereum mainnet, the call fails.
Have u tried web3client.app.
You can test any abi methods across 500+ chains in any smart contracts.
Identify which network your contract resides and do test it out.
It has code generate feature that lets you to easily get a bug free high quality code
I have tried changing the values from 0.025 ether to 1 ether then also its showing the same error.
Also, I have tried with the rational number like 1/8 still not working.
LOOKED into some answers but they didn't resolve the error.
I have the same code in other project and it's working over there.
Error Which I received
Uncaught (in promise) Error: invalid BigNumber value (argument="value", value={"value":"25000000000000000"}, code=INVALID_ARGUMENT, version=bignumber/5.5.0)
Could not get the stack frames of error: TypeError: Cannot read properties of null (reading 'length')
Image of the Error
Here is my Code for the Listing Price
uint256 listingPrice = 0.025 ether ; // Here ether is denoting the MATIC
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
Here is the Code for fetching the value in UI
async function putItem(url) {
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
const { royalty } = formInput;
//NFT Contract
let contract = new ethers.Contract(nftAddress, NFT.abi, signer);
//minting the certificate
let transaction = await contract.createToken(url);
//waiting for the minting transaction to finish
let tx = await transaction.wait();
let event = tx.events[0];
let value = event.args[2];
let tokenId = value.toNumber(); //Token Id Of the NFT
console.log(tokenId)
//NFT Market Contract
contract = new ethers.Contract(nftMarketAddress, NFTMarket.abi, signer);
//fetching listing price from the contract
let listingPrice = await contract.getListingPrice();
listingPrice = listingPrice.toString();
//listing the certificate.
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
{ value: (listingPrice) },
royalty,
index
);
//waiting for the transaction to complete
await transaction.wait();
console.log("completed")
//navigate back to home page
}
If any more detail required, please comment.
It looks like you're trying to send an object as the parameter { value: (listingPrice) }
This should probably be written as either an array of parameters or just the listingPrice
//listing the certificate.
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
listingPrice,
royalty,
index
);
Source: https://docs.ethers.io/v5/api/contract/contract/#contract-functionsSend
For my case I needed to add .toString() to the BigNumber before passing it to the contract.
async changePayoutAmount_ether(amount_ether) {
let amount_wei = new BigNumber(amount_ether).shiftedBy(18).toString()
await this.state.pcrContract.methods.setPayoutAmount(amount_wei).send({from: this.state.account}).then(console.log)
}
Also for anyone troubleshooting, note that there are at least two BigNumber libraries: I believe this error comes from this one but be careful if you're reading docs from the ethers.js one because the syntax for the constructors is different.
I got this error as well. In my case, I forgot to update the ABI.
You can use the following module:
import converter form "ethereum-uint-converter"
And if you want to know more detail, click here.
I think this is the issue:
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
{ value: (listingPrice) },
royalty,
index
);
{ value: (listingPrice) }, is supposed to be object that represents the amount of money you are sending along side the transaction and it should be the last parameter in the function. Because looks like you are creating an NFT market item and you have to pay the price of listing.
Since you are creating an nft, looks like you have a const { royalty } = formInput. I believe you wanted to send the nft price instead of { value: (listingPrice) }. so your transaction should be like this
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
// I assume you want to send the nft price here from form
priceFromForm,
royalty,
index,
// this should be the last parameter
{ value: (listingPrice) }
);
I `ve got this function at my .sol file
function enter() public payable{
require(msg.value > .01 ether);
players.push(msg.sender);
}
But when i`m trying to send some eth on it from deployed file it says 'invalid address or ENS name'.
const lotteryContract = new ethers.Contract(
addr,
LotteryArtifact.abi,
acc2
)
const tx ={
from:acc2,
value:ethers.utils.parseEther('2'),
}
await lotteryContract.enter(tx)
Seems like it`s syntactically error in this way of calling it.Also i tried to send it this way
const tx ={
to:lotteryContract.enter(),
value:ethers.utils.parseEther('2'),
}
await acc1.sendTransaction(tx)
Testing this 'enter' function in remix ide logging this
enter image description here
According to the screenshot can anyone explain does 0xf8e81D47203A594245E36C48e151709F0C19fBe8 means the 'enter' function address?
I have a custom token I've sent to my DApp, and I am trying to send some amount of 'rewards' to my users for posting things on the website. However, it seems like nothing is being sent from my backend server/DApp to the users. I'm wondering what I might be doing wrong here. Code below:
server.js
(Basic idea - call approve then transferFrom)
app.post('/send-TOKEN-to-addr', async (req, res) => {
const post_addr = req.body.addr; // the client sends in user's addr
var transfer_amt = 5000; // token reward to user
try {
console.log('send-TOKEN-to-addr gets called for :: '+String(post_addr));
TOKEN_Contract.methods.approve(DAppAddr, regular_post_transfer_amt).call((err, result) => {
console.log('approve:: '+String(result));
//return res.send(result);
});
TOKEN_Contract.methods.transferFrom(DAppAddr, post_addr, transfer_amt).call((err, result) => {
//console.log(result);
return res.send(result);
});
} catch (e) { throw e; }
});
On the backend I get:
send-TOKEN-to-addr gets called for :: 0xb65ec054bd7f633efd8bd0b59531de464046a7c0
approve:: true
But on the frontend I get no response. As well, when I check the balances of TOKEN for the DApp and the addr, nothing changes, so I think nothing happens here.
I am seeking advice on getting my DApp to send the tokens it has to other addresses. I confirmed that the DApp has the tokens already, I just can't seem to send on behalf of it within my node.js framework.
Edit 1
I have some basic functionality already working with my token (within the DApp), such as the below call:
app.post('/balanceOf-TOKEN-by-addr', async (req, res) => {
//console.log('balanceOf-TOKEN-by-addr - server');
const post_addr = req.body.addr;
//console.log(post_addr);
try {
TOKEN_Contract.methods.balanceOf(post_addr).call((err, result) => {
//console.log(result);
return res.send(result);
});
} catch (e) { throw e; }
});
Edit 2
Adding code for how I initialize my DApp - I will need the private keys to call send() methods from it? Because my DApp has a bunch of the TOKENs that I want to send out.
const WEB3_PROVIDER = "HTTP://127.0.0.1:7545"
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
console.log("web3 already initialized.");
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider(WEB3_PROVIDER));
console.log("New web3 object initialized.");
}
const DAppABIFile = require('./assets/abis/DAppABI');
const DAppABI = DAppABIFile.DAppABI;
const DAppAddr = "0x5C7704a050286D742............"; // public key
const DAppContract = new web3.eth.Contract(DAppABI, DAppAddr);
There's a difference between a call (read-only) and a transaction (read-write).
Your snippet only calls the contract but doesn't send transactions. So the contract is not able to write state changes from just the calls.
TOKEN_Contract.methods.<method>.call((err, result) => {});
Web3 uses the .send() function (docs) to send a transaction.
TOKEN_Contract.methods.<method>.send({
from: '0x<sender_address>'
}, (err, result) => {});
Note that the transaction needs to be signed with the private key of the from address. This can be done in 2 ways:
The node provider (most likely passed in the new Web3(<provider_url>) constructor) knows the private key to the sender address and has its account unlocked - this is usually done only on local networks used for development.
You have passed the sender private key to your web3 instance using the wallet.add() function (docs). Then it gets signed in your JS app, and the payload sent to the node in the background contains only the signed transaction (not the private key).
You can also set this address as the defaultAccount (docs) so that you don't have to keep passing it to the .send() function.
Well first check if the wallet that you are using to call the functions is the owner of the tokens and not the dapp, if the dapp is the owner i would recommend you that the address you are using to call the contract have an owner permission and add the dapp contract a function to send the token to some address, if the address that you are using is the owner of the tokens just call the ´TOKEN_CONTRACT.methods.transfer(post_addr,transfer_amt)´
now, just as an explanation, the reason because that endpoint is not sending the tokens is this, in that operation are 4 address used, the user address, the contract address, the token address and the address you are using to send the transactions from the backend, when you call TOKEN_Contract.methods.approve(DAppAddr, regular_post_transfer_amt) you are approving the contract address to move the tokens that owns the address you use in your backend not viceversa, for that the contract would have made that call and pass as a parameter the backend address, so when you call TOKEN_Contract.methods.transferFrom(DAppAddr, post_addr, transfer_amt) you are trying to move the tokens of the contract approved to the backend address and send it to the user address, but the amount of this approved tokens is 0, because the thing i explained before
I am currently sending transactions with this code:
const provider = ethers.getDefaultProvider("ropsten", {
infura:
"https://ropsten.infura.io/v3/ee11be9f1d1c43199618db4a7b22aa79",
});
const signer = new ethers.Wallet(PRIVATE_KEY);
const account = signer.connect(provider);
const uniswap = new ethers.Contract(
ropstenUniswapContract,
[
"function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)",
],
account
);
const gasCost = ethers.BigNumber.from((+gasPrice/10) * Math.pow(10, 9));
console.log('Computed gas cost ->', gasCost);
const tx = await uniswap.swapExactETHForTokens(
amountOutMin,
path,
to,
deadline,
{ value, gasPrice: gasCost }
);
// Transaction Hash and Block
setTransactionHash(tx.hash);
const receipt = await tx.wait();
console.log(receipt);
My question is:
How can I make MetaMask sign the transaction on my behalf instead of supplying my private key?
Transaction signing is abstracted away with web3.js or Ethers.js. You can directly connect your Ethers.js to MetaMask provider (windows.ethereum) in in-page JavaScript code.
An example here.
Your (browser-facing) app can communicate with the MetaMask browser extension using the Ethereum Provider API.
Speficically the eth_sendTransaction method passing it the data field (specifying which function in your contract you want to execute, as well as the passed argument values), along with other params such as the sender address.
If you're new to MetaMask, just a quick note: To get the user's list of addresses, you need to call the eth_requestAccounts method.