parity-hdwallet-provider
Version:
HD Wallet-enabled Web3 provider for parity
247 lines • 11.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const bip39 = __importStar(require("ethereum-cryptography/bip39"));
const english_1 = require("ethereum-cryptography/bip39/wordlists/english");
const EthUtil = __importStar(require("ethereumjs-util"));
const ethereumjs_wallet_1 = __importDefault(require("ethereumjs-wallet"));
const ethereumjs_wallet_2 = require("ethereumjs-wallet");
const eth_sig_util_1 = require("eth-sig-util");
const ethereumjs_tx_1 = require("ethereumjs-tx");
// @ts-ignore
const web3_provider_engine_1 = __importDefault(require("@trufflesuite/web3-provider-engine"));
const filters_1 = __importDefault(require("@trufflesuite/web3-provider-engine/subproviders/filters"));
const nonce_tracker_1 = __importDefault(require("@trufflesuite/web3-provider-engine/subproviders/nonce-tracker"));
const hooked_wallet_1 = __importDefault(require("@trufflesuite/web3-provider-engine/subproviders/hooked-wallet"));
const provider_1 = __importDefault(require("@trufflesuite/web3-provider-engine/subproviders/provider"));
// @ts-ignore
const rpc_1 = __importDefault(require("@trufflesuite/web3-provider-engine/subproviders/rpc"));
// @ts-ignore
const websocket_1 = __importDefault(require("@trufflesuite/web3-provider-engine/subproviders/websocket"));
const url_1 = __importDefault(require("url"));
const getOptions_1 = require("./constructor/getOptions");
const getPrivateKeys_1 = require("./constructor/getPrivateKeys");
const getMnemonic_1 = require("./constructor/getMnemonic");
// Important: do not use debug module. Reason: https://github.com/trufflesuite/truffle/issues/2374#issuecomment-536109086
// This line shares nonce state across multiple provider instances. Necessary
// because within truffle the wallet is repeatedly newed if it's declared in the config within a
// function, resetting nonce from tx to tx. An instance can opt out
// of this behavior by passing `shareNonce=false` to the constructor.
// See issue #65 for more
const singletonNonceSubProvider = new nonce_tracker_1.default();
class HDWalletProvider {
constructor(...args) {
const _a = getOptions_1.getOptions(...args), { providerOrUrl, // required
addressIndex = 0, numberOfAddresses = 10, shareNonce = true, derivationPath = `m/44'/60'/0'/0/`, pollingInterval = 4000, chainId } = _a,
// what's left is either a mnemonic or a list of private keys
signingAuthority = __rest(_a, ["providerOrUrl", "addressIndex", "numberOfAddresses", "shareNonce", "derivationPath", "pollingInterval", "chainId"]);
const mnemonic = getMnemonic_1.getMnemonic(signingAuthority);
const privateKeys = getPrivateKeys_1.getPrivateKeys(signingAuthority);
this.walletHdpath = derivationPath;
this.wallets = {};
this.addresses = [];
this.engine = new web3_provider_engine_1.default({
pollingInterval
});
// Hack so that parity nodes won't barf on the "skipFlag: true" flag in the
// RPC call: https://github.com/MetaMask/web3-provider-engine/issues/346
this.engine._blockTracker._setSkipCacheFlag = false;
if (!HDWalletProvider.isValidProvider(providerOrUrl)) {
throw new Error([
`Malformed provider URL: '${providerOrUrl}'`,
"Please specify a correct URL, using the http, https, ws, or wss protocol.",
""
].join("\n"));
}
// private helper to check if given mnemonic uses BIP39 passphrase protection
const checkBIP39Mnemonic = ({ phrase, password }) => {
this.hdwallet = ethereumjs_wallet_2.hdkey.fromMasterSeed(bip39.mnemonicToSeedSync(phrase, password));
if (!bip39.validateMnemonic(phrase, english_1.wordlist)) {
throw new Error("Mnemonic invalid or undefined");
}
// crank the addresses out
for (let i = addressIndex; i < addressIndex + numberOfAddresses; i++) {
const wallet = this.hdwallet
.derivePath(this.walletHdpath + i)
.getWallet();
const addr = `0x${wallet.getAddress().toString("hex")}`;
this.addresses.push(addr);
this.wallets[addr] = wallet;
}
};
// private helper leveraging ethUtils to populate wallets/addresses
const ethUtilValidation = (privateKeys) => {
// crank the addresses out
for (let i = addressIndex; i < privateKeys.length; i++) {
const privateKey = Buffer.from(privateKeys[i].replace("0x", ""), "hex");
if (EthUtil.isValidPrivate(privateKey)) {
const wallet = ethereumjs_wallet_1.default.fromPrivateKey(privateKey);
const address = wallet.getAddressString();
this.addresses.push(address);
this.wallets[address] = wallet;
}
}
};
if (mnemonic && mnemonic.phrase) {
checkBIP39Mnemonic(mnemonic);
}
else if (privateKeys) {
ethUtilValidation(privateKeys);
} // no need to handle else case here, since matchesNewOptions() covers it
if (this.addresses.length === 0) {
throw new Error(`Could not create addresses from your mnemonic or private key(s). ` +
`Please check that your inputs are correct.`);
}
const tmpAccounts = this.addresses;
const tmpWallets = this.wallets;
this.engine.addProvider(new hooked_wallet_1.default({
getAccounts(cb) {
cb(null, tmpAccounts);
},
getPrivateKey(address, cb) {
if (!tmpWallets[address]) {
return cb("Account not found");
}
else {
cb(null, tmpWallets[address].getPrivateKey().toString("hex"));
}
},
signTransaction(txParams, cb) {
// we need to rename the 'gas' field
txParams.gasLimit = txParams.gas;
delete txParams.gas;
let pkey;
const from = txParams.from.toLowerCase();
if (tmpWallets[from]) {
pkey = tmpWallets[from].getPrivateKey();
}
else {
cb("Account not found");
}
const chain = (chainId && typeof chainId === "string") ?
parseInt(chainId) :
chainId;
const tx = new ethereumjs_tx_1.Transaction(txParams, { chain });
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 (!tmpWallets[from]) {
cb("Account not found");
}
let pkey = tmpWallets[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);
},
signTypedMessage({ from: checkSummedFrom, data }, cb) {
if (!data) {
cb("No data to sign");
}
let pkey;
let from = checkSummedFrom.toLowerCase();
if (tmpWallets[from]) {
pkey = tmpWallets[from].getPrivateKey();
const result = eth_sig_util_1.signTypedData_v4(pkey, { data });
cb(null, result);
}
else {
cb("Account not found");
}
}
}));
!shareNonce
? this.engine.addProvider(new nonce_tracker_1.default())
: this.engine.addProvider(singletonNonceSubProvider);
this.engine.addProvider(new filters_1.default());
if (typeof providerOrUrl === "string") {
const url = providerOrUrl;
const providerProtocol = (url_1.default.parse(url).protocol || "http:").toLowerCase();
switch (providerProtocol) {
case "ws:":
case "wss:":
this.engine.addProvider(new websocket_1.default({ rpcUrl: url }));
break;
default:
this.engine.addProvider(new rpc_1.default({ rpcUrl: url }));
}
}
else {
const provider = providerOrUrl;
this.engine.addProvider(new provider_1.default(provider));
}
// Required by the provider engine.
this.engine.start((err) => {
if (err)
throw err;
});
}
send(payload, callback) {
return this.engine.send.call(this.engine, payload, callback);
}
sendAsync(payload, callback) {
this.engine.sendAsync.call(this.engine, payload, callback);
}
getAddress(idx) {
if (!idx) {
return this.addresses[0];
}
else {
return this.addresses[idx];
}
}
getAddresses() {
return this.addresses;
}
static isValidProvider(provider) {
const validProtocols = ["http:", "https:", "ws:", "wss:"];
if (typeof provider === "string") {
const url = url_1.default.parse(provider.toLowerCase());
return !!(validProtocols.includes(url.protocol || "") && url.slashes);
}
return true;
}
}
module.exports = HDWalletProvider;
//# sourceMappingURL=index.js.map