Truffle Wallet Provider should support Websockets
See original GitHub issueIssue
The truffle-hdwallet-provider currently only supports http connections.
I’m using drizzle connected to Pantheon. For my test environment, I’ve spun it up so that none of my accounts need any eth to interact with the dapp. I like how your hooked wallet implementation can create ephemeral keys from a set mnemonic. It would be nice to be able to use that without forking.
Steps to Reproduce
Pass any WebSocket provider into truffle-hdwallet-provider and attach it to drizzle and it will tell you that subscriptions aren’t supported. This is because the SubproviderProvider used in the engine doesn’t support subscriptions. Any sub-provider that does, will be ignored.
Expected Behavior
Passing in a provider will yield the full feature set of the provider, not just being able to send a request.
Workaround
Right now, by editing the code to add a subprovider instead of creating a SubproviderProvider (phew) everything works as expected.
This could likely either be fixed by addressing what a SubProviderProvider can do, or by tweaking the initialization of your provider engine. The latter seemed better to me. I’d be happy to make a pr if you’ll accept.
Example
```jsconst bip39 = require('bip39');
const ethJSWallet = require('ethereumjs-wallet');
const hdkey = require('ethereumjs-wallet/hdkey');
const ProviderEngine = require('web3-provider-engine');
const FiltersProvider = require('web3-provider-engine/subproviders/filters.js');
const WebsocketProvider = require('web3-provider-engine/subproviders/websocket.js');
const NonceProvider = require('web3-provider-engine/subproviders/nonce-tracker');
const HookedProvider = require('web3-provider-engine/subproviders/hooked-wallet.js');
const Transaction = require('ethereumjs-tx');
const ethUtil = require('ethereumjs-util');
export default (mnemonic, address_index = 0, num_addresses = 10) => {
let hdwallet;
const wallet_hdpath = "m/44'/60'/0'/0/";
const wallets = {};
const addresses = [];
const engine = new ProviderEngine();
// private helper to normalize given mnemonic
const normalizePrivateKeys = mnemonic => {
if (Array.isArray(mnemonic)) return mnemonic;
else if (mnemonic && !mnemonic.includes(' ')) return [mnemonic];
// if truthy, but no spaces in mnemonic
else return false; // neither an array nor valid value passed;
};
// private helper to check if given mnemonic uses BIP39 passphrase protection
const checkBIP39Mnemonic = mnemonic => {
hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonic));
if (!bip39.validateMnemonic(mnemonic)) {
throw new Error('Mnemonic invalid or undefined');
}
// crank the addresses out
for (let i = address_index; i < address_index + num_addresses; i++) {
const wallet = hdwallet.derivePath(wallet_hdpath + i).getWallet();
const addr = `0x${wallet.getAddress().toString('hex')}`;
addresses.push(addr);
wallets[addr] = wallet;
}
};
// private helper leveraging ethUtils to populate wallets/addresses
const ethUtilValidation = privateKeys => {
// crank the addresses out
for (let i = address_index; i < address_index + num_addresses; i++) {
const privateKey = Buffer.from(privateKeys[i].replace('0x', ''), 'hex');
if (ethUtil.isValidPrivate(privateKey)) {
const wallet = ethJSWallet.fromPrivateKey(privateKey);
const address = wallet.getAddressString();
addresses.push(address);
wallets[address] = wallet;
}
}
};
const privateKeys = normalizePrivateKeys(mnemonic);
if (!privateKeys) checkBIP39Mnemonic(mnemonic);
else ethUtilValidation(privateKeys);
const tmp_accounts = addresses;
const tmp_wallets = wallets;
engine.addProvider(
new HookedProvider({
getAccounts(cb) {
cb(null, tmp_accounts);
},
getPrivateKey(address, cb) {
if (!tmp_wallets[address]) {
return cb('Account not found');
} else {
cb(null, tmp_wallets[address].getPrivateKey().toString('hex'));
}
},
signTransaction(txParams, cb) {
let pkey;
const from = txParams.from.toLowerCase();
if (tmp_wallets[from]) {
pkey = tmp_wallets[from].getPrivateKey();
} else {
cb('Account not found');
}
const tx = new Transaction(txParams);
tx.sign(pkey);
const rawTx = `0x${tx.serialize().toString('hex')}`;
cb(null, rawTx);
},
signMessage({ data, from }, cb) {
const dataIfExists = data;
if (!dataIfExists) {
cb('No data to sign');
}
if (!tmp_wallets[from]) {
cb('Account not found');
}
const pkey = tmp_wallets[from].getPrivateKey();
const dataBuff = ethUtil.toBuffer(dataIfExists);
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
const sig = ethUtil.ecsign(msgHashBuff, pkey);
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
cb(null, rpcSig);
},
signPersonalMessage(...args) {
this.signMessage(...args);
}
})
);
engine.addProvider(new NonceProvider());
engine.addProvider(new FiltersProvider());
// Interesting bit is here
engine.addProvider(new WebsocketProvider({ rpcUrl: 'ws://127.0.0.1:8546' }));
engine.start(); // Required by the provider engine.
return engine;
};
</details>
## Environment
* Operating System: osx
* Ethereum client: pantheon/Drizzle
* Truffle version (`truffle version`): 5.0.18
* node version (`node --version`): 10.15
* npm version (`npm --version`): 6.9.0
Issue Analytics
- State:
- Created 4 years ago
- Reactions:3
- Comments:5 (3 by maintainers)

Top Related StackOverflow Question
Is this still an issue with the latest version of
@truffle/hdwallet-provider?Closing this for issue maintenance since it appears that @truffle/hdwallet-provider does support websockets.
If there’s still an issue, well, let us know, and we’ll be happy to re-open! Thanks y’all 🙇