trezor-web3-subprovider
Version:
Trezor Web3 Subprovider used on OtoCo.io
197 lines (196 loc) • 9.55 kB
JavaScript
;
// Based on source from:
// https://github.com/web3modal/trezor-provider
// Package update and code upgrade by: Filipe Soccol
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TrezorSubprovider = void 0;
const utils_1 = require("./utils");
const EthereumTx = require("ethereumjs-tx");
const HDNode = require("hdkey");
const types_1 = require("./types");
const utils_2 = require("./utils");
const baseProvider_1 = require("./baseProvider");
const PRIVATE_KEY_PATH = `44'/60'/0'/0`;
const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10;
const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000;
class TrezorSubprovider extends baseProvider_1.BaseWalletSubprovider {
/**
* Instantiates a TrezorSubprovider. Defaults to private key path set to `44'/60'/0'/0/`.
* Must be initialized with trezor-connect API module https://github.com/trezor/connect.
* @param TrezorSubprovider config object containing trezor-connect API
* @return TrezorSubprovider instance
*/
constructor(config) {
super();
this._privateKeyPath = PRIVATE_KEY_PATH;
this._trezorConnectClientApi = config.trezorConnectClientApi;
this._networkId = config.networkId;
this._addressSearchLimit =
config.accountFetchingConfigs !== undefined &&
config.accountFetchingConfigs.addressSearchLimit !== undefined
? config.accountFetchingConfigs.addressSearchLimit
: DEFAULT_ADDRESS_SEARCH_LIMIT;
}
/**
* Retrieve a users Trezor account. This method is automatically called
* when issuing a `eth_accounts` JSON RPC request via your providerEngine
* instance.
* @return An array of accounts
*/
getAccountsAsync(numberOfAccounts = DEFAULT_NUM_ADDRESSES_TO_FETCH) {
return __awaiter(this, void 0, void 0, function* () {
const initialDerivedKeyInfo = yield this._initialDerivedKeyInfoAsync();
const derivedKeyInfos = utils_2.walletUtils.calculateDerivedHDKeyInfos(initialDerivedKeyInfo, numberOfAccounts);
const accounts = derivedKeyInfos.map(k => k.address);
return accounts;
});
}
/**
* Signs a transaction on the Trezor with the account specificed by the `from` field in txParams.
* If you've added the TrezorSubprovider to your app's provider, you can simply send an `eth_sendTransaction`
* JSON RPC request, and this method will be called auto-magically. If you are not using this via a ProviderEngine
* instance, you can call it directly.
* @param txParams Parameters of the transaction to sign
* @return Signed transaction hex string
*/
signTransactionAsync(txData) {
return __awaiter(this, void 0, void 0, function* () {
if (txData.from === undefined || !utils_1.addressUtils.isAddress(txData.from)) {
throw new Error(types_1.WalletSubproviderErrors.FromAddressMissingOrInvalid);
}
txData.value = txData.value ? txData.value : '0x0';
txData.data = txData.data ? txData.data : '0x';
txData.gas = txData.gas ? txData.gas : '0x0';
txData.gasPrice = txData.gasPrice ? txData.gasPrice : '0x0';
const initialDerivedKeyInfo = yield this._initialDerivedKeyInfoAsync();
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(initialDerivedKeyInfo, txData.from);
const fullDerivationPath = derivedKeyInfo.derivationPath;
const response = yield this._trezorConnectClientApi.ethereumSignTransaction({
path: fullDerivationPath,
transaction: {
to: txData.to,
value: txData.value,
data: txData.data,
chainId: this._networkId,
nonce: txData.nonce,
gasLimit: txData.gas,
gasPrice: txData.gasPrice,
},
});
if (response.success) {
const payload = response.payload;
const tx = new EthereumTx.Transaction(txData);
// Set the EIP155 bits
const vIndex = 6;
tx.raw[vIndex] = Buffer.from([1]); // v
const rIndex = 7;
tx.raw[rIndex] = Buffer.from([]); // r
const sIndex = 8;
tx.raw[sIndex] = Buffer.from([]); // s
// slice off leading 0x
tx.v = Buffer.from(payload.v.slice(2), 'hex');
tx.r = Buffer.from(payload.r.slice(2), 'hex');
tx.s = Buffer.from(payload.s.slice(2), 'hex');
return `0x${tx.serialize().toString('hex')}`;
}
else {
const payload = response.payload;
throw new Error(payload.error);
}
});
}
/**
* Sign a personal Ethereum signed message. The signing account will be the account
* associated with the provided address. If you've added the TrezorSubprovider to
* your app's provider, you can simply send an `eth_sign` or `personal_sign` JSON RPC
* request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param data Hex string message to sign
* @param address Address of the account to sign with
* @return Signature hex string (order: rsv)
*/
signPersonalMessageAsync(data, address) {
return __awaiter(this, void 0, void 0, function* () {
if (data === undefined) {
throw new Error(types_1.WalletSubproviderErrors.DataMissingForSignPersonalMessage);
}
const initialDerivedKeyInfo = yield this._initialDerivedKeyInfoAsync();
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(initialDerivedKeyInfo, address);
const fullDerivationPath = derivedKeyInfo.derivationPath;
const response = yield this._trezorConnectClientApi.ethereumSignMessage({
path: fullDerivationPath,
message: data,
hex: true,
});
if (response.success) {
const payload = response.payload;
return `0x${payload.signature}`;
}
else {
const payload = response.payload;
throw new Error(payload.error);
}
});
}
/**
* TODO:: eth_signTypedData is currently not supported on Trezor devices.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
// tslint:disable-next-line:prefer-function-over-method
signTypedDataAsync(address, typedData) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error(types_1.WalletSubproviderErrors.MethodNotSupported);
});
}
_initialDerivedKeyInfoAsync() {
return __awaiter(this, void 0, void 0, function* () {
if (this._initialDerivedKeyInfo) {
return this._initialDerivedKeyInfo;
}
else {
const parentKeyDerivationPath = `m/${this._privateKeyPath}`;
const response = yield this._trezorConnectClientApi.getPublicKey({
path: parentKeyDerivationPath,
});
if (response.success) {
const payload = response.payload;
const hdKey = new HDNode();
hdKey.publicKey = new Buffer(payload.publicKey, 'hex');
hdKey.chainCode = new Buffer(payload.chainCode, 'hex');
const address = utils_2.walletUtils.addressOfHDKey(hdKey);
const initialDerivedKeyInfo = {
hdKey,
address,
derivationPath: parentKeyDerivationPath,
baseDerivationPath: this._privateKeyPath,
};
this._initialDerivedKeyInfo = initialDerivedKeyInfo;
return initialDerivedKeyInfo;
}
else {
const payload = response.payload;
throw new Error(payload.error);
}
}
});
}
_findDerivedKeyInfoForAddress(initalHDKey, address) {
const matchedDerivedKeyInfo = utils_2.walletUtils.findDerivedKeyInfoForAddressIfExists(address, initalHDKey, this._addressSearchLimit);
if (matchedDerivedKeyInfo === undefined) {
throw new Error(`${types_1.WalletSubproviderErrors.AddressNotFound}: ${address}`);
}
return matchedDerivedKeyInfo;
}
}
exports.TrezorSubprovider = TrezorSubprovider;