web3js – How to Sign Messages in Truffle Tests for Blockchain Applications

signaturetruffletruffle-testweb3js

I use Truffle to develop locally and run tests with truffle test (letting it create a temporary local blockchain).

In one of my tests I need to sign a message and for that I need the test wallet's privateKey. Unfortunately Truffle's contract() provides only an array of accounts (an array of addresses). I googled and it seems that there is no way to get the private key for those addresses.

So I tried the following:

await web3.eth.accounts.wallet.create(1);
const wallet = web3.eth.accounts.wallet[0];

// ok
const signedMessage = await wallet.sign('message');

// 🆘 fails with "sender account not recognized"
const contract = await MyContract.new({ from: wallet.address }); 

The Issue: The message signer should also be the deployer of the contract but when I try to instantiate the contract as the newly created wallet.address I get the following error:

1) Contract: MyContract
       test case no. 1 :
     Error: Returned error: sender account not recognized
      at Context.<anonymous> (test/my_contract.js:67:43)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

Best Answer

We observe that web3 exposes a few wallet functionalities that may not be very clear at first:

  1. web3.eth.accounts / getAccounts()
  2. web3.eth.accounts.wallet

It seems web3.eth.accounts.wallet.create is a utility meant to create accounts on the fly without actually auto-binding them to your local node's account list (credits: previous answer). You could use web3.eth.personal.newAccout() but that still wouldn't give you access to the private key.

Solution

Using the two utilities, we can (1) create a new wallet (2) add it to the "personal" list.

 /** Connect to ganache */
 const web3 = new Web3('http://localhost:7545')

 /** Create the new wallet */
 await web3.eth.accounts.wallet.create(1)
 const wallet = web3.eth.accounts.wallet[0]

 /** Bind the new wallet to the personal accounts */
 await web3.eth.personal.importRawKey(wallet.privateKey, '') // password is empty
 await web3.eth.personal.unlockAccount(wallet.address, '', 10000) // arbitrary duration

 /** Check wallet was added to accounts */
 const accounts = await web3.eth.getAccounts()
 console.log(accounts, wallet.address)

Small caveat here is that if you wanna pursue your contract creation, you also need to fund the new account with some ETH for gas.