How to call a function with a private key using web3 and Infura?

Hi @abcoathup - Came across this while looking for a solution but without using HDWalletProvider.

I’m facing issues with using HDWalletProvider and Infura. Seems like using HDWalletProvider causes a high number of eth_getBlockByNumber requests (issue raised by multiple people e.g., here)

How to specify specific sender account if private keys are known?

const ethNetwork = 'wss://rinkeby.infura.io/ws/v3/PROJECT-ID'
const web3 = await new Web3(new Web3.providers.WebsocketProvider(ethNetwork))

// abi and address defined here
const contract = await new web3.eth.Contract(abi, address);

// PRIVATE_KEY variable defined
const account = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY)
console.log(account)
/*
{
  address: 'ADDRESS',
  privateKey: PRIVATE_KEY,
  signTransaction: [Function: signTransaction],
  sign: [Function: sign],
  encrypt: [Function: encrypt]
}
*/

await contract.methods.method_name().call({
  from: account
})

throws error:

 throw new Error(`Provided address ${address} is invalid, the capitalization checksum test failed, or it's an indirect IBAN address which can't be converted.`);

But I can see in 'ADDRESS' output that it has capital letters too. And:

$ truffle console
> web3.utils.isAddress('ADDRESS')
true

web3.utils.toChecksumAddress('ADDRESS')
ADDRESS
// returns same case-sensitive ADDRESS

What’s the correct way to make call() requests from a specific account using web3 only and NOT through HDWalletProvider using Infura API? Any ideas?

1 Like

Hi @megatower66,

I am not sure of the syntax to use to use WebsocketProvider with a private key. I assume you would need to unlock the account.

You could try another provider that accepts private keys rather than a mnemonic.

Thanks @abcoathup - you’re right that account needs to be unlocked.

I’ve mnemonic as well but couldn’t find any web3 helper function that takes mnemonic.

But how does one unlock an account on a hosted node (such as Infura) without using HDWalletProvider and only native web3 functions?

I tried signing the call request in a similar way one would sign the send txn.

Here’s what I did:

const ethNetwork = 'wss://rinkeby.infura.io/ws/v3/PROJECT-ID'
const web3 = await new Web3(new Web3.providers.WebsocketProvider(ethNetwork))

// abi and address defined here
const contract = await new web3.eth.Contract(abi, address);

// PRIVATE_KEY variable defined
const account = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY)

// define METHOD_NAME, ARG1, ARG2 here
const transaction = contract.methods.METHOD_NAME(ARG1, ARG2);

// define CONTRACT_ADDRESS
const options = {
        to: CONTRACT_ADDRESS,
        data: transaction.encodeABI(),
        gas: await transaction.estimateGas({from: account.address}),
        gasPrice: await web3.eth.getGasPrice() // or use some predefined value
    };

const signed  = await web3.eth.accounts.signTransaction(options, PRIVATE_KEY);
const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);

console.log(receipt) // print receipt

/*

gas: 119561
gasPrice: 1000000000
{
  blockHash: '0x0292aad...',
  blockNumber: ....,
  contractAddress: null,
  cumulativeGasUsed: 2930052,
  from: '0xc4789c....,
  gasUsed: 118627,
  logs: [],
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  status: true,
  to: '0xcfd....7',
  transactionHash: '0x535a...',
  transactionIndex: 9
}

*/

As you can see, I was expecting returned values from METHOD_NAME but got an empty array in receipt.logs

However, if I use truffle console to connect to rinkeby network (using HDWalletProvider in truffle-config.js), I get returned values from METHOD_NAME (which is supposed to be executed using METHOD_NAME.call()

HDWalletProvider is perfect but doesn’t work well with hosted node (such as Infura) as it executes a lot of eth_getBlockByNumber requests and hits Infura’s 100K/day API limit in less than 10 minutes (or say 5 function calls).

Some say it is due to the way await calls work internally in web3:

let result = await contract.methods.METHOD_NAME().call({from:accounts[0]})

which awaits for block confirmations. Am not sure about the explanation but more so don’t know the resolution.

So is there a way to make call() requests from a specific account in web3 but NOT use HDWalletProvider on a hosted node such as Infura? (as HDWalletProvider doesn’t work well in web3 on Infura).

1 Like

Ok, so I just tried another way and that seemed to work with unlocking the account using web3 native functions only!

const PRIVATE_KEY = 'your_key'
web3.eth.accounts.wallet.add(PRIVATE_KEY)
const account = web3.eth.accounts.wallet[0].address

let result = await contract.methods.METHOD_NAME().call({from: account});

console.log(result)
// prints expected returned value from METHOD_NAME contract function!

I’ll now try send() as well for contract.methods.METHOD_NAME().send() but am assuming it should work.

Will circle back and report if Infura is settled as well due to not using HDWalletProvider as earlier assumed :crossed_fingers:

1 Like

So 24 hours in, send() works as expected and Infura is behaving quite well when used with native web3 functions only. Seems like HDWalletProvider and Infura don’t seem to like each other and both don’t seem to provide any community support either.

Winner is OpenZeppelin and @abcoathup for the awesome community support. :clap: :clap:

1 Like

Hi @megatower66,

Thanks for sharing the solution.

I haven’t had problems with the Infura free tier and HDWalletProvider yet, but noice to know there is a native web3 option.