@metamask-previews/eth-qr-keyring 0.0.0-a9b0f29
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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +15 -0
- package/README.md +1 -0
- package/dist/device.cjs +346 -0
- package/dist/device.cjs.map +1 -0
- package/dist/device.d.cts +175 -0
- package/dist/device.d.cts.map +1 -0
- package/dist/device.d.mts +175 -0
- package/dist/device.d.mts.map +1 -0
- package/dist/device.mjs +346 -0
- package/dist/device.mjs.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/dist/qr-keyring-scanner-bridge.cjs +37 -0
- package/dist/qr-keyring-scanner-bridge.cjs.map +1 -0
- package/dist/qr-keyring-scanner-bridge.d.cts +26 -0
- package/dist/qr-keyring-scanner-bridge.d.cts.map +1 -0
- package/dist/qr-keyring-scanner-bridge.d.mts +26 -0
- package/dist/qr-keyring-scanner-bridge.d.mts.map +1 -0
- package/dist/qr-keyring-scanner-bridge.mjs +33 -0
- package/dist/qr-keyring-scanner-bridge.mjs.map +1 -0
- package/dist/qr-keyring.cjs +288 -0
- package/dist/qr-keyring.cjs.map +1 -0
- package/dist/qr-keyring.d.cts +181 -0
- package/dist/qr-keyring.d.cts.map +1 -0
- package/dist/qr-keyring.d.mts +181 -0
- package/dist/qr-keyring.d.mts.map +1 -0
- package/dist/qr-keyring.mjs +283 -0
- package/dist/qr-keyring.mjs.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { type TypedTransaction, type TypedTxData } from "@ethereumjs/tx";
|
|
2
|
+
import type { TypedMessage, MessageTypes } from "@metamask/eth-sig-util";
|
|
3
|
+
import type { Keyring } from "@metamask/keyring-utils";
|
|
4
|
+
import { type Hex } from "@metamask/utils";
|
|
5
|
+
import { type DeviceDetails, type IndexedAddress } from "./device.mjs";
|
|
6
|
+
export declare const QR_KEYRING_TYPE = "QR Hardware Wallet Device";
|
|
7
|
+
export declare enum QrScanRequestType {
|
|
8
|
+
/**
|
|
9
|
+
* Request a scan for a QR code containing a UR
|
|
10
|
+
* with information related to a hardware wallet.
|
|
11
|
+
*/
|
|
12
|
+
PAIR = "pair",
|
|
13
|
+
/**
|
|
14
|
+
* Request a scan for a QR code containing a
|
|
15
|
+
* UR-encoded transaction signature.
|
|
16
|
+
*/
|
|
17
|
+
SIGN = "sign"
|
|
18
|
+
}
|
|
19
|
+
export type QrSignatureRequest = {
|
|
20
|
+
requestId: string;
|
|
21
|
+
payload: SerializedUR;
|
|
22
|
+
requestTitle?: string;
|
|
23
|
+
requestDescription?: string;
|
|
24
|
+
};
|
|
25
|
+
export type QrScanRequest = {
|
|
26
|
+
type: QrScanRequestType;
|
|
27
|
+
request?: QrSignatureRequest;
|
|
28
|
+
};
|
|
29
|
+
export type SerializedUR = {
|
|
30
|
+
type: string;
|
|
31
|
+
cbor: string;
|
|
32
|
+
};
|
|
33
|
+
export type QrKeyringBridge = {
|
|
34
|
+
requestScan: (request: QrScanRequest) => Promise<SerializedUR>;
|
|
35
|
+
};
|
|
36
|
+
export type QrKeyringOptions = {
|
|
37
|
+
ur?: string;
|
|
38
|
+
bridge: QrKeyringBridge;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The state of the QrKeyring
|
|
42
|
+
*
|
|
43
|
+
* @property accounts - The accounts in the QrKeyring
|
|
44
|
+
*/
|
|
45
|
+
export type SerializedQrKeyringState = {
|
|
46
|
+
version?: number;
|
|
47
|
+
accounts?: string[];
|
|
48
|
+
currentAccount?: number;
|
|
49
|
+
} & ({
|
|
50
|
+
initialized?: false;
|
|
51
|
+
} | ({
|
|
52
|
+
initialized: true;
|
|
53
|
+
} & DeviceDetails));
|
|
54
|
+
/**
|
|
55
|
+
* Returns the default serialized state of the QrKeyring.
|
|
56
|
+
*
|
|
57
|
+
* @returns The default serialized state.
|
|
58
|
+
*/
|
|
59
|
+
export declare const getDefaultSerializedQrKeyringState: () => SerializedQrKeyringState;
|
|
60
|
+
export declare class QrKeyring implements Keyring {
|
|
61
|
+
#private;
|
|
62
|
+
static type: string;
|
|
63
|
+
readonly type = "QR Hardware Wallet Device";
|
|
64
|
+
readonly bridge: QrKeyringBridge;
|
|
65
|
+
constructor(options: QrKeyringOptions);
|
|
66
|
+
/**
|
|
67
|
+
* Serializes the QrKeyring state
|
|
68
|
+
*
|
|
69
|
+
* @returns The serialized state
|
|
70
|
+
*/
|
|
71
|
+
serialize(): Promise<SerializedQrKeyringState>;
|
|
72
|
+
/**
|
|
73
|
+
* Deserializes the QrKeyring state
|
|
74
|
+
*
|
|
75
|
+
* @param state - The serialized state to deserialize
|
|
76
|
+
*/
|
|
77
|
+
deserialize(state: SerializedQrKeyringState): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Adds accounts to the QrKeyring
|
|
80
|
+
*
|
|
81
|
+
* @param accountsToAdd - The number of accounts to add
|
|
82
|
+
* @returns The accounts added
|
|
83
|
+
*/
|
|
84
|
+
addAccounts(accountsToAdd: number): Promise<Hex[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Gets the accounts in the QrKeyring
|
|
87
|
+
*
|
|
88
|
+
* @returns The accounts in the QrKeyring
|
|
89
|
+
*/
|
|
90
|
+
getAccounts(): Promise<Hex[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Remove an account from the keyring
|
|
93
|
+
*
|
|
94
|
+
* @param address - The address of the account to remove
|
|
95
|
+
*/
|
|
96
|
+
removeAccount(address: Hex): void;
|
|
97
|
+
/**
|
|
98
|
+
* Pair a QR-based Hardware Device from a CBOR encoded UR to the QrKeyring
|
|
99
|
+
*
|
|
100
|
+
* @param ur - The CBOR encoded UR
|
|
101
|
+
*/
|
|
102
|
+
pairDevice(ur: string | SerializedUR): void;
|
|
103
|
+
/**
|
|
104
|
+
* Sets the next account index to unlock
|
|
105
|
+
*
|
|
106
|
+
* @param index - The index of the account to unlock
|
|
107
|
+
*/
|
|
108
|
+
setAccountToUnlock(index: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* Get the name of the paired device or the keyring type
|
|
111
|
+
* if unavailable.
|
|
112
|
+
*
|
|
113
|
+
* @returns The name of the paired device or the keyring type.
|
|
114
|
+
*/
|
|
115
|
+
getName(): string;
|
|
116
|
+
/**
|
|
117
|
+
* Fetch the first page of accounts. If the keyring is not currently initialized,
|
|
118
|
+
* it will trigger a scan request to initialize it.
|
|
119
|
+
*
|
|
120
|
+
* @returns The first page of accounts as an array of Hex strings.
|
|
121
|
+
*/
|
|
122
|
+
getFirstPage(): Promise<IndexedAddress[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Fetch the next page of accounts. If the keyring is not currently initialized,
|
|
125
|
+
* it will trigger a scan request to initialize it.
|
|
126
|
+
*
|
|
127
|
+
* @returns The next page of accounts as an array of IndexedAddress objects.
|
|
128
|
+
*/
|
|
129
|
+
getNextPage(): Promise<IndexedAddress[]>;
|
|
130
|
+
/**
|
|
131
|
+
* Fetch the previous page of accounts. If the keyring is not currently initialized,
|
|
132
|
+
* it will trigger a scan request to initialize it.
|
|
133
|
+
*
|
|
134
|
+
* @returns The previous page of accounts as an array of IndexedAddress objects.
|
|
135
|
+
*/
|
|
136
|
+
getPreviousPage(): Promise<IndexedAddress[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Fetch the current page of accounts. If the keyring is not currently initialized,
|
|
139
|
+
* it will trigger a scan request to initialize it.
|
|
140
|
+
*
|
|
141
|
+
* @returns The current page of accounts as an array of IndexedAddress objects.
|
|
142
|
+
*/
|
|
143
|
+
getCurrentPage(): Promise<IndexedAddress[]>;
|
|
144
|
+
/**
|
|
145
|
+
* Clear the keyring state and forget any paired device or accounts.
|
|
146
|
+
*/
|
|
147
|
+
forgetDevice(): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Sign a transaction. This is equivalent to the `eth_signTransaction`
|
|
150
|
+
* Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
|
|
151
|
+
* more details.
|
|
152
|
+
*
|
|
153
|
+
* @param address - The address of the account to use for signing.
|
|
154
|
+
* @param transaction - The transaction to sign.
|
|
155
|
+
* @returns The signed transaction.
|
|
156
|
+
*/
|
|
157
|
+
signTransaction(address: Hex, transaction: TypedTransaction): Promise<TypedTxData>;
|
|
158
|
+
/**
|
|
159
|
+
* Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
|
|
160
|
+
* JSON-RPC method.
|
|
161
|
+
*
|
|
162
|
+
* @param address - The address of the account to use for signing.
|
|
163
|
+
* @param data - The data to sign.
|
|
164
|
+
* @returns The signed message.
|
|
165
|
+
*/
|
|
166
|
+
signTypedData<Types extends MessageTypes>(address: Hex, data: TypedMessage<Types>): Promise<string>;
|
|
167
|
+
/**
|
|
168
|
+
* Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC
|
|
169
|
+
* method, which is exposed by MetaMask as the method `personal_sign`. See
|
|
170
|
+
* the Ethereum JSON-RPC API documentation for more details.
|
|
171
|
+
*
|
|
172
|
+
* For more information about this method and why we call it `personal_sign`,
|
|
173
|
+
* see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.
|
|
174
|
+
*
|
|
175
|
+
* @param address - The address of the account to use for signing.
|
|
176
|
+
* @param message - The message to sign.
|
|
177
|
+
* @returns The signed message.
|
|
178
|
+
*/
|
|
179
|
+
signPersonalMessage(address: Hex, message: Hex): Promise<string>;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=qr-keyring.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-keyring.d.mts","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
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");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
9
|
+
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");
|
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
|
+
};
|
|
12
|
+
var _QrKeyring_instances, _QrKeyring_device, _QrKeyring_accounts, _QrKeyring_accountToUnlock, _QrKeyring_currentPage, _QrKeyring_scanAndInitialize;
|
|
13
|
+
import { add0x, assert, getChecksumAddress } from "@metamask/utils";
|
|
14
|
+
import { Device, DeviceMode } from "./device.mjs";
|
|
15
|
+
export const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';
|
|
16
|
+
export var QrScanRequestType;
|
|
17
|
+
(function (QrScanRequestType) {
|
|
18
|
+
/**
|
|
19
|
+
* Request a scan for a QR code containing a UR
|
|
20
|
+
* with information related to a hardware wallet.
|
|
21
|
+
*/
|
|
22
|
+
QrScanRequestType["PAIR"] = "pair";
|
|
23
|
+
/**
|
|
24
|
+
* Request a scan for a QR code containing a
|
|
25
|
+
* UR-encoded transaction signature.
|
|
26
|
+
*/
|
|
27
|
+
QrScanRequestType["SIGN"] = "sign";
|
|
28
|
+
})(QrScanRequestType || (QrScanRequestType = {}));
|
|
29
|
+
/**
|
|
30
|
+
* Returns the default serialized state of the QrKeyring.
|
|
31
|
+
*
|
|
32
|
+
* @returns The default serialized state.
|
|
33
|
+
*/
|
|
34
|
+
export const getDefaultSerializedQrKeyringState = () => ({
|
|
35
|
+
initialized: false,
|
|
36
|
+
accounts: [],
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Normalizes an address to a 0x-prefixed checksum address.
|
|
40
|
+
*
|
|
41
|
+
* @param address - The address to normalize.
|
|
42
|
+
* @returns The normalized address as a Hex string.
|
|
43
|
+
*/
|
|
44
|
+
function normalizeAddress(address) {
|
|
45
|
+
return getChecksumAddress(add0x(address));
|
|
46
|
+
}
|
|
47
|
+
export class QrKeyring {
|
|
48
|
+
constructor(options) {
|
|
49
|
+
_QrKeyring_instances.add(this);
|
|
50
|
+
this.type = QR_KEYRING_TYPE;
|
|
51
|
+
_QrKeyring_device.set(this, void 0);
|
|
52
|
+
_QrKeyring_accounts.set(this, []);
|
|
53
|
+
_QrKeyring_accountToUnlock.set(this, void 0);
|
|
54
|
+
_QrKeyring_currentPage.set(this, 0);
|
|
55
|
+
this.bridge = options.bridge;
|
|
56
|
+
if (options?.ur) {
|
|
57
|
+
this.pairDevice(options.ur);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Serializes the QrKeyring state
|
|
62
|
+
*
|
|
63
|
+
* @returns The serialized state
|
|
64
|
+
*/
|
|
65
|
+
async serialize() {
|
|
66
|
+
const deviceDetails = __classPrivateFieldGet(this, _QrKeyring_device, "f")
|
|
67
|
+
? __classPrivateFieldGet(this, _QrKeyring_device, "f").getDeviceDetails()
|
|
68
|
+
: undefined;
|
|
69
|
+
if (!deviceDetails ||
|
|
70
|
+
![DeviceMode.HD, DeviceMode.ACCOUNT].includes(deviceDetails.keyringMode)) {
|
|
71
|
+
// the keyring has not initialized with a deviceDetails device yet
|
|
72
|
+
return getDefaultSerializedQrKeyringState();
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...deviceDetails,
|
|
76
|
+
initialized: true,
|
|
77
|
+
accounts: __classPrivateFieldGet(this, _QrKeyring_accounts, "f").slice(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Deserializes the QrKeyring state
|
|
82
|
+
*
|
|
83
|
+
* @param state - The serialized state to deserialize
|
|
84
|
+
*/
|
|
85
|
+
async deserialize(state) {
|
|
86
|
+
if (!state.initialized) {
|
|
87
|
+
__classPrivateFieldSet(this, _QrKeyring_accounts, [], "f");
|
|
88
|
+
__classPrivateFieldSet(this, _QrKeyring_device, undefined, "f");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
__classPrivateFieldSet(this, _QrKeyring_device, new Device({
|
|
92
|
+
requestScan: this.bridge.requestScan.bind(this.bridge),
|
|
93
|
+
source: state,
|
|
94
|
+
}), "f");
|
|
95
|
+
__classPrivateFieldSet(this, _QrKeyring_accounts, (state.accounts ?? []).map(normalizeAddress), "f");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Adds accounts to the QrKeyring
|
|
99
|
+
*
|
|
100
|
+
* @param accountsToAdd - The number of accounts to add
|
|
101
|
+
* @returns The accounts added
|
|
102
|
+
*/
|
|
103
|
+
async addAccounts(accountsToAdd) {
|
|
104
|
+
if (!__classPrivateFieldGet(this, _QrKeyring_device, "f")) {
|
|
105
|
+
throw new Error('No device paired.');
|
|
106
|
+
}
|
|
107
|
+
const newAccounts = [];
|
|
108
|
+
for (let i = 0; i < accountsToAdd; i++) {
|
|
109
|
+
const index = __classPrivateFieldGet(this, _QrKeyring_accountToUnlock, "f") ?? __classPrivateFieldGet(this, _QrKeyring_accounts, "f").length + i;
|
|
110
|
+
const address = __classPrivateFieldGet(this, _QrKeyring_device, "f").addressFromIndex(index);
|
|
111
|
+
if (__classPrivateFieldGet(this, _QrKeyring_accounts, "f").includes(address)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
__classPrivateFieldGet(this, _QrKeyring_accounts, "f").push(address);
|
|
115
|
+
newAccounts.push(address);
|
|
116
|
+
this.setAccountToUnlock(index + 1);
|
|
117
|
+
}
|
|
118
|
+
return newAccounts;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Gets the accounts in the QrKeyring
|
|
122
|
+
*
|
|
123
|
+
* @returns The accounts in the QrKeyring
|
|
124
|
+
*/
|
|
125
|
+
async getAccounts() {
|
|
126
|
+
return __classPrivateFieldGet(this, _QrKeyring_accounts, "f").slice();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Remove an account from the keyring
|
|
130
|
+
*
|
|
131
|
+
* @param address - The address of the account to remove
|
|
132
|
+
*/
|
|
133
|
+
removeAccount(address) {
|
|
134
|
+
const normalizedAddress = normalizeAddress(address);
|
|
135
|
+
__classPrivateFieldSet(this, _QrKeyring_accounts, __classPrivateFieldGet(this, _QrKeyring_accounts, "f").filter((account) => account !== normalizedAddress), "f");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Pair a QR-based Hardware Device from a CBOR encoded UR to the QrKeyring
|
|
139
|
+
*
|
|
140
|
+
* @param ur - The CBOR encoded UR
|
|
141
|
+
*/
|
|
142
|
+
pairDevice(ur) {
|
|
143
|
+
__classPrivateFieldSet(this, _QrKeyring_device, new Device({
|
|
144
|
+
requestScan: this.bridge.requestScan.bind(this.bridge),
|
|
145
|
+
source: ur,
|
|
146
|
+
}), "f");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Sets the next account index to unlock
|
|
150
|
+
*
|
|
151
|
+
* @param index - The index of the account to unlock
|
|
152
|
+
*/
|
|
153
|
+
setAccountToUnlock(index) {
|
|
154
|
+
__classPrivateFieldSet(this, _QrKeyring_accountToUnlock, index, "f");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get the name of the paired device or the keyring type
|
|
158
|
+
* if unavailable.
|
|
159
|
+
*
|
|
160
|
+
* @returns The name of the paired device or the keyring type.
|
|
161
|
+
*/
|
|
162
|
+
getName() {
|
|
163
|
+
if (!__classPrivateFieldGet(this, _QrKeyring_device, "f")) {
|
|
164
|
+
return QR_KEYRING_TYPE;
|
|
165
|
+
}
|
|
166
|
+
const source = __classPrivateFieldGet(this, _QrKeyring_device, "f").getDeviceDetails();
|
|
167
|
+
return source.name;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Fetch the first page of accounts. If the keyring is not currently initialized,
|
|
171
|
+
* it will trigger a scan request to initialize it.
|
|
172
|
+
*
|
|
173
|
+
* @returns The first page of accounts as an array of Hex strings.
|
|
174
|
+
*/
|
|
175
|
+
async getFirstPage() {
|
|
176
|
+
__classPrivateFieldSet(this, _QrKeyring_currentPage, 0, "f");
|
|
177
|
+
return this.getCurrentPage();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Fetch the next page of accounts. If the keyring is not currently initialized,
|
|
181
|
+
* it will trigger a scan request to initialize it.
|
|
182
|
+
*
|
|
183
|
+
* @returns The next page of accounts as an array of IndexedAddress objects.
|
|
184
|
+
*/
|
|
185
|
+
async getNextPage() {
|
|
186
|
+
__classPrivateFieldSet(this, _QrKeyring_currentPage, __classPrivateFieldGet(this, _QrKeyring_currentPage, "f") + 1, "f");
|
|
187
|
+
return this.getCurrentPage();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Fetch the previous page of accounts. If the keyring is not currently initialized,
|
|
191
|
+
* it will trigger a scan request to initialize it.
|
|
192
|
+
*
|
|
193
|
+
* @returns The previous page of accounts as an array of IndexedAddress objects.
|
|
194
|
+
*/
|
|
195
|
+
async getPreviousPage() {
|
|
196
|
+
if (__classPrivateFieldGet(this, _QrKeyring_currentPage, "f") > 0) {
|
|
197
|
+
__classPrivateFieldSet(this, _QrKeyring_currentPage, __classPrivateFieldGet(this, _QrKeyring_currentPage, "f") - 1, "f");
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
__classPrivateFieldSet(this, _QrKeyring_currentPage, 0, "f");
|
|
201
|
+
}
|
|
202
|
+
return this.getCurrentPage();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Fetch the current page of accounts. If the keyring is not currently initialized,
|
|
206
|
+
* it will trigger a scan request to initialize it.
|
|
207
|
+
*
|
|
208
|
+
* @returns The current page of accounts as an array of IndexedAddress objects.
|
|
209
|
+
*/
|
|
210
|
+
async getCurrentPage() {
|
|
211
|
+
if (!__classPrivateFieldGet(this, _QrKeyring_device, "f")) {
|
|
212
|
+
await __classPrivateFieldGet(this, _QrKeyring_instances, "m", _QrKeyring_scanAndInitialize).call(this);
|
|
213
|
+
}
|
|
214
|
+
assert(__classPrivateFieldGet(this, _QrKeyring_device, "f"), 'A device is expected to be paired before fetching accounts.');
|
|
215
|
+
return __classPrivateFieldGet(this, _QrKeyring_device, "f").getAddressesPage(__classPrivateFieldGet(this, _QrKeyring_currentPage, "f"));
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Clear the keyring state and forget any paired device or accounts.
|
|
219
|
+
*/
|
|
220
|
+
async forgetDevice() {
|
|
221
|
+
__classPrivateFieldSet(this, _QrKeyring_device, undefined, "f");
|
|
222
|
+
__classPrivateFieldSet(this, _QrKeyring_accounts, [], "f");
|
|
223
|
+
__classPrivateFieldSet(this, _QrKeyring_accountToUnlock, undefined, "f");
|
|
224
|
+
__classPrivateFieldSet(this, _QrKeyring_currentPage, 0, "f");
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Sign a transaction. This is equivalent to the `eth_signTransaction`
|
|
228
|
+
* Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
|
|
229
|
+
* more details.
|
|
230
|
+
*
|
|
231
|
+
* @param address - The address of the account to use for signing.
|
|
232
|
+
* @param transaction - The transaction to sign.
|
|
233
|
+
* @returns The signed transaction.
|
|
234
|
+
*/
|
|
235
|
+
async signTransaction(address, transaction) {
|
|
236
|
+
if (!__classPrivateFieldGet(this, _QrKeyring_device, "f")) {
|
|
237
|
+
throw new Error('No device paired.');
|
|
238
|
+
}
|
|
239
|
+
return __classPrivateFieldGet(this, _QrKeyring_device, "f").signTransaction(address, transaction);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
|
|
243
|
+
* JSON-RPC method.
|
|
244
|
+
*
|
|
245
|
+
* @param address - The address of the account to use for signing.
|
|
246
|
+
* @param data - The data to sign.
|
|
247
|
+
* @returns The signed message.
|
|
248
|
+
*/
|
|
249
|
+
async signTypedData(address, data) {
|
|
250
|
+
if (!__classPrivateFieldGet(this, _QrKeyring_device, "f")) {
|
|
251
|
+
throw new Error('No device paired.');
|
|
252
|
+
}
|
|
253
|
+
return __classPrivateFieldGet(this, _QrKeyring_device, "f").signTypedData(address, data);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC
|
|
257
|
+
* method, which is exposed by MetaMask as the method `personal_sign`. See
|
|
258
|
+
* the Ethereum JSON-RPC API documentation for more details.
|
|
259
|
+
*
|
|
260
|
+
* For more information about this method and why we call it `personal_sign`,
|
|
261
|
+
* see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.
|
|
262
|
+
*
|
|
263
|
+
* @param address - The address of the account to use for signing.
|
|
264
|
+
* @param message - The message to sign.
|
|
265
|
+
* @returns The signed message.
|
|
266
|
+
*/
|
|
267
|
+
async signPersonalMessage(address, message) {
|
|
268
|
+
if (!__classPrivateFieldGet(this, _QrKeyring_device, "f")) {
|
|
269
|
+
throw new Error('No device paired.');
|
|
270
|
+
}
|
|
271
|
+
return __classPrivateFieldGet(this, _QrKeyring_device, "f").signPersonalMessage(address, message);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
_QrKeyring_device = new WeakMap(), _QrKeyring_accounts = new WeakMap(), _QrKeyring_accountToUnlock = new WeakMap(), _QrKeyring_currentPage = new WeakMap(), _QrKeyring_instances = new WeakSet(), _QrKeyring_scanAndInitialize =
|
|
275
|
+
/**
|
|
276
|
+
* Scan for a QR code and initialize the keyring with the
|
|
277
|
+
* scanned UR.
|
|
278
|
+
*/
|
|
279
|
+
async function _QrKeyring_scanAndInitialize() {
|
|
280
|
+
this.pairDevice(await this.bridge.requestScan({ type: QrScanRequestType.PAIR }));
|
|
281
|
+
};
|
|
282
|
+
QrKeyring.type = QR_KEYRING_TYPE;
|
|
283
|
+
//# sourceMappingURL=qr-keyring.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-keyring.mjs","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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","sourcesContent":["import { type TypedTransaction, type TypedTxData } from '@ethereumjs/tx';\nimport type { TypedMessage, MessageTypes } from '@metamask/eth-sig-util';\nimport type { Keyring } from '@metamask/keyring-utils';\nimport { type Hex, add0x, assert, getChecksumAddress } from '@metamask/utils';\n\nimport {\n type DeviceDetails,\n type IndexedAddress,\n Device,\n DeviceMode,\n} from './device';\n\nexport const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';\n\nexport enum QrScanRequestType {\n /**\n * Request a scan for a QR code containing a UR\n * with information related to a hardware wallet.\n */\n PAIR = 'pair',\n /**\n * Request a scan for a QR code containing a\n * UR-encoded transaction signature.\n */\n SIGN = 'sign',\n}\n\nexport type QrSignatureRequest = {\n requestId: string;\n payload: SerializedUR;\n requestTitle?: string;\n requestDescription?: string;\n};\n\nexport type QrScanRequest = {\n type: QrScanRequestType;\n request?: QrSignatureRequest;\n};\n\nexport type SerializedUR = {\n type: string;\n cbor: string;\n};\n\nexport type QrKeyringBridge = {\n requestScan: (request: QrScanRequest) => Promise<SerializedUR>;\n};\n\nexport type QrKeyringOptions = {\n ur?: string;\n bridge: QrKeyringBridge;\n};\n\n/**\n * The state of the QrKeyring\n *\n * @property accounts - The accounts in the QrKeyring\n */\nexport type SerializedQrKeyringState = {\n version?: number;\n accounts?: string[];\n currentAccount?: number;\n} & (\n | {\n initialized?: false;\n }\n | ({\n initialized: true;\n } & DeviceDetails)\n);\n\n/**\n * Returns the default serialized state of the QrKeyring.\n *\n * @returns The default serialized state.\n */\nexport const getDefaultSerializedQrKeyringState =\n (): SerializedQrKeyringState => ({\n initialized: false,\n accounts: [],\n });\n\n/**\n * Normalizes an address to a 0x-prefixed checksum address.\n *\n * @param address - The address to normalize.\n * @returns The normalized address as a Hex string.\n */\nfunction normalizeAddress(address: string): Hex {\n return getChecksumAddress(add0x(address));\n}\n\nexport class QrKeyring implements Keyring {\n static type = QR_KEYRING_TYPE;\n\n readonly type = QR_KEYRING_TYPE;\n\n readonly bridge: QrKeyringBridge;\n\n #device?: Device | undefined;\n\n #accounts: Hex[] = [];\n\n #accountToUnlock?: number | undefined;\n\n #currentPage: number = 0;\n\n constructor(options: QrKeyringOptions) {\n this.bridge = options.bridge;\n\n if (options?.ur) {\n this.pairDevice(options.ur);\n }\n }\n\n /**\n * Serializes the QrKeyring state\n *\n * @returns The serialized state\n */\n async serialize(): Promise<SerializedQrKeyringState> {\n const deviceDetails = this.#device\n ? this.#device.getDeviceDetails()\n : undefined;\n\n if (\n !deviceDetails ||\n ![DeviceMode.HD, DeviceMode.ACCOUNT].includes(deviceDetails.keyringMode)\n ) {\n // the keyring has not initialized with a deviceDetails device yet\n return getDefaultSerializedQrKeyringState();\n }\n\n return {\n ...deviceDetails,\n initialized: true,\n accounts: this.#accounts.slice(),\n };\n }\n\n /**\n * Deserializes the QrKeyring state\n *\n * @param state - The serialized state to deserialize\n */\n async deserialize(state: SerializedQrKeyringState): Promise<void> {\n if (!state.initialized) {\n this.#accounts = [];\n this.#device = undefined;\n return;\n }\n\n this.#device = new Device({\n requestScan: this.bridge.requestScan.bind(this.bridge),\n source: state,\n });\n this.#accounts = (state.accounts ?? []).map(normalizeAddress);\n }\n\n /**\n * Adds accounts to the QrKeyring\n *\n * @param accountsToAdd - The number of accounts to add\n * @returns The accounts added\n */\n async addAccounts(accountsToAdd: number): Promise<Hex[]> {\n if (!this.#device) {\n throw new Error('No device paired.');\n }\n\n const newAccounts: Hex[] = [];\n\n for (let i = 0; i < accountsToAdd; i++) {\n const index = this.#accountToUnlock ?? this.#accounts.length + i;\n const address = this.#device.addressFromIndex(index);\n\n if (this.#accounts.includes(address)) {\n continue;\n }\n\n this.#accounts.push(address);\n newAccounts.push(address);\n this.setAccountToUnlock(index + 1);\n }\n\n return newAccounts;\n }\n\n /**\n * Gets the accounts in the QrKeyring\n *\n * @returns The accounts in the QrKeyring\n */\n async getAccounts(): Promise<Hex[]> {\n return this.#accounts.slice();\n }\n\n /**\n * Remove an account from the keyring\n *\n * @param address - The address of the account to remove\n */\n removeAccount(address: Hex): void {\n const normalizedAddress = normalizeAddress(address);\n this.#accounts = this.#accounts.filter(\n (account) => account !== normalizedAddress,\n );\n }\n\n /**\n * Pair a QR-based Hardware Device from a CBOR encoded UR to the QrKeyring\n *\n * @param ur - The CBOR encoded UR\n */\n pairDevice(ur: string | SerializedUR): void {\n this.#device = new Device({\n requestScan: this.bridge.requestScan.bind(this.bridge),\n source: ur,\n });\n }\n\n /**\n * Sets the next account index to unlock\n *\n * @param index - The index of the account to unlock\n */\n setAccountToUnlock(index: number): void {\n this.#accountToUnlock = index;\n }\n\n /**\n * Get the name of the paired device or the keyring type\n * if unavailable.\n *\n * @returns The name of the paired device or the keyring type.\n */\n getName(): string {\n if (!this.#device) {\n return QR_KEYRING_TYPE;\n }\n const source = this.#device.getDeviceDetails();\n return source.name;\n }\n\n /**\n * Fetch the first page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The first page of accounts as an array of Hex strings.\n */\n async getFirstPage(): Promise<IndexedAddress[]> {\n this.#currentPage = 0;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the next page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The next page of accounts as an array of IndexedAddress objects.\n */\n async getNextPage(): Promise<IndexedAddress[]> {\n this.#currentPage += 1;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the previous page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The previous page of accounts as an array of IndexedAddress objects.\n */\n async getPreviousPage(): Promise<IndexedAddress[]> {\n if (this.#currentPage > 0) {\n this.#currentPage -= 1;\n } else {\n this.#currentPage = 0;\n }\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the current page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The current page of accounts as an array of IndexedAddress objects.\n */\n async getCurrentPage(): Promise<IndexedAddress[]> {\n if (!this.#device) {\n await this.#scanAndInitialize();\n }\n assert(\n this.#device,\n 'A device is expected to be paired before fetching accounts.',\n );\n return this.#device.getAddressesPage(this.#currentPage);\n }\n\n /**\n * Clear the keyring state and forget any paired device or accounts.\n */\n async forgetDevice(): Promise<void> {\n this.#device = undefined;\n this.#accounts = [];\n this.#accountToUnlock = undefined;\n this.#currentPage = 0;\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 if (!this.#device) {\n throw new Error('No device paired.');\n }\n return this.#device.signTransaction(address, transaction);\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 if (!this.#device) {\n throw new Error('No device paired.');\n }\n return this.#device.signTypedData(address, data);\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 if (!this.#device) {\n throw new Error('No device paired.');\n }\n return this.#device.signPersonalMessage(address, message);\n }\n\n /**\n * Scan for a QR code and initialize the keyring with the\n * scanned UR.\n */\n async #scanAndInitialize(): Promise<void> {\n this.pairDevice(\n await this.bridge.requestScan({ type: QrScanRequestType.PAIR }),\n );\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metamask-previews/eth-qr-keyring",
|
|
3
|
+
"version": "0.0.0-a9b0f29",
|
|
4
|
+
"description": "A simple standard interface for a series of Ethereum private keys.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ethereum",
|
|
7
|
+
"keyring",
|
|
8
|
+
"qr",
|
|
9
|
+
"metamask"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/MetaMask/accounts/packages/keyring-eth-qr#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/MetaMask/accounts/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/MetaMask/accounts.git"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.mts",
|
|
23
|
+
"default": "./dist/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
27
|
+
"default": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"types": "./dist/index.d.cts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc --build tsconfig.build.json",
|
|
38
|
+
"build:clean": "rimraf dist && yarn build",
|
|
39
|
+
"build:docs": "typedoc",
|
|
40
|
+
"build:force": "tsc --build tsconfig.build.json --force",
|
|
41
|
+
"changelog:update": "../../scripts/update-changelog.sh @metamask/eth-qr-keyring",
|
|
42
|
+
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-qr-keyring",
|
|
43
|
+
"publish:preview": "yarn npm publish --tag preview",
|
|
44
|
+
"sample": "ts-node src/sample.ts",
|
|
45
|
+
"test": "jest",
|
|
46
|
+
"test:clean": "jest --clearCache",
|
|
47
|
+
"test:verbose": "jest --verbose",
|
|
48
|
+
"test:watch": "jest --watch"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@ethereumjs/rlp": "^5.0.2",
|
|
52
|
+
"@ethereumjs/tx": "^5.4.0",
|
|
53
|
+
"@ethereumjs/util": "^9.1.0",
|
|
54
|
+
"@keystonehq/bc-ur-registry-eth": "^0.19.1",
|
|
55
|
+
"@metamask/eth-sig-util": "^8.2.0",
|
|
56
|
+
"@metamask/keyring-utils": "3.0.0",
|
|
57
|
+
"@metamask/utils": "^11.1.0",
|
|
58
|
+
"hdkey": "^2.1.0",
|
|
59
|
+
"uuid": "^9.0.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@ethereumjs/common": "^4.4.0",
|
|
63
|
+
"@keystonehq/metamask-airgapped-keyring": "^0.15.2",
|
|
64
|
+
"@lavamoat/allow-scripts": "^3.2.1",
|
|
65
|
+
"@metamask/auto-changelog": "^3.4.4",
|
|
66
|
+
"@types/hdkey": "^2.0.1",
|
|
67
|
+
"@types/jest": "^29.5.12",
|
|
68
|
+
"@types/node": "^20.12.12",
|
|
69
|
+
"deepmerge": "^4.2.2",
|
|
70
|
+
"depcheck": "^1.4.7",
|
|
71
|
+
"jest": "^29.5.0",
|
|
72
|
+
"ts-jest": "^29.0.5",
|
|
73
|
+
"ts-node": "^10.9.2",
|
|
74
|
+
"typedoc": "^0.25.13",
|
|
75
|
+
"typescript": "~5.6.3"
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": "^18.18 || >=20"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public",
|
|
82
|
+
"registry": "https://registry.npmjs.org/"
|
|
83
|
+
},
|
|
84
|
+
"lavamoat": {
|
|
85
|
+
"allowScripts": {
|
|
86
|
+
"keccak": true,
|
|
87
|
+
"secp256k1": true,
|
|
88
|
+
"@lavamoat/preinstall-always-fail": false,
|
|
89
|
+
"ethereumjs-tx>ethereumjs-util>ethereum-cryptography>keccak": false,
|
|
90
|
+
"ethereumjs-tx>ethereumjs-util>ethereum-cryptography>secp256k1": false,
|
|
91
|
+
"@keystonehq/ur-decoder>@keystonehq/bc-ur-registry-eth>ethereumjs-util>ethereum-cryptography>keccak": false,
|
|
92
|
+
"@lavamoat/allow-scripts>@lavamoat/preinstall-always-fail": false,
|
|
93
|
+
"hdkey>secp256k1": false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|