truffle icon indicating copy to clipboard operation
truffle copied to clipboard

Provider factory in truffle config should be able to operate asynchronously

Open benjamincburns opened this issue 7 years ago • 13 comments

When using clients/networks other than Ganache, it can be advantageous to use a provider like HDWalletProvider. These providers are initialized with some secret (such as a mnemonic or private key), and automatically sign outbound transactions using one or more accounts derived from this secret.

Truffle handles this today by allowing the user to specify a provider function in a network config instead of a host and port. This provider function is meant to synchronously return an instance of Provider for truffle to use internally with its own instance of web3.

Because the provider must be returned synchronously, the user is limited in their options for how they can retrieve a secret with which to construct their provider. Should they wish to prompt a user for a password to decrypt a wallet file, or conduct any other asynchronous operations, they cannot. Instead truffle should allow this provider factory function to optionally return a Promise which resolves to an instance of Provider instead of returning the Provider instance directly.

benjamincburns avatar Jun 21 '18 20:06 benjamincburns

Example of how this functionality might be used:

const prompt = require('prompt');                                                                                        
const fs     = require('fs');                                                                                            
const Wallet = require('ethereumjs-wallet');prompt.start();                                                              
const pify = require('pify');               

// fork of truffle-hdwallet-provider which accepts a private key as the first argument
// see https://github.com/rhlsthrm/truffle-hdwallet-provider-privkey for more info
const HDWalletProvider = require('truffle-hdwallet-provider-privkey');                                                                             
                                                                                                                         
var schema = {                                                                                                           
    properties: {                                                                                                        
        walletFilename: {                                                                                                
            //validator: /^[a-zA-Z0-9\-]+$/,                                                                             
            //warning: 'Wallet name must be only letters, numbers or dashes',                                            
            required: true,                                                                                              
        },                                                                                                               
        password: {                                                                                                      
            hidden: true,                                                                                                
            required: true,                                                                                              
        }                                                                                                                
    }                                                                                                                    
};                                                                                                                       
                                                                                                                         
module.exports = {                                                                                                       
  networks: {                                                                                                            
    mainnet: {                                                                                                           
      provider: async () => {                                                                                            
        let result = await pify(prompt.get)(schema)                                                                      
                                                                                                                         
        let strJson = fs.readFileSync(result.walletFilename, 'utf8');                                                    
        let wallet  = Wallet.fromV3(strJson, result.password);                                                           
                                                                                                                         
        this.privKey = wallet.getPrivateKey();                                                                           
                                                                                                                         
        return new HDWalletProvider([privKey.toString('hex')], 'https://mainnet.infura.io:8545');                             
      },                                                                                                                 
      network_id: "1"                                                                                                    
    }                                                                                                                    
  }                                                                                                                      
};

benjamincburns avatar Jun 21 '18 21:06 benjamincburns

Is this supported?

EDIT: Spoke to @gnidan and this is not supported unfortunately. Env vars is an option probably.

mrwillis avatar Dec 03 '18 22:12 mrwillis

Was the functionality @benjamincburns proposed in his commits unacceptable in some way? (Curious because I'm thinking of using it in a fork.)

coventry avatar Mar 01 '19 18:03 coventry

@coventry why fork?

Either way, I imagine it'd be almost trivial to write an abstract provider which calls an init method on the first call to send. You'd probably want to add some kind of locking mechanism to it so that you don't init multiple times in the case that multiple calls get pipelined. I'll see about adding one somewhere once the EIP-1193 dust settles, provided nobody else beats me to the punch.

benjamincburns avatar Mar 04 '19 06:03 benjamincburns

@gnidan it looks like this issue was closed but the PR I submitted never made it in. This means that users still can't have async logic in their provider initialization code, so they can't, for example, prompt for a password to unlock an encrypted wallet file.

benjamincburns avatar May 11 '20 21:05 benjamincburns

If this has been implemented, perhaps the docs should be updated to reflect this here?

https://www.trufflesuite.com/docs/truffle/reference/configuration#providers

benjamincburns avatar May 11 '20 21:05 benjamincburns

I can't work out whether this is implemented. @gnidan ?

elenadimitrova avatar Nov 06 '20 09:11 elenadimitrova

@elenadimitrova IIRC we ended up needing to revert that PR because of complexities it introduced. I'll re-open this.

gnidan avatar Nov 09 '20 00:11 gnidan

Hey @elenadimitrova, we're trying to evaluate the priority of this task and wanted to ask how important of a feature this would be for you guys. Would this be a super useful feature?

eggplantzzz avatar Nov 11 '20 18:11 eggplantzzz

It is blocking for us to move to truffle atm. We are using an async method to get the private key used to instantiate the HDWalletProvider:

staging: {
      provider: async () => {
        const { pkey, infuraKey } = await getKeys();
        return new HDWalletProvider(pkey, `https://mainnet.infura.io/v3/${infuraKey}`);
      },
      network_id: 1, // mainnet
      gas: _gasLimit,
      gasPrice: _gasPrice,
    },

This async method is here, getting the key from AWS.

elenadimitrova avatar Nov 12 '20 08:11 elenadimitrova

Ok, thanks @elenadimitrova for letting us know! We'll try and get it in the pipeline.

eggplantzzz avatar Nov 18 '20 18:11 eggplantzzz

A gentle nudge as this is one of the two blockers to move the argent-contracts repo to truffle. Let me know if you need any help with it.

elenadimitrova avatar Nov 27 '20 08:11 elenadimitrova

Thanks @elenadimitrova!

eggplantzzz avatar Nov 30 '20 19:11 eggplantzzz

Previously this may not have been feasible due to callback hell. This is probably worth revisiting to determine whether this is doable.

eggplantzzz avatar Feb 16 '23 19:02 eggplantzzz