@metamask-previews/eth-qr-keyring 0.0.0-36c61e7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +15 -0
  3. package/README.md +1 -0
  4. package/dist/device.cjs +346 -0
  5. package/dist/device.cjs.map +1 -0
  6. package/dist/device.d.cts +175 -0
  7. package/dist/device.d.cts.map +1 -0
  8. package/dist/device.d.mts +175 -0
  9. package/dist/device.d.mts.map +1 -0
  10. package/dist/device.mjs +346 -0
  11. package/dist/device.mjs.map +1 -0
  12. package/dist/index.cjs +19 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +3 -0
  15. package/dist/index.d.cts.map +1 -0
  16. package/dist/index.d.mts +3 -0
  17. package/dist/index.d.mts.map +1 -0
  18. package/dist/index.mjs +3 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/qr-keyring-deferred-promise-bridge.cjs +76 -0
  21. package/dist/qr-keyring-deferred-promise-bridge.cjs.map +1 -0
  22. package/dist/qr-keyring-deferred-promise-bridge.d.cts +46 -0
  23. package/dist/qr-keyring-deferred-promise-bridge.d.cts.map +1 -0
  24. package/dist/qr-keyring-deferred-promise-bridge.d.mts +46 -0
  25. package/dist/qr-keyring-deferred-promise-bridge.d.mts.map +1 -0
  26. package/dist/qr-keyring-deferred-promise-bridge.mjs +72 -0
  27. package/dist/qr-keyring-deferred-promise-bridge.mjs.map +1 -0
  28. package/dist/qr-keyring-scanner-bridge.cjs +37 -0
  29. package/dist/qr-keyring-scanner-bridge.cjs.map +1 -0
  30. package/dist/qr-keyring-scanner-bridge.d.cts +26 -0
  31. package/dist/qr-keyring-scanner-bridge.d.cts.map +1 -0
  32. package/dist/qr-keyring-scanner-bridge.d.mts +26 -0
  33. package/dist/qr-keyring-scanner-bridge.d.mts.map +1 -0
  34. package/dist/qr-keyring-scanner-bridge.mjs +33 -0
  35. package/dist/qr-keyring-scanner-bridge.mjs.map +1 -0
  36. package/dist/qr-keyring.cjs +288 -0
  37. package/dist/qr-keyring.cjs.map +1 -0
  38. package/dist/qr-keyring.d.cts +181 -0
  39. package/dist/qr-keyring.d.cts.map +1 -0
  40. package/dist/qr-keyring.d.mts +181 -0
  41. package/dist/qr-keyring.d.mts.map +1 -0
  42. package/dist/qr-keyring.mjs +283 -0
  43. package/dist/qr-keyring.mjs.map +1 -0
  44. package/package.json +97 -0
@@ -0,0 +1,175 @@
1
+ import { type TypedTransaction, type TypedTxData } from "@ethereumjs/tx";
2
+ import type { MessageTypes, TypedMessage } from "@metamask/eth-sig-util";
3
+ import { type Hex } from "@metamask/utils";
4
+ import { type QrKeyringBridge, type SerializedUR } from "./qr-keyring.mjs";
5
+ export declare const SUPPORTED_UR_TYPE: {
6
+ CRYPTO_HDKEY: string;
7
+ CRYPTO_ACCOUNT: string;
8
+ ETH_SIGNATURE: string;
9
+ };
10
+ export declare enum DeviceMode {
11
+ HD = "hd",
12
+ ACCOUNT = "account"
13
+ }
14
+ /**
15
+ * Common details for the device source, which can be either a CryptoAccount or CryptoHDKey.
16
+ */
17
+ export type CommonDeviceDetails = {
18
+ /**
19
+ * Value take out from the device note field, if available.
20
+ */
21
+ keyringAccount: string;
22
+ /**
23
+ * The name of the device
24
+ */
25
+ name: string;
26
+ /**
27
+ * The device fingerprint, hex-encoded
28
+ */
29
+ xfp: string;
30
+ /**
31
+ * Indexes of the accounts derived from the device
32
+ * in the form of a map from address to index
33
+ */
34
+ indexes: Record<Hex, number>;
35
+ };
36
+ /**
37
+ * Details for the HD mode of the Device. This mode derives
38
+ * accounts from a root public key (xpub) and a derivation path.
39
+ */
40
+ export type HDModeDeviceDetails = {
41
+ /**
42
+ * The device mode is HD, indicating that it derives accounts from a
43
+ * root public key (xpub) and a derivation path.
44
+ */
45
+ keyringMode: DeviceMode.HD;
46
+ /**
47
+ * The xpub of the HD key
48
+ */
49
+ xpub: string;
50
+ /**
51
+ * The derivation path of the HD key
52
+ */
53
+ hdPath: string;
54
+ /**
55
+ * The path used to derive child accounts
56
+ */
57
+ childrenPath: string;
58
+ };
59
+ /**
60
+ * Details for the Account mode of the Device. This mode derives
61
+ * accounts from a set of addresses and their corresponding paths.
62
+ */
63
+ export type AccountModeDeviceDetails = {
64
+ /**
65
+ * The device mode is ACCOUNT, indicating that it derives accounts from
66
+ * a set of addresses and their corresponding paths.
67
+ */
68
+ keyringMode: DeviceMode.ACCOUNT;
69
+ /**
70
+ * The derivation paths for each hex-encoded address in the device
71
+ */
72
+ paths: Record<Hex, string>;
73
+ };
74
+ /**
75
+ * An address with its corresponding index.
76
+ */
77
+ export type IndexedAddress = {
78
+ address: Hex;
79
+ index: number;
80
+ };
81
+ /**
82
+ * The details of the source CryptoAccount or CryptoHDKey
83
+ * that the Device uses to derive accounts.
84
+ */
85
+ export type DeviceDetails = CommonDeviceDetails & (HDModeDeviceDetails | AccountModeDeviceDetails);
86
+ export type DeviceOptions = {
87
+ /**
88
+ * The requestScan function to scan the QR code
89
+ */
90
+ requestScan: QrKeyringBridge['requestScan'];
91
+ /**
92
+ * The source of the device, which can be of type `DeviceDetails`,
93
+ * `string`, or `SerializedUR`.
94
+ *
95
+ * When a `string` or `SerializedUR` is provided, the Device will
96
+ * initialize itself from the UR.
97
+ */
98
+ source: DeviceDetails | string | SerializedUR;
99
+ };
100
+ export declare class Device {
101
+ #private;
102
+ /**
103
+ * Create a new Device instance.
104
+ *
105
+ * @param options - The options for the Device, including the requestScan function
106
+ * and the source of the device details.
107
+ * @param options.requestScan - The function to request a scan of the QR code.
108
+ * @param options.source - The source of the device details, which can be a
109
+ * UR string, a `SerializedUR`, or a `DeviceDetails` object.
110
+ */
111
+ constructor({ requestScan, source }: DeviceOptions);
112
+ /**
113
+ * Derive an address from the source at a given index
114
+ *
115
+ * @param index - The index to derive the address from
116
+ * @returns The derived address in hex format
117
+ * @throws Will throw an error if the source is not initialized
118
+ */
119
+ addressFromIndex(index: number): Hex;
120
+ /**
121
+ * Retrieve the path of an address derived from the source.
122
+ *
123
+ * @param address - The address to retrieve the path for
124
+ * @returns The path of the address
125
+ */
126
+ pathFromAddress(address: Hex): string;
127
+ /**
128
+ * Get a page of addresses derived from the source.
129
+ *
130
+ * @param page - The page number to retrieve
131
+ * @param pageSize - The number of addresses per page
132
+ * @returns An array of IndexedAddress objects, each containing the address and its index
133
+ * @throws Will throw an error if the source is not initialized
134
+ */
135
+ getAddressesPage(page: number, pageSize?: number): IndexedAddress[];
136
+ /**
137
+ * Retrieve the details of the paired device.
138
+ *
139
+ * @returns Thea paired device details
140
+ */
141
+ getDeviceDetails(): DeviceDetails;
142
+ /**
143
+ * Sign a transaction. This is equivalent to the `eth_signTransaction`
144
+ * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
145
+ * more details.
146
+ *
147
+ * @param address - The address of the account to use for signing.
148
+ * @param transaction - The transaction to sign.
149
+ * @returns The signed transaction.
150
+ */
151
+ signTransaction(address: Hex, transaction: TypedTransaction): Promise<TypedTxData>;
152
+ /**
153
+ * Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
154
+ * JSON-RPC method.
155
+ *
156
+ * @param address - The address of the account to use for signing.
157
+ * @param data - The data to sign.
158
+ * @returns The signed message.
159
+ */
160
+ signTypedData<Types extends MessageTypes>(address: Hex, data: TypedMessage<Types>): Promise<string>;
161
+ /**
162
+ * Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC
163
+ * method, which is exposed by MetaMask as the method `personal_sign`. See
164
+ * the Ethereum JSON-RPC API documentation for more details.
165
+ *
166
+ * For more information about this method and why we call it `personal_sign`,
167
+ * see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.
168
+ *
169
+ * @param address - The address of the account to use for signing.
170
+ * @param message - The message to sign.
171
+ * @returns The signed message.
172
+ */
173
+ signPersonalMessage(address: Hex, message: Hex): Promise<string>;
174
+ }
175
+ //# sourceMappingURL=device.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.d.mts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAGjB,uBAAuB;AAUxB,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,+BAA+B;AACzE,OAAO,EAAuC,KAAK,GAAG,EAAE,wBAAwB;AAKhF,OAAO,EAEL,KAAK,eAAe,EAEpB,KAAK,YAAY,EAClB,yBAAqB;AAEtB,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAC;AAEF,oBAAY,UAAU;IACpB,EAAE,OAAO;IACT,OAAO,YAAY;CACpB;AAMD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,WAAW,EAAE,UAAU,CAAC,EAAE,CAAC;IAC3B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAC7C,CAAC,mBAAmB,GAAG,wBAAwB,CAAC,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG;IAC1B;;OAEG;IACH,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC;CAC/C,CAAC;AAmEF,qBAAa,MAAM;;IAKjB;;;;;;;;OAQG;gBACS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,aAAa;IAQlD;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG;IA2BpC;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IA8BrC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,cAAc,EAAE;IAa9D;;;;OAIG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;;;;;;;OAQG;IACG,eAAe,CACnB,OAAO,EAAE,GAAG,EACZ,WAAW,EAAE,gBAAgB,GAC5B,OAAO,CAAC,WAAW,CAAC;IA8CvB;;;;;;;OAOG;IACG,aAAa,CAAC,KAAK,SAAS,YAAY,EAC5C,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,GACxB,OAAO,CAAC,MAAM,CAAC;IA6BlB;;;;;;;;;;;OAWG;IACG,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;CAkIvE"}
@@ -0,0 +1,346 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Device_instances, _Device_requestScan, _Device_pairedDevice, _Device_deviceDetailsFromUR, _Device_requestSignature, _Device_decodeUR;
13
+ function $importDefault(module) {
14
+ if (module?.__esModule) {
15
+ return module.default;
16
+ }
17
+ return module;
18
+ }
19
+ import { RLP } from "@ethereumjs/rlp";
20
+ import { TransactionType, TransactionFactory } from "@ethereumjs/tx";
21
+ import { publicToAddress } from "@ethereumjs/util";
22
+ import { CryptoAccount, CryptoHDKey, DataType, ETHSignature, EthSignRequest, URRegistryDecoder } from "@keystonehq/bc-ur-registry-eth";
23
+ import { add0x, getChecksumAddress, remove0x } from "@metamask/utils";
24
+ // eslint-disable-next-line @typescript-eslint/naming-convention
25
+ import $HdKey from "hdkey";
26
+ const HdKey = $importDefault($HdKey);
27
+ import { stringify, v4 as uuidv4 } from "uuid";
28
+ import { QrScanRequestType } from "./qr-keyring.mjs";
29
+ export const SUPPORTED_UR_TYPE = {
30
+ CRYPTO_HDKEY: 'crypto-hdkey',
31
+ CRYPTO_ACCOUNT: 'crypto-account',
32
+ ETH_SIGNATURE: 'eth-signature',
33
+ };
34
+ export var DeviceMode;
35
+ (function (DeviceMode) {
36
+ DeviceMode["HD"] = "hd";
37
+ DeviceMode["ACCOUNT"] = "account";
38
+ })(DeviceMode || (DeviceMode = {}));
39
+ const DEFAULT_CHILDREN_PATH = '0/*';
40
+ const MAX_INDEX = 1000;
41
+ /**
42
+ * Get the fingerprint of the source CryptoAccount or CryptoHDKey
43
+ *
44
+ * @param source - The source CryptoAccount or CryptoHDKey
45
+ * @returns The fingerprint of the source
46
+ */
47
+ function getFingerprintFromSource(source) {
48
+ return source instanceof CryptoAccount
49
+ ? source.getMasterFingerprint()?.toString('hex')
50
+ : source.getParentFingerprint()?.toString('hex');
51
+ }
52
+ /**
53
+ * Get fingerprint, account paths and names from the a CryptoAccount
54
+ *
55
+ * Note: This function emulates the behavior of the `@keystonehq/base-eth-keyring`
56
+ * library when dealing with CryptoAccount objects (for backwards compatibility
57
+ * reasons). Though, the way it retrieves `name` and `keyringAccount` is questionable,
58
+ * as `name` and `keyringAccount` are updated after each descriptor discovery, effectively
59
+ * returning the last descriptor's `name` and `keyringAccount`.
60
+ *
61
+ * @param source - The source CryptoAccount
62
+ * @returns The paths
63
+ */
64
+ function readCryptoAccountOutputDescriptors(source) {
65
+ const descriptors = source.getOutputDescriptors();
66
+ if (!descriptors || descriptors.length === 0) {
67
+ throw new Error('No output descriptors found in CryptoAccount');
68
+ }
69
+ let name = '';
70
+ let keyringAccount = '';
71
+ const paths = descriptors.reduce((descriptorsPaths, current) => {
72
+ const hdKey = current.getHDKey();
73
+ if (hdKey) {
74
+ const path = `M/${hdKey.getOrigin().getPath()}`;
75
+ const address = getChecksumAddress(add0x(Buffer.from(publicToAddress(hdKey.getKey(), true)).toString('hex')));
76
+ descriptorsPaths[address] = path;
77
+ name = hdKey.getName();
78
+ keyringAccount = hdKey.getNote();
79
+ }
80
+ return descriptorsPaths;
81
+ }, {});
82
+ return {
83
+ paths,
84
+ name,
85
+ keyringAccount,
86
+ xfp: getFingerprintFromSource(source),
87
+ };
88
+ }
89
+ export class Device {
90
+ /**
91
+ * Create a new Device instance.
92
+ *
93
+ * @param options - The options for the Device, including the requestScan function
94
+ * and the source of the device details.
95
+ * @param options.requestScan - The function to request a scan of the QR code.
96
+ * @param options.source - The source of the device details, which can be a
97
+ * UR string, a `SerializedUR`, or a `DeviceDetails` object.
98
+ */
99
+ constructor({ requestScan, source }) {
100
+ _Device_instances.add(this);
101
+ _Device_requestScan.set(this, void 0);
102
+ _Device_pairedDevice.set(this, void 0);
103
+ __classPrivateFieldSet(this, _Device_requestScan, requestScan, "f");
104
+ __classPrivateFieldSet(this, _Device_pairedDevice, typeof source === 'string' || 'cbor' in source
105
+ ? __classPrivateFieldGet(this, _Device_instances, "m", _Device_deviceDetailsFromUR).call(this, source)
106
+ : source, "f");
107
+ }
108
+ /**
109
+ * Derive an address from the source at a given index
110
+ *
111
+ * @param index - The index to derive the address from
112
+ * @returns The derived address in hex format
113
+ * @throws Will throw an error if the source is not initialized
114
+ */
115
+ addressFromIndex(index) {
116
+ if (__classPrivateFieldGet(this, _Device_pairedDevice, "f").keyringMode === DeviceMode.ACCOUNT) {
117
+ const address = Object.keys(__classPrivateFieldGet(this, _Device_pairedDevice, "f").paths)[index];
118
+ if (!address) {
119
+ throw new Error(`Address not found for index ${index}`);
120
+ }
121
+ return add0x(address);
122
+ }
123
+ const childPath = `m/${__classPrivateFieldGet(this, _Device_pairedDevice, "f").childrenPath.replace('*', index.toString())}`;
124
+ const hdKey = HdKey.fromExtendedKey(__classPrivateFieldGet(this, _Device_pairedDevice, "f").xpub);
125
+ const childKey = hdKey.derive(childPath);
126
+ const address = Buffer.from(publicToAddress(childKey.publicKey, true)).toString('hex');
127
+ const normalizedAddress = getChecksumAddress(add0x(address));
128
+ __classPrivateFieldGet(this, _Device_pairedDevice, "f").indexes[normalizedAddress] = index;
129
+ return normalizedAddress;
130
+ }
131
+ /**
132
+ * Retrieve the path of an address derived from the source.
133
+ *
134
+ * @param address - The address to retrieve the path for
135
+ * @returns The path of the address
136
+ */
137
+ pathFromAddress(address) {
138
+ const normalizedAddress = getChecksumAddress(add0x(address));
139
+ if (__classPrivateFieldGet(this, _Device_pairedDevice, "f").keyringMode === DeviceMode.ACCOUNT) {
140
+ const path = __classPrivateFieldGet(this, _Device_pairedDevice, "f").paths[normalizedAddress];
141
+ if (path === undefined) {
142
+ throw new Error(`Unknown address ${normalizedAddress}`);
143
+ }
144
+ return path;
145
+ }
146
+ let index = __classPrivateFieldGet(this, _Device_pairedDevice, "f").indexes[normalizedAddress];
147
+ if (index === undefined) {
148
+ for (let i = 0; i < MAX_INDEX; i++) {
149
+ const derivedAddress = this.addressFromIndex(i);
150
+ if (derivedAddress === normalizedAddress) {
151
+ index = i;
152
+ break;
153
+ }
154
+ }
155
+ if (index === undefined) {
156
+ throw new Error(`Unknown address ${normalizedAddress}`);
157
+ }
158
+ }
159
+ return `${__classPrivateFieldGet(this, _Device_pairedDevice, "f").hdPath}/${__classPrivateFieldGet(this, _Device_pairedDevice, "f").childrenPath
160
+ .replace('*', index.toString())
161
+ .replace(/\*/gu, '0')}`;
162
+ }
163
+ /**
164
+ * Get a page of addresses derived from the source.
165
+ *
166
+ * @param page - The page number to retrieve
167
+ * @param pageSize - The number of addresses per page
168
+ * @returns An array of IndexedAddress objects, each containing the address and its index
169
+ * @throws Will throw an error if the source is not initialized
170
+ */
171
+ getAddressesPage(page, pageSize = 5) {
172
+ const startIndex = page * pageSize;
173
+ const endIndex = startIndex + pageSize;
174
+ const addresses = [];
175
+ for (let i = startIndex; i < endIndex; i++) {
176
+ const address = this.addressFromIndex(i);
177
+ addresses.push({ address, index: i });
178
+ }
179
+ return addresses;
180
+ }
181
+ /**
182
+ * Retrieve the details of the paired device.
183
+ *
184
+ * @returns Thea paired device details
185
+ */
186
+ getDeviceDetails() {
187
+ return __classPrivateFieldGet(this, _Device_pairedDevice, "f");
188
+ }
189
+ /**
190
+ * Sign a transaction. This is equivalent to the `eth_signTransaction`
191
+ * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
192
+ * more details.
193
+ *
194
+ * @param address - The address of the account to use for signing.
195
+ * @param transaction - The transaction to sign.
196
+ * @returns The signed transaction.
197
+ */
198
+ async signTransaction(address, transaction) {
199
+ const dataType = transaction.type === TransactionType.Legacy
200
+ ? DataType.transaction
201
+ : DataType.typedTransaction;
202
+ const messageToSign = Buffer.from(transaction.type === TransactionType.Legacy
203
+ ? RLP.encode(transaction.getMessageToSign())
204
+ : transaction.getMessageToSign());
205
+ const requestId = uuidv4();
206
+ const ethSignRequestUR = EthSignRequest.constructETHRequest(messageToSign, dataType, this.pathFromAddress(address), __classPrivateFieldGet(this, _Device_pairedDevice, "f").xfp, requestId, Number(transaction.common.chainId())).toUR();
207
+ const { r, s, v } = await __classPrivateFieldGet(this, _Device_instances, "m", _Device_requestSignature).call(this, {
208
+ requestId,
209
+ payload: {
210
+ type: ethSignRequestUR.type,
211
+ cbor: ethSignRequestUR.cbor.toString('hex'),
212
+ },
213
+ requestTitle: 'Scan with your hardware wallet',
214
+ requestDescription: 'After your device has signed this message, click on "Scan" to receive the signature',
215
+ });
216
+ return TransactionFactory.fromTxData({
217
+ ...transaction.toJSON(),
218
+ r,
219
+ s,
220
+ v,
221
+ }, {
222
+ common: transaction.common,
223
+ });
224
+ }
225
+ /**
226
+ * Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
227
+ * JSON-RPC method.
228
+ *
229
+ * @param address - The address of the account to use for signing.
230
+ * @param data - The data to sign.
231
+ * @returns The signed message.
232
+ */
233
+ async signTypedData(address, data) {
234
+ const requestId = uuidv4();
235
+ const ethSignRequestUR = EthSignRequest.constructETHRequest(Buffer.from(JSON.stringify(data), 'utf8'), DataType.typedData, this.pathFromAddress(address), __classPrivateFieldGet(this, _Device_pairedDevice, "f").xfp, requestId, undefined, address).toUR();
236
+ const { r, s, v } = await __classPrivateFieldGet(this, _Device_instances, "m", _Device_requestSignature).call(this, {
237
+ requestId,
238
+ payload: {
239
+ type: ethSignRequestUR.type,
240
+ cbor: ethSignRequestUR.cbor.toString('hex'),
241
+ },
242
+ });
243
+ return add0x(Buffer.concat([
244
+ Uint8Array.from(r),
245
+ Uint8Array.from(s),
246
+ Uint8Array.from(v),
247
+ ]).toString('hex'));
248
+ }
249
+ /**
250
+ * Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC
251
+ * method, which is exposed by MetaMask as the method `personal_sign`. See
252
+ * the Ethereum JSON-RPC API documentation for more details.
253
+ *
254
+ * For more information about this method and why we call it `personal_sign`,
255
+ * see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.
256
+ *
257
+ * @param address - The address of the account to use for signing.
258
+ * @param message - The message to sign.
259
+ * @returns The signed message.
260
+ */
261
+ async signPersonalMessage(address, message) {
262
+ const requestId = uuidv4();
263
+ const ethSignRequestUR = EthSignRequest.constructETHRequest(Buffer.from(remove0x(message), 'hex'), DataType.personalMessage, this.pathFromAddress(address), __classPrivateFieldGet(this, _Device_pairedDevice, "f").xfp, requestId, undefined, address).toUR();
264
+ const { r, s, v } = await __classPrivateFieldGet(this, _Device_instances, "m", _Device_requestSignature).call(this, {
265
+ requestId,
266
+ payload: {
267
+ type: ethSignRequestUR.type,
268
+ cbor: ethSignRequestUR.cbor.toString('hex'),
269
+ },
270
+ });
271
+ return add0x(Buffer.concat([
272
+ Uint8Array.from(r),
273
+ Uint8Array.from(s),
274
+ Uint8Array.from(v),
275
+ ]).toString('hex'));
276
+ }
277
+ }
278
+ _Device_requestScan = new WeakMap(), _Device_pairedDevice = new WeakMap(), _Device_instances = new WeakSet(), _Device_deviceDetailsFromUR = function _Device_deviceDetailsFromUR(ur) {
279
+ const source = __classPrivateFieldGet(this, _Device_instances, "m", _Device_decodeUR).call(this, ur);
280
+ const fingerprint = getFingerprintFromSource(source);
281
+ if (source instanceof CryptoAccount) {
282
+ const { name, xfp, paths, keyringAccount } = readCryptoAccountOutputDescriptors(source);
283
+ return {
284
+ keyringMode: DeviceMode.ACCOUNT,
285
+ keyringAccount,
286
+ name,
287
+ xfp,
288
+ paths,
289
+ indexes: {},
290
+ };
291
+ }
292
+ const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;
293
+ return {
294
+ keyringMode: DeviceMode.HD,
295
+ keyringAccount: getNote(),
296
+ name: getName(),
297
+ xfp: fingerprint,
298
+ hdPath: `m/${getOrigin().getPath()}`,
299
+ childrenPath: getChildren()?.getPath() || DEFAULT_CHILDREN_PATH,
300
+ xpub: getBip32Key(),
301
+ indexes: {},
302
+ };
303
+ }, _Device_requestSignature =
304
+ /**
305
+ * Request a signature for a transaction or message.
306
+ *
307
+ * @param request - The signature request containing the data to sign.
308
+ * @returns The signature as an object containing r, s, and v values.
309
+ */
310
+ async function _Device_requestSignature(request) {
311
+ const response = await __classPrivateFieldGet(this, _Device_requestScan, "f").call(this, {
312
+ type: QrScanRequestType.SIGN,
313
+ request,
314
+ });
315
+ const signatureEnvelope = ETHSignature.fromCBOR(Buffer.from(response.cbor, 'hex'));
316
+ const signature = signatureEnvelope.getSignature();
317
+ const requestId = signatureEnvelope.getRequestId();
318
+ if (!requestId) {
319
+ throw new Error('Signature request ID is missing.');
320
+ }
321
+ if (request.requestId !== stringify(requestId)) {
322
+ throw new Error(`Signature request ID mismatch. Expected: ${request.requestId}, received: ${requestId.toString('hex')}`);
323
+ }
324
+ return {
325
+ r: signature.subarray(0, 32),
326
+ s: signature.subarray(32, 64),
327
+ v: signature.subarray(64),
328
+ };
329
+ }, _Device_decodeUR = function _Device_decodeUR(ur) {
330
+ const decodedUR = typeof ur === 'string'
331
+ ? URRegistryDecoder.decode(ur)
332
+ : {
333
+ type: ur.type,
334
+ cbor: Buffer.from(ur.cbor, 'hex'),
335
+ };
336
+ const { type, cbor } = decodedUR;
337
+ switch (type) {
338
+ case SUPPORTED_UR_TYPE.CRYPTO_HDKEY:
339
+ return CryptoHDKey.fromCBOR(cbor);
340
+ case SUPPORTED_UR_TYPE.CRYPTO_ACCOUNT:
341
+ return CryptoAccount.fromCBOR(cbor);
342
+ default:
343
+ throw new Error('Unsupported UR type');
344
+ }
345
+ };
346
+ //# sourceMappingURL=device.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.mjs","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,GAAG,EAAE,wBAAwB;AACtC,OAAO,EAEL,eAAe,EAGf,kBAAkB,EACnB,uBAAuB;AACxB,OAAO,EAAE,eAAe,EAAE,yBAAyB;AACnD,OAAO,EACL,aAAa,EACb,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,iBAAiB,EAClB,uCAAuC;AAExC,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAY,wBAAwB;AAChF,gEAAgE;AAChE,OAAO,MAAK,cAAc;;AAC1B,OAAO,EAAE,SAAS,EAAE,EAAE,IAAI,MAAM,EAAE,aAAa;AAE/C,OAAO,EACL,iBAAiB,EAIlB,yBAAqB;AAEtB,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,cAAc;IAC5B,cAAc,EAAE,gBAAgB;IAChC,aAAa,EAAE,eAAe;CAC/B,CAAC;AAEF,MAAM,CAAN,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,uBAAS,CAAA;IACT,iCAAmB,CAAA;AACrB,CAAC,EAHW,UAAU,KAAV,UAAU,QAGrB;AAED,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAK,CAAC;AA+FxB;;;;;GAKG;AACH,SAAS,wBAAwB,CAAC,MAAmC;IACnE,OAAO,MAAM,YAAY,aAAa;QACpC,CAAC,CAAC,MAAM,CAAC,oBAAoB,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC;QAChD,CAAC,CAAC,MAAM,CAAC,oBAAoB,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,kCAAkC,CAAC,MAAqB;IAM/D,MAAM,WAAW,GAAG,MAAM,CAAC,oBAAoB,EAAE,CAAC;IAElD,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,cAAc,GAAG,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAC9B,CAAC,gBAAqC,EAAE,OAAO,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;YAChD,MAAM,OAAO,GAAG,kBAAkB,CAChC,KAAK,CACH,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CACnE,CACF,CAAC;YACF,gBAAgB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;YACjC,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YACvB,cAAc,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,gBAAgB,CAAC;IAC1B,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO;QACL,KAAK;QACL,IAAI;QACJ,cAAc;QACd,GAAG,EAAE,wBAAwB,CAAC,MAAM,CAAC;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,MAAM;IAKjB;;;;;;;;OAQG;IACH,YAAY,EAAE,WAAW,EAAE,MAAM,EAAiB;;QAbzC,sCAA6C;QAE7C,uCAA6B;QAYpC,uBAAA,IAAI,uBAAgB,WAAW,MAAA,CAAC;QAChC,uBAAA,IAAI,wBACF,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,MAAM;YAC5C,CAAC,CAAC,uBAAA,IAAI,sDAAqB,MAAzB,IAAI,EAAsB,MAAM,CAAC;YACnC,CAAC,CAAC,MAAM,MAAA,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAa;QAC5B,IAAI,uBAAA,IAAI,4BAAc,CAAC,WAAW,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;YAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,uBAAA,IAAI,4BAAc,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;YAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QACD,MAAM,SAAS,GAAG,KAAK,uBAAA,IAAI,4BAAc,CAAC,YAAY,CAAC,OAAO,CAC5D,GAAG,EACH,KAAK,CAAC,QAAQ,EAAE,CACjB,EAAE,CAAC;QAEJ,MAAM,KAAK,GAAG,KAAK,CAAC,eAAe,CAAC,uBAAA,IAAI,4BAAc,CAAC,IAAI,CAAC,CAAC;QAC7D,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAEzC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CACzB,eAAe,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAC1C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAElB,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAE7D,uBAAA,IAAI,4BAAc,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,KAAK,CAAC;QAEtD,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,OAAY;QAC1B,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAE7D,IAAI,uBAAA,IAAI,4BAAc,CAAC,WAAW,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;YAC1D,MAAM,IAAI,GAAG,uBAAA,IAAI,4BAAc,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACzD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,mBAAmB,iBAAiB,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,KAAK,GAAG,uBAAA,IAAI,4BAAc,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC1D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;gBACnC,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;gBAChD,IAAI,cAAc,KAAK,iBAAiB,EAAE,CAAC;oBACzC,KAAK,GAAG,CAAC,CAAC;oBACV,MAAM;gBACR,CAAC;YACH,CAAC;YACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CAAC,mBAAmB,iBAAiB,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;QAED,OAAO,GAAG,uBAAA,IAAI,4BAAc,CAAC,MAAM,IAAI,uBAAA,IAAI,4BAAc,CAAC,YAAY;aACnE,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;aAC9B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;IAC5B,CAAC;IAED;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAY,EAAE,QAAQ,GAAG,CAAC;QACzC,MAAM,UAAU,GAAG,IAAI,GAAG,QAAQ,CAAC;QACnC,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;QACvC,MAAM,SAAS,GAAqB,EAAE,CAAC;QAEvC,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACzC,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,gBAAgB;QACd,OAAO,uBAAA,IAAI,4BAAc,CAAC;IAC5B,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,eAAe,CACnB,OAAY,EACZ,WAA6B;QAE7B,MAAM,QAAQ,GACZ,WAAW,CAAC,IAAI,KAAK,eAAe,CAAC,MAAM;YACzC,CAAC,CAAC,QAAQ,CAAC,WAAW;YACtB,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAEhC,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAC/B,WAAW,CAAC,IAAI,KAAK,eAAe,CAAC,MAAM;YACzC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC;YAC5C,CAAC,CAAE,WAA2C,CAAC,gBAAgB,EAAE,CACpE,CAAC;QAEF,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC;QAC3B,MAAM,gBAAgB,GAAG,cAAc,CAAC,mBAAmB,CACzD,aAAa,EACb,QAAQ,EACR,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAC7B,uBAAA,IAAI,4BAAc,CAAC,GAAG,EACtB,SAAS,EACT,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CACrC,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,uBAAA,IAAI,mDAAkB,MAAtB,IAAI,EAAmB;YAC/C,SAAS;YACT,OAAO,EAAE;gBACP,IAAI,EAAE,gBAAgB,CAAC,IAAI;gBAC3B,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;aAC5C;YACD,YAAY,EAAE,gCAAgC;YAC9C,kBAAkB,EAChB,qFAAqF;SACxF,CAAC,CAAC;QAEH,OAAO,kBAAkB,CAAC,UAAU,CAClC;YACE,GAAG,WAAW,CAAC,MAAM,EAAE;YACvB,CAAC;YACD,CAAC;YACD,CAAC;SACF,EACD;YACE,MAAM,EAAE,WAAW,CAAC,MAAM;SAC3B,CACF,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa,CACjB,OAAY,EACZ,IAAyB;QAEzB,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC;QAC3B,MAAM,gBAAgB,GAAG,cAAc,CAAC,mBAAmB,CACzD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EACzC,QAAQ,CAAC,SAAS,EAClB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAC7B,uBAAA,IAAI,4BAAc,CAAC,GAAG,EACtB,SAAS,EACT,SAAS,EACT,OAAO,CACR,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,uBAAA,IAAI,mDAAkB,MAAtB,IAAI,EAAmB;YAC/C,SAAS;YACT,OAAO,EAAE;gBACP,IAAI,EAAE,gBAAgB,CAAC,IAAI;gBAC3B,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;aAC5C;SACF,CAAC,CAAC;QAEH,OAAO,KAAK,CACV,MAAM,CAAC,MAAM,CAAC;YACZ,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;SACnB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CACnB,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,mBAAmB,CAAC,OAAY,EAAE,OAAY;QAClD,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC;QAC3B,MAAM,gBAAgB,GAAG,cAAc,CAAC,mBAAmB,CACzD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,EACrC,QAAQ,CAAC,eAAe,EACxB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAC7B,uBAAA,IAAI,4BAAc,CAAC,GAAG,EACtB,SAAS,EACT,SAAS,EACT,OAAO,CACR,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,uBAAA,IAAI,mDAAkB,MAAtB,IAAI,EAAmB;YAC/C,SAAS;YACT,OAAO,EAAE;gBACP,IAAI,EAAE,gBAAgB,CAAC,IAAI;gBAC3B,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;aAC5C;SACF,CAAC,CAAC;QAEH,OAAO,KAAK,CACV,MAAM,CAAC,MAAM,CAAC;YACZ,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;SACnB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CACnB,CAAC;IACJ,CAAC;CAuGF;iLA/FsB,EAAyB;IAC5C,MAAM,MAAM,GAAG,uBAAA,IAAI,2CAAU,MAAd,IAAI,EAAW,EAAE,CAAC,CAAC;IAClC,MAAM,WAAW,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAErD,IAAI,MAAM,YAAY,aAAa,EAAE,CAAC;QACpC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,GACxC,kCAAkC,CAAC,MAAM,CAAC,CAAC;QAC7C,OAAO;YACL,WAAW,EAAE,UAAU,CAAC,OAAO;YAC/B,cAAc;YACd,IAAI;YACJ,GAAG;YACH,KAAK;YACL,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IACzE,OAAO;QACL,WAAW,EAAE,UAAU,CAAC,EAAE;QAC1B,cAAc,EAAE,OAAO,EAAE;QACzB,IAAI,EAAE,OAAO,EAAE;QACf,GAAG,EAAE,WAAW;QAChB,MAAM,EAAE,KAAK,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;QACpC,YAAY,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,IAAI,qBAAqB;QAC/D,IAAI,EAAE,WAAW,EAAE;QACnB,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,mCACH,OAA2B;IAE3B,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,2BAAa,MAAjB,IAAI,EAAc;QACvC,IAAI,EAAE,iBAAiB,CAAC,IAAI;QAC5B,OAAO;KACR,CAAC,CAAC;IACH,MAAM,iBAAiB,GAAG,YAAY,CAAC,QAAQ,CAC7C,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAClC,CAAC;IACF,MAAM,SAAS,GAAG,iBAAiB,CAAC,YAAY,EAAE,CAAC;IACnD,MAAM,SAAS,GAAG,iBAAiB,CAAC,YAAY,EAAE,CAAC;IAEnD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,4CACE,OAAO,CAAC,SACV,eAAe,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAC3C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC;QAC5B,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;QAC7B,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;KAC1B,CAAC;AACJ,CAAC,+CASS,EAAyB;IACjC,MAAM,SAAS,GACb,OAAO,EAAE,KAAK,QAAQ;QACpB,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,CAAC,CAAC;YACE,IAAI,EAAE,EAAE,CAAC,IAAI;YACb,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;SAClC,CAAC;IAER,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAEjC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,iBAAiB,CAAC,YAAY;YACjC,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,KAAK,iBAAiB,CAAC,cAAc;YACnC,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtC;YACE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC","sourcesContent":["import { RLP } from '@ethereumjs/rlp';\nimport {\n type TypedTransaction,\n TransactionType,\n type TypedTxData,\n type FeeMarketEIP1559Transaction,\n TransactionFactory,\n} from '@ethereumjs/tx';\nimport { publicToAddress } from '@ethereumjs/util';\nimport {\n CryptoAccount,\n CryptoHDKey,\n DataType,\n ETHSignature,\n EthSignRequest,\n URRegistryDecoder,\n} from '@keystonehq/bc-ur-registry-eth';\nimport type { MessageTypes, TypedMessage } from '@metamask/eth-sig-util';\nimport { add0x, getChecksumAddress, remove0x, type Hex } from '@metamask/utils';\n// eslint-disable-next-line @typescript-eslint/naming-convention\nimport HdKey from 'hdkey';\nimport { stringify, v4 as uuidv4 } from 'uuid';\n\nimport {\n QrScanRequestType,\n type QrKeyringBridge,\n type QrSignatureRequest,\n type SerializedUR,\n} from './qr-keyring';\n\nexport const SUPPORTED_UR_TYPE = {\n CRYPTO_HDKEY: 'crypto-hdkey',\n CRYPTO_ACCOUNT: 'crypto-account',\n ETH_SIGNATURE: 'eth-signature',\n};\n\nexport enum DeviceMode {\n HD = 'hd',\n ACCOUNT = 'account',\n}\n\nconst DEFAULT_CHILDREN_PATH = '0/*';\n\nconst MAX_INDEX = 1_000;\n\n/**\n * Common details for the device source, which can be either a CryptoAccount or CryptoHDKey.\n */\nexport type CommonDeviceDetails = {\n /**\n * Value take out from the device note field, if available.\n */\n keyringAccount: string;\n /**\n * The name of the device\n */\n name: string;\n /**\n * The device fingerprint, hex-encoded\n */\n xfp: string;\n /**\n * Indexes of the accounts derived from the device\n * in the form of a map from address to index\n */\n indexes: Record<Hex, number>;\n};\n\n/**\n * Details for the HD mode of the Device. This mode derives\n * accounts from a root public key (xpub) and a derivation path.\n */\nexport type HDModeDeviceDetails = {\n /**\n * The device mode is HD, indicating that it derives accounts from a\n * root public key (xpub) and a derivation path.\n */\n keyringMode: DeviceMode.HD;\n /**\n * The xpub of the HD key\n */\n xpub: string;\n /**\n * The derivation path of the HD key\n */\n hdPath: string;\n /**\n * The path used to derive child accounts\n */\n childrenPath: string;\n};\n\n/**\n * Details for the Account mode of the Device. This mode derives\n * accounts from a set of addresses and their corresponding paths.\n */\nexport type AccountModeDeviceDetails = {\n /**\n * The device mode is ACCOUNT, indicating that it derives accounts from\n * a set of addresses and their corresponding paths.\n */\n keyringMode: DeviceMode.ACCOUNT;\n /**\n * The derivation paths for each hex-encoded address in the device\n */\n paths: Record<Hex, string>;\n};\n\n/**\n * An address with its corresponding index.\n */\nexport type IndexedAddress = {\n address: Hex;\n index: number;\n};\n\n/**\n * The details of the source CryptoAccount or CryptoHDKey\n * that the Device uses to derive accounts.\n */\nexport type DeviceDetails = CommonDeviceDetails &\n (HDModeDeviceDetails | AccountModeDeviceDetails);\n\nexport type DeviceOptions = {\n /**\n * The requestScan function to scan the QR code\n */\n requestScan: QrKeyringBridge['requestScan'];\n /**\n * The source of the device, which can be of type `DeviceDetails`,\n * `string`, or `SerializedUR`.\n *\n * When a `string` or `SerializedUR` is provided, the Device will\n * initialize itself from the UR.\n */\n source: DeviceDetails | string | SerializedUR;\n};\n\n/**\n * Get the fingerprint of the source CryptoAccount or CryptoHDKey\n *\n * @param source - The source CryptoAccount or CryptoHDKey\n * @returns The fingerprint of the source\n */\nfunction getFingerprintFromSource(source: CryptoAccount | CryptoHDKey): string {\n return source instanceof CryptoAccount\n ? source.getMasterFingerprint()?.toString('hex')\n : source.getParentFingerprint()?.toString('hex');\n}\n\n/**\n * Get fingerprint, account paths and names from the a CryptoAccount\n *\n * Note: This function emulates the behavior of the `@keystonehq/base-eth-keyring`\n * library when dealing with CryptoAccount objects (for backwards compatibility\n * reasons). Though, the way it retrieves `name` and `keyringAccount` is questionable,\n * as `name` and `keyringAccount` are updated after each descriptor discovery, effectively\n * returning the last descriptor's `name` and `keyringAccount`.\n *\n * @param source - The source CryptoAccount\n * @returns The paths\n */\nfunction readCryptoAccountOutputDescriptors(source: CryptoAccount): {\n xfp: string;\n paths: Record<Hex, string>;\n name: string;\n keyringAccount: string;\n} {\n const descriptors = source.getOutputDescriptors();\n\n if (!descriptors || descriptors.length === 0) {\n throw new Error('No output descriptors found in CryptoAccount');\n }\n\n let name = '';\n let keyringAccount = '';\n const paths = descriptors.reduce(\n (descriptorsPaths: Record<Hex, string>, current) => {\n const hdKey = current.getHDKey();\n if (hdKey) {\n const path = `M/${hdKey.getOrigin().getPath()}`;\n const address = getChecksumAddress(\n add0x(\n Buffer.from(publicToAddress(hdKey.getKey(), true)).toString('hex'),\n ),\n );\n descriptorsPaths[address] = path;\n name = hdKey.getName();\n keyringAccount = hdKey.getNote();\n }\n return descriptorsPaths;\n },\n {},\n );\n\n return {\n paths,\n name,\n keyringAccount,\n xfp: getFingerprintFromSource(source),\n };\n}\n\nexport class Device {\n readonly #requestScan: QrKeyringBridge['requestScan'];\n\n readonly #pairedDevice: DeviceDetails;\n\n /**\n * Create a new Device instance.\n *\n * @param options - The options for the Device, including the requestScan function\n * and the source of the device details.\n * @param options.requestScan - The function to request a scan of the QR code.\n * @param options.source - The source of the device details, which can be a\n * UR string, a `SerializedUR`, or a `DeviceDetails` object.\n */\n constructor({ requestScan, source }: DeviceOptions) {\n this.#requestScan = requestScan;\n this.#pairedDevice =\n typeof source === 'string' || 'cbor' in source\n ? this.#deviceDetailsFromUR(source)\n : source;\n }\n\n /**\n * Derive an address from the source at a given index\n *\n * @param index - The index to derive the address from\n * @returns The derived address in hex format\n * @throws Will throw an error if the source is not initialized\n */\n addressFromIndex(index: number): Hex {\n if (this.#pairedDevice.keyringMode === DeviceMode.ACCOUNT) {\n const address = Object.keys(this.#pairedDevice.paths)[index];\n if (!address) {\n throw new Error(`Address not found for index ${index}`);\n }\n return add0x(address);\n }\n const childPath = `m/${this.#pairedDevice.childrenPath.replace(\n '*',\n index.toString(),\n )}`;\n\n const hdKey = HdKey.fromExtendedKey(this.#pairedDevice.xpub);\n const childKey = hdKey.derive(childPath);\n\n const address = Buffer.from(\n publicToAddress(childKey.publicKey, true),\n ).toString('hex');\n\n const normalizedAddress = getChecksumAddress(add0x(address));\n\n this.#pairedDevice.indexes[normalizedAddress] = index;\n\n return normalizedAddress;\n }\n\n /**\n * Retrieve the path of an address derived from the source.\n *\n * @param address - The address to retrieve the path for\n * @returns The path of the address\n */\n pathFromAddress(address: Hex): string {\n const normalizedAddress = getChecksumAddress(add0x(address));\n\n if (this.#pairedDevice.keyringMode === DeviceMode.ACCOUNT) {\n const path = this.#pairedDevice.paths[normalizedAddress];\n if (path === undefined) {\n throw new Error(`Unknown address ${normalizedAddress}`);\n }\n return path;\n }\n\n let index = this.#pairedDevice.indexes[normalizedAddress];\n if (index === undefined) {\n for (let i = 0; i < MAX_INDEX; i++) {\n const derivedAddress = this.addressFromIndex(i);\n if (derivedAddress === normalizedAddress) {\n index = i;\n break;\n }\n }\n if (index === undefined) {\n throw new Error(`Unknown address ${normalizedAddress}`);\n }\n }\n\n return `${this.#pairedDevice.hdPath}/${this.#pairedDevice.childrenPath\n .replace('*', index.toString())\n .replace(/\\*/gu, '0')}`;\n }\n\n /**\n * Get a page of addresses derived from the source.\n *\n * @param page - The page number to retrieve\n * @param pageSize - The number of addresses per page\n * @returns An array of IndexedAddress objects, each containing the address and its index\n * @throws Will throw an error if the source is not initialized\n */\n getAddressesPage(page: number, pageSize = 5): IndexedAddress[] {\n const startIndex = page * pageSize;\n const endIndex = startIndex + pageSize;\n const addresses: IndexedAddress[] = [];\n\n for (let i = startIndex; i < endIndex; i++) {\n const address = this.addressFromIndex(i);\n addresses.push({ address, index: i });\n }\n\n return addresses;\n }\n\n /**\n * Retrieve the details of the paired device.\n *\n * @returns Thea paired device details\n */\n getDeviceDetails(): DeviceDetails {\n return this.#pairedDevice;\n }\n\n /**\n * Sign a transaction. This is equivalent to the `eth_signTransaction`\n * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for\n * more details.\n *\n * @param address - The address of the account to use for signing.\n * @param transaction - The transaction to sign.\n * @returns The signed transaction.\n */\n async signTransaction(\n address: Hex,\n transaction: TypedTransaction,\n ): Promise<TypedTxData> {\n const dataType =\n transaction.type === TransactionType.Legacy\n ? DataType.transaction\n : DataType.typedTransaction;\n\n const messageToSign = Buffer.from(\n transaction.type === TransactionType.Legacy\n ? RLP.encode(transaction.getMessageToSign())\n : (transaction as FeeMarketEIP1559Transaction).getMessageToSign(),\n );\n\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n messageToSign,\n dataType,\n this.pathFromAddress(address),\n this.#pairedDevice.xfp,\n requestId,\n Number(transaction.common.chainId()),\n ).toUR();\n\n const { r, s, v } = await this.#requestSignature({\n requestId,\n payload: {\n type: ethSignRequestUR.type,\n cbor: ethSignRequestUR.cbor.toString('hex'),\n },\n requestTitle: 'Scan with your hardware wallet',\n requestDescription:\n 'After your device has signed this message, click on \"Scan\" to receive the signature',\n });\n\n return TransactionFactory.fromTxData(\n {\n ...transaction.toJSON(),\n r,\n s,\n v,\n },\n {\n common: transaction.common,\n },\n );\n }\n\n /**\n * Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum\n * JSON-RPC method.\n *\n * @param address - The address of the account to use for signing.\n * @param data - The data to sign.\n * @returns The signed message.\n */\n async signTypedData<Types extends MessageTypes>(\n address: Hex,\n data: TypedMessage<Types>,\n ): Promise<string> {\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n Buffer.from(JSON.stringify(data), 'utf8'),\n DataType.typedData,\n this.pathFromAddress(address),\n this.#pairedDevice.xfp,\n requestId,\n undefined,\n address,\n ).toUR();\n\n const { r, s, v } = await this.#requestSignature({\n requestId,\n payload: {\n type: ethSignRequestUR.type,\n cbor: ethSignRequestUR.cbor.toString('hex'),\n },\n });\n\n return add0x(\n Buffer.concat([\n Uint8Array.from(r),\n Uint8Array.from(s),\n Uint8Array.from(v),\n ]).toString('hex'),\n );\n }\n\n /**\n * Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC\n * method, which is exposed by MetaMask as the method `personal_sign`. See\n * the Ethereum JSON-RPC API documentation for more details.\n *\n * For more information about this method and why we call it `personal_sign`,\n * see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.\n *\n * @param address - The address of the account to use for signing.\n * @param message - The message to sign.\n * @returns The signed message.\n */\n async signPersonalMessage(address: Hex, message: Hex): Promise<string> {\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n Buffer.from(remove0x(message), 'hex'),\n DataType.personalMessage,\n this.pathFromAddress(address),\n this.#pairedDevice.xfp,\n requestId,\n undefined,\n address,\n ).toUR();\n\n const { r, s, v } = await this.#requestSignature({\n requestId,\n payload: {\n type: ethSignRequestUR.type,\n cbor: ethSignRequestUR.cbor.toString('hex'),\n },\n });\n\n return add0x(\n Buffer.concat([\n Uint8Array.from(r),\n Uint8Array.from(s),\n Uint8Array.from(v),\n ]).toString('hex'),\n );\n }\n\n /**\n * Derive the device details from a UR string\n *\n * @param ur - The UR string to set the root account from\n * @returns The device details derived from the UR\n */\n #deviceDetailsFromUR(ur: string | SerializedUR): DeviceDetails {\n const source = this.#decodeUR(ur);\n const fingerprint = getFingerprintFromSource(source);\n\n if (source instanceof CryptoAccount) {\n const { name, xfp, paths, keyringAccount } =\n readCryptoAccountOutputDescriptors(source);\n return {\n keyringMode: DeviceMode.ACCOUNT,\n keyringAccount,\n name,\n xfp,\n paths,\n indexes: {},\n };\n }\n\n const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;\n return {\n keyringMode: DeviceMode.HD,\n keyringAccount: getNote(),\n name: getName(),\n xfp: fingerprint,\n hdPath: `m/${getOrigin().getPath()}`,\n childrenPath: getChildren()?.getPath() || DEFAULT_CHILDREN_PATH,\n xpub: getBip32Key(),\n indexes: {},\n };\n }\n\n /**\n * Request a signature for a transaction or message.\n *\n * @param request - The signature request containing the data to sign.\n * @returns The signature as an object containing r, s, and v values.\n */\n async #requestSignature(\n request: QrSignatureRequest,\n ): Promise<{ r: Buffer; s: Buffer; v: Buffer }> {\n const response = await this.#requestScan({\n type: QrScanRequestType.SIGN,\n request,\n });\n const signatureEnvelope = ETHSignature.fromCBOR(\n Buffer.from(response.cbor, 'hex'),\n );\n const signature = signatureEnvelope.getSignature();\n const requestId = signatureEnvelope.getRequestId();\n\n if (!requestId) {\n throw new Error('Signature request ID is missing.');\n }\n\n if (request.requestId !== stringify(requestId)) {\n throw new Error(\n `Signature request ID mismatch. Expected: ${\n request.requestId\n }, received: ${requestId.toString('hex')}`,\n );\n }\n\n return {\n r: signature.subarray(0, 32),\n s: signature.subarray(32, 64),\n v: signature.subarray(64),\n };\n }\n\n /**\n * Decodes a UR\n *\n * @param ur - The UR to decode\n * @returns The decoded CryptoAccount or CryptoHDKey\n * @throws Will throw an error if the UR type is not supported\n */\n #decodeUR(ur: string | SerializedUR): CryptoAccount | CryptoHDKey {\n const decodedUR =\n typeof ur === 'string'\n ? URRegistryDecoder.decode(ur)\n : {\n type: ur.type,\n cbor: Buffer.from(ur.cbor, 'hex'),\n };\n\n const { type, cbor } = decodedUR;\n\n switch (type) {\n case SUPPORTED_UR_TYPE.CRYPTO_HDKEY:\n return CryptoHDKey.fromCBOR(cbor);\n case SUPPORTED_UR_TYPE.CRYPTO_ACCOUNT:\n return CryptoAccount.fromCBOR(cbor);\n default:\n throw new Error('Unsupported UR type');\n }\n }\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./qr-keyring-scanner-bridge.cjs"), exports);
18
+ __exportStar(require("./qr-keyring.cjs"), exports);
19
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kEAA4C;AAC5C,mDAA6B","sourcesContent":["export * from './qr-keyring-scanner-bridge';\nexport * from './qr-keyring';\n"]}
@@ -0,0 +1,3 @@
1
+ export * from "./qr-keyring-scanner-bridge.cjs";
2
+ export * from "./qr-keyring.cjs";
3
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAA4C;AAC5C,iCAA6B"}
@@ -0,0 +1,3 @@
1
+ export * from "./qr-keyring-scanner-bridge.mjs";
2
+ export * from "./qr-keyring.mjs";
3
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAA4C;AAC5C,iCAA6B"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./qr-keyring-scanner-bridge.mjs";
2
+ export * from "./qr-keyring.mjs";
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAA4C;AAC5C,iCAA6B","sourcesContent":["export * from './qr-keyring-scanner-bridge';\nexport * from './qr-keyring';\n"]}