Using local private key with Web3.js
Asked Answered
S

3

7

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
Selfeducated answered 28/5, 2021 at 9:58 Comment(0)
S
9

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.

Selfeducated answered 28/5, 2021 at 9:58 Comment(3)
I know it is stupid but I would like to add a comment here in case anyone may run into the same dummy situation. I kept getting an error like: json { code: -32000, message: 'internal' } Then I figured out that the test-net I am working on just halted for maintenance. So yes it was an another amazing day for the developer :)Nubbly
The official docs from Infura shows how to do it with hdwallet like middleware docs.infura.io/infura/tutorials/ethereum/… (Oct 2022)Ionosphere
My personal question is how to do the same over web3j as it seems to be does not have privateKeyToAccount() method.Ionosphere
I
1

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;
}
    }
Interlunation answered 22/6, 2022 at 5:15 Comment(0)
L
0

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

Longhorn answered 20/6, 2022 at 16:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.