How to sign an ethereum transaction with metamask - ethereum

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.

Related

how to confirm ethers off-chain signatures on-chain

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

Reading smart contract function - what am I doing wrong?

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

Check balance of ERC20 token in Hardhat using Ethers.js

I'm writing unit tests using Hardhat's mainnet fork, and for a test I want to check the owner account's initial balance of an ERC20 token, in particular DAI. Here's my test so far:
const { ethers } = require("hardhat");
describe("Simple Test", function () {
it("Check balance of DAI", async function () {
const provider = ethers.provider;
const [owner] = await ethers.getSigners();
// Want to get initial DAI balance here so it can be compared later
});
});
What's a simple way to do this?
Found what I was looking for. First thing I did was save the DAI contract address at the top:
const DAI_ADDRESS = "0x6b175474e89094c44da98b954eedeac495271d0f";
Then I had to use the ERC20 token ABI, which I placed in the same folder as my test:
const ERC20ABI = require('./ERC20.json');
Then to get the owner account's DAI balance I just called balanceOf() from the DAI contract:
const DAI = new ethers.Contract(DAI_ADDRESS, ERC20ABI, provider);
DAIBalance = await DAI.balanceOf(owner.address);
This should work for any ERC20 token as well.

Transfering ERC20 Tokens from DApp/Smart Contract to user on a Node.js server

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

Using local private key with Web3.js

How can I interact with smart contracts and send transactions with Web3.js by having a local private key? The private key is either hardcoded or comes from an environment (.env) file?
This is needed for Node.js and server-side interaction or batch jobs with Ethereum/Polygon/Binance Smart Chain smart contracts.
You may encounter e.g. the error
Error: The method eth_sendTransaction does not exist/is not available
Ethereum node providers like Infura, QuikNode and others require you to sign outgoing transactions locally before you broadcast them through their node.
Web3.js does not have this function built-in. You need to use #truffle/hdwallet-provider package as a middleware for your Ethereum provider.
Example in TypeScript:
const Web3 = require('web3');
const HDWalletProvider = require("#truffle/hdwallet-provider");
import { abi } from "../../build/contracts/AnythingTruffleCompiled.json";
//
// Project secrets are hardcoded here
// - do not do this in real life
//
// No 0x prefix
const myPrivateKeyHex = "123123123";
const infuraProjectId = "123123123";
const provider = new Web3.providers.HttpProvider(`https://mainnet.infura.io/v3/${infuraProjectId}`);
// Create web3.js middleware that signs transactions locally
const localKeyProvider = new HDWalletProvider({
privateKeys: [myPrivateKeyHex],
providerOrUrl: provider,
});
const web3 = new Web3(localKeyProvider);
const myAccount = web3.eth.accounts.privateKeyToAccount(myPrivateKeyHex);
// Interact with existing, already deployed, smart contract on Ethereum mainnet
const address = '0x123123123123123123';
const myContract = new web3.eth.Contract(abi as any, address);
// Some example calls how to read data from the smart contract
const currentDuration = await myContract.methods.stakingTime().call();
const currentAmount = await myContract.methods.stakingAmount().call();
console.log('Transaction signer account is', myAccount.address, ', smart contract is', address);
console.log('Starting transaction now');
// Approve this balance to be used for the token swap
const receipt = await myContract.methods.myMethod(1, 2).send({ from: myAccount.address });
console.log('TX receipt', receipt);
You need to also avoid to commit your private key to any Github repository. A dotenv package is a low entry solution for secrets management.
You can achieve what you want by using ethers.js instead of web3 with no other package needed.
First import the library:
Node.js:
npm install --save ethers
const { ethers } = require("ethers");
Web browser:
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"
type="application/javascript"></script>
Define the provider. One way is using the provider URL like this:
const provider = new ethers.providers.JsonRpcProvider(rpcProvider);
Then, in order to interact with the contract without asking for authorization, we will create a wallet using the private key and the provider like this:
const signer = new ethers.Wallet(privateKey,provider)
Now, you can create the contract with the address, ABI, and the signer we created in the previous step:
const contract = new ethers.Contract(contractAddress,ABI, signer);
Now, you can interact with the contract directly. For example, getting the balance of a token:
const tokenBalance = await nftContractReadonly.balanceOf(signer.getAddress(),tokenId);
Don't forget to store the private key in a safe place and never hardcode it in a web page.
Further reading: Provider Signer
There is a better and simple way to sign and execute the smart contract function. Here your function is addBonus.
First of all we'll create the smart contract instance:
const createInstance = () => {
const bscProvider = new Web3(
new Web3.providers.HttpProvider(config.get('bscRpcURL')),
);
const web3BSC = new Web3(bscProvider);
const transactionContractInstance = new web3BSC.eth.Contract(
transactionSmartContractABI,
transactionSmartContractAddress,
);
return { web3BSC, transactionContractInstance };
};
Now we'll create a new function to sign and execute out addBonus Function
const updateSmartContract = async (//parameters you need) => {
try {
const contractInstance = createInstance();
// need to calculate gas fees for the addBonus
const gasFees =
await contractInstance.transactionContractInstance.methods
.addBonus(
// all the parameters
)
.estimateGas({ from: publicAddress_of_your_desired_wallet });
const tx = {
// this is the address responsible for this transaction
from: chainpalsPlatformAddress,
// target address, this could be a smart contract address
to: transactionSmartContractAddress,
// gas fees for the transaction
gas: gasFees,
// this encodes the ABI of the method and the arguments
data: await contractInstance.transactionContractInstance.methods
.addBonus(
// all the parameters
)
.encodeABI(),
};
// sign the transaction with a private key. It'll return messageHash, v, r, s, rawTransaction, transactionHash
const signPromise =
await contractInstance.web3BSC.eth.accounts.signTransaction(
tx,
config.get('WALLET_PRIVATE_KEY'),
);
// the rawTransaction here is already serialized so you don't need to serialize it again
// Send the signed txn
const sendTxn =
await contractInstance.web3BSC.eth.sendSignedTransaction(
signPromise.rawTransaction,
);
return Promise.resolve(sendTxn);
} catch(error) {
throw error;
}
}