Using local private key with Web3.js - ethereum

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;
}
}

Related

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

Unable to create contract clone on Hardhat. "Error: Transaction reverted: function call to a non-contract account"

I'm trying to create a clone using OpenZeppelin Clones library. However, it seems that hardhat is not able to recognize the created clone contracts address.
The same code works on Remix, so does this have something to with Hardhat? NOTE: I have tried using Ganache as well, however it reverts with the same error.
Here is my factory contract:
contract WhoopyFactory {
address immutable implementationContract;
address[] public allClones;
event NewClone(address indexed _instance);
mapping(address => address) public whoopyList;
constructor() {
implementationContract = address (new Whoopy());
}
function createClone(address _whoopyCreator) payable external returns(address) { address clone = Clones.clone(implementationContract); Whoopy(clone).initialize(_whoopyCreator);
emit NewClone(clone);
return clone;
}
And here is the test I am running:
describe("Whoopy + WhoopyFactory", function () {
it("Initialises contract correctly", async function () {
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:7545")
const deployer = provider.getSigner(0);
const player = provider.getSigner(1);
Whoopy = await ethers.getContractFactory("Whoopy")
whoopy = await Whoopy.deploy()
await whoopy.deployed()
WhoopyFactory = await ethers.getContractFactory("WhoopyFactory")
wf = await WhoopyFactory.deploy()
await wf.deployed()
wf.connect(player)
const tx = await wf.createClone("0x70997970C51812dc3A010C7d01b50e0d17dc79C8")
console.log(tx)
const txReceipt = await tx.wait(1)
console.log(txReceipt)
This is the error which reverts:
Error: Transaction reverted: function call to a non-contract account
at Whoopy.initialize (contracts/Whoopy.sol:117)
at <UnrecognizedContract>.<unknown> (0x9f1ac54bef0dd2f6f3462ea0fa94fc62300d3a8e)
As I said before, this code works correctly on Remix. Hope someone can point me in the right direction. Thanks in advance!
From what I understood, you are deploying a smart contract on Ganache, you can use hardhat's own local chain, by doing yarn chain, or npx hardhat node, and change the port to 8545.
You can make it more simpler by replacing the following
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:7545")
const deployer = provider.getSigner(0);
const player = provider.getSigner(1);
with
const accounts = await hre.ethers.getSigners();
const deployer = accounts[0];
const player = accounts[1];
and while deploying use npx hardhat test --network localhost

sending a transaction requires a signer

I have an NFT Contract and I need to mint NFTs. Also I have a second contract
for handling buying and selling NFTs into this contract.
I am using the ethers library.
this is my code for config:
static async Initial(): Promise<any> {
let provider = new ethers.providers.JsonRpcProvider(config.contractConfig.url);
const signer = provider.getSigner();
this.tokenContract = new ethers.Contract(config.contractConfig.nftAddress, NFT.abi, provider);
this.dNFT = new ethers.Contract(config.contractConfig.dortajNftAddress, DNFT.abi, signer);
}
config address:
export default {
nftAddress: '0x444F15B115ED9663DAE46786a34AA3F6E8c0B57D',
dortajNftAddress: '0x8e4bE2a3BD1169596c38952D8e837b20D419Bcd1',
url : 'HTTP://127.0.0.1:7545'
}
now i need to use this function for mint nft :
let transaction = await this.tokenContract.mintToken();
const tx = await transaction.wait();
but it show me this error:
Error: sending a transaction requires a signer (operation="sendTransaction", code=UNSUPPORTED_OPERATION, version=contracts/5.5.0)
How can I solve this problem?
In this line
this.tokenContract = new ethers.Contract(config.contractConfig.nftAddress, NFT.abi, provider);
try to pass signer instead of provider.

Solidity function returning manager address of 0x00000 after contract deployment in Remix IDE

I have recently ran into a bit of an issue with a solidity contract I created and am trying to deploy and interact with inside of the Remix IDE. The function I have written to return the address of the contract manager is as so
function getManager() public view returns(address){
return manager;
}
The way the contract manager is set is as so
function Contest() public {
manager = msg.sender;
}
I have created two JavaScript compile and deploy scripts which I run inside of my terminal. The deploy script is written as this
const HDWalletProvider = require('truffle-hdwallet-provider');
const Web3 = require('web3');
const {interface, bytecode} = require('./compile.js');
const provider = new HDWalletProvider(
'MetaMask Mnemonic',
'Infura endpoint key'
);
const web3 = new Web3(provider);
const deploy = async () => {
const accounts = await web3.eth.getAccounts();
console.log('deploying from ', accounts[0]);
const ABI = interface;
const deployedContract = await new web3.eth.Contract(JSON.parse(ABI))
.deploy({data: bytecode})
.send({
gas: '1000000',
from: accounts[0]
});
console.log('contract deployed to', deployedContract.options.address);
}
deploy();
Once my contract is deployed I receive the deployed address (something like 0xbc0370FbC085FAf053b418e6ff62F26ED2C7Dee1) and then I go over to the Remix IDE where I put in the address and use the Injected web3 option. However when I call the "getManager" function it returns an address of 0x0000000000000000000000000000000000000000 and I can't seem to get it to return the address of the manager. This method was working once before and I cannot seem to figure out why it's not working any longer.

Call contract methods with web3 from newly created account

I need to call methods from my contract in Ethereum without using MetaMask. I use Infura API and try to call my methods from account, recently created with web3.eth.create() method. This method returns object like this:
{
address: "0xb8CE9ab6943e0eCED004cG5834Hfn7d",
privateKey: "0x348ce564d427a3311b6536bbcff9390d69395b06ed6",
signTransaction: function(tx){...},
sign: function(data){...},
encrypt: function(password){...}
}
I also using infura provider:
const web3 = new Web3(new Web3.providers.HttpProvider(
"https://rinkeby.infura.io/5555666777888"
))
So, when I try to write smth like that:
contract.methods.contribute().send({
from: '0xb8CE9ab6943e0eCED004cG5834Hfn7d', // here I paste recently created address
value: web3.utils.toWei("0.5", "ether")
});
I have this error:
Error: No "from" address specified in neither the given options, nor
the default options.
How it could be no from address if I write it in from option??
P.S. With Metamask my application works fine. But when I logout from MetaMask and try to create new account and use it, I have that issue.
In fact, we can't just send transactions from newly created address. We must sign this transaction with our private key. For example, we can use ethereumjs-tx module for NodeJS.
const Web3 = require('web3')
const Tx = require('ethereumjs-tx')
let web3 = new Web3(
new Web3.providers.HttpProvider(
"https://ropsten.infura.io/---your api key-----"
)
)
const account = '0x46fC1600b1869b3b4F9097185...'; //Your account address
const privateKey = Buffer.from('6e4702be2aa6b2c96ca22df40a004c2c944...', 'hex');
const contractAddress = '0x2b622616e3f338266a4becb32...'; // Deployed manually
const abi = [Your ABI from contract]
const contract = new web3.eth.Contract(abi, contractAddress, {
from: account,
gasLimit: 3000000,
});
const contractFunction = contract.methods.createCampaign(0.1); // Here you can call your contract functions
const functionAbi = contractFunction.encodeABI();
let estimatedGas;
let nonce;
console.log("Getting gas estimate");
contractFunction.estimateGas({from: account}).then((gasAmount) => {
estimatedGas = gasAmount.toString(16);
console.log("Estimated gas: " + estimatedGas);
web3.eth.getTransactionCount(account).then(_nonce => {
nonce = _nonce.toString(16);
console.log("Nonce: " + nonce);
const txParams = {
gasPrice: 100000,
gasLimit: 3000000,
to: contractAddress,
data: functionAbi,
from: account,
nonce: '0x' + nonce
};
const tx = new Tx(txParams);
tx.sign(privateKey); // Transaction Signing here
const serializedTx = tx.serialize();
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).on('receipt', receipt => {
console.log(receipt);
})
});
});
Transaction time is about 20-30 seconds so you should wait quite a bit.