@machinomy/hdwallet-provider
Version:
HD Wallet-enabled Web3 provider
136 lines • 6.23 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LedgerSubprovider = exports.InvalidNetworkIdError = void 0;
const hooked_wallet_1 = __importDefault(require("web3-provider-engine/subproviders/hooked-wallet"));
const path_util_1 = require("./path.util");
const hw_app_eth_1 = __importDefault(require("@ledgerhq/hw-app-eth"));
const util_1 = require("./util");
const transaction_util_1 = require("./util/transaction.util");
class InvalidNetworkIdError extends Error {
}
exports.InvalidNetworkIdError = InvalidNetworkIdError;
class LedgerSubprovider extends hooked_wallet_1.default {
constructor(getTransport, options) {
const path = path_util_1.normalizePath(options.path || path_util_1.DEFAULT_PATH);
const askConfirm = options.askConfirm || false;
const accountsLength = options.accountsLength || 1;
const accountsOffset = options.accountsOffset || 0;
const pathComponents = path_util_1.componentsFromPath(path);
const _addressToPath = new Map();
/**
* @return address => path mapping
*/
async function getAccounts() {
const transport = await getTransport();
try {
const eth = new hw_app_eth_1.default(transport);
const addresses = new Map();
for (let i = accountsOffset; i < accountsOffset + accountsLength; i++) {
const path = pathComponents.basePath + (pathComponents.index + i).toString();
const address = await eth.getAddress(path, askConfirm, false);
addresses.set(path, address.address);
_addressToPath.set(address.address.toLowerCase(), path);
}
return addresses;
}
finally {
transport.close();
}
}
/**
* @return path => address mapping
*/
async function getAddressToPath() {
if (_addressToPath.size == 0) {
await getAccounts();
}
return _addressToPath;
}
async function signPersonalMessage(msgData) {
const addressToPath = await getAddressToPath();
const path = addressToPath.get(msgData.from.toLowerCase());
if (!path)
throw new Error(`address unknown '${msgData.from}'`);
const transport = await getTransport();
try {
const eth = new hw_app_eth_1.default(transport);
const result = await eth.signPersonalMessage(path, util_1.stripHexPrefix(msgData.data));
const v = parseInt(result.v.toString(), 10) - 27;
let vHex = v.toString(16);
if (vHex.length < 2) {
vHex = `0${v}`;
}
return `0x${result.r}${result.s}${vHex}`;
}
finally {
transport.close();
}
}
async function signTransaction(networkId, txData) {
const addressToPath = await getAddressToPath();
const path = addressToPath.get(txData.from.toLowerCase());
if (!path)
throw new Error("address unknown '" + txData.from + "'");
const transport = await getTransport();
try {
const eth = new hw_app_eth_1.default(transport);
const tx = transaction_util_1.buildTransaction(txData, networkId);
// Set the EIP155 bits
tx.raw[6] = Buffer.from([networkId]); // v
tx.raw[7] = Buffer.from([]); // r
tx.raw[8] = Buffer.from([]); // s
// Pass hex-rlp to ledger for signing
const result = await eth.signTransaction(path, tx.serialize().toString("hex"));
// Store signature in transaction
tx.v = Buffer.from(result.v, "hex");
tx.r = Buffer.from(result.r, "hex");
tx.s = Buffer.from(result.s, "hex");
// EIP155: v should be chain_id * 2 + {35, 36}
const signedChainId = Math.floor((tx.v[0] - 35) / 2);
const validChainId = networkId & 0xff; // FIXME this is to fixed a current workaround that app don't support > 0xff
if (signedChainId !== validChainId) {
throw new InvalidNetworkIdError(`Invalid networkId signature returned. Expected: ${networkId}, Got: ${signedChainId}`);
}
return `0x${tx.serialize().toString("hex")}`;
}
finally {
transport.close();
}
}
super({
getAccounts: callback => {
getAccounts()
.then(res => callback(null, Array.from(res.values())))
.catch(err => callback(err, undefined));
},
signPersonalMessage: (txData, callback) => {
signPersonalMessage(txData)
.then(res => callback(null, res))
.catch(err => callback(err, undefined));
},
signMessage: (txData, callback) => {
signPersonalMessage(txData)
.then(res => callback(null, res))
.catch(err => callback(err, undefined));
},
signTransaction: (txData, callback) => {
this.engine.sendAsync(util_1.createPayload({ method: "net_version" }), (err, result) => {
if (err) {
return callback(err);
}
else {
const networkId = Number(result.result);
signTransaction(networkId, txData)
.then(res => callback(null, res))
.catch(err => callback(err, undefined));
}
});
}
});
}
}
exports.LedgerSubprovider = LedgerSubprovider;
//# sourceMappingURL=ledger.subprovider.js.map