@metamask-previews/profile-metrics-controller 3.1.5-preview-a302a81d3 → 3.1.6-preview-a65eb72
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 +15 -1
- package/dist/ProfileMetricsService-method-action-types.cjs.map +1 -1
- package/dist/ProfileMetricsService-method-action-types.d.cts +26 -1
- package/dist/ProfileMetricsService-method-action-types.d.cts.map +1 -1
- package/dist/ProfileMetricsService-method-action-types.d.mts +26 -1
- package/dist/ProfileMetricsService-method-action-types.d.mts.map +1 -1
- package/dist/ProfileMetricsService-method-action-types.mjs.map +1 -1
- package/dist/ProfileMetricsService.cjs +100 -4
- package/dist/ProfileMetricsService.cjs.map +1 -1
- package/dist/ProfileMetricsService.d.cts +68 -1
- package/dist/ProfileMetricsService.d.cts.map +1 -1
- package/dist/ProfileMetricsService.d.mts +68 -1
- package/dist/ProfileMetricsService.d.mts.map +1 -1
- package/dist/ProfileMetricsService.mjs +99 -3
- package/dist/ProfileMetricsService.mjs.map +1 -1
- package/dist/utils/canonicalize.cjs +72 -0
- package/dist/utils/canonicalize.cjs.map +1 -0
- package/dist/utils/canonicalize.d.cts +30 -0
- package/dist/utils/canonicalize.d.cts.map +1 -0
- package/dist/utils/canonicalize.d.mts +30 -0
- package/dist/utils/canonicalize.d.mts.map +1 -0
- package/dist/utils/canonicalize.mjs +67 -0
- package/dist/utils/canonicalize.mjs.map +1 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add proof of ownership API wiring pre-requisites ([#8974](https://github.com/MetaMask/core/pull/8974))
|
|
13
|
+
- Add `ProfileMetricsService:fetchNonces` messenger action wrapping `POST /api/v2/nonce/batch`.
|
|
14
|
+
- Add optional `proof` field on accounts submitted via `ProfileMetricsService:submitMetrics` so that the auth API can use it to mark accounts as `verified: true`.
|
|
15
|
+
|
|
16
|
+
## [3.1.6]
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Bump `@metamask/accounts-controller` from `^38.1.2` to `^39.0.0` ([#8999](https://github.com/MetaMask/core/pull/8999))
|
|
21
|
+
- Bump `@metamask/transaction-controller` from `^66.0.0` to `^66.0.1` ([#8999](https://github.com/MetaMask/core/pull/8999))
|
|
22
|
+
|
|
10
23
|
## [3.1.5]
|
|
11
24
|
|
|
12
25
|
### Changed
|
|
@@ -139,7 +152,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
139
152
|
|
|
140
153
|
- Initial release ([#7194](https://github.com/MetaMask/core/pull/7194), [#7196](https://github.com/MetaMask/core/pull/7196), [#7263](https://github.com/MetaMask/core/pull/7263))
|
|
141
154
|
|
|
142
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/profile-metrics-controller@3.1.
|
|
155
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/profile-metrics-controller@3.1.6...HEAD
|
|
156
|
+
[3.1.6]: https://github.com/MetaMask/core/compare/@metamask/profile-metrics-controller@3.1.5...@metamask/profile-metrics-controller@3.1.6
|
|
143
157
|
[3.1.5]: https://github.com/MetaMask/core/compare/@metamask/profile-metrics-controller@3.1.4...@metamask/profile-metrics-controller@3.1.5
|
|
144
158
|
[3.1.4]: https://github.com/MetaMask/core/compare/@metamask/profile-metrics-controller@3.1.3...@metamask/profile-metrics-controller@3.1.4
|
|
145
159
|
[3.1.3]: https://github.com/MetaMask/core/compare/@metamask/profile-metrics-controller@3.1.2...@metamask/profile-metrics-controller@3.1.3
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService-method-action-types.cjs","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { ProfileMetricsService } from './ProfileMetricsService';\n\n/**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\nexport type ProfileMetricsServiceSubmitMetricsAction = {\n type: `ProfileMetricsService:submitMetrics`;\n handler: ProfileMetricsService['submitMetrics'];\n};\n\n/**\n * Union of all ProfileMetricsService action types.\n */\nexport type ProfileMetricsServiceMethodActions =\n ProfileMetricsServiceSubmitMetricsAction;\n"]}
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService-method-action-types.cjs","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { ProfileMetricsService } from './ProfileMetricsService';\n\n/**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\nexport type ProfileMetricsServiceFetchNoncesAction = {\n type: `ProfileMetricsService:fetchNonces`;\n handler: ProfileMetricsService['fetchNonces'];\n};\n\n/**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\nexport type ProfileMetricsServiceSubmitMetricsAction = {\n type: `ProfileMetricsService:submitMetrics`;\n handler: ProfileMetricsService['submitMetrics'];\n};\n\n/**\n * Union of all ProfileMetricsService action types.\n */\nexport type ProfileMetricsServiceMethodActions =\n | ProfileMetricsServiceFetchNoncesAction\n | ProfileMetricsServiceSubmitMetricsAction;\n"]}
|
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
*/
|
|
5
5
|
import type { ProfileMetricsService } from "./ProfileMetricsService.cjs";
|
|
6
|
+
/**
|
|
7
|
+
* Fetch single-use nonces from the auth API, one per identifier.
|
|
8
|
+
*
|
|
9
|
+
* Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple
|
|
10
|
+
* `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are
|
|
11
|
+
* merged into a single record. Each chunk independently goes through the
|
|
12
|
+
* service policy (retry, circuit-breaker, degraded). If any chunk ultimately
|
|
13
|
+
* fails, the whole call rejects so the caller can soft-degrade the entire
|
|
14
|
+
* entropy-source batch consistently.
|
|
15
|
+
*
|
|
16
|
+
* The returned record is keyed by the auth API's echoed `identifier` field
|
|
17
|
+
* (`response[i].identifier -> response[i].nonce`). The call asserts that
|
|
18
|
+
* the response identifier set is exactly the requested set; any mismatch
|
|
19
|
+
* (missing, extra, or duplicated identifier) causes the chunk to throw so
|
|
20
|
+
* the caller never silently proceeds with partial nonces.
|
|
21
|
+
*
|
|
22
|
+
* @param data - The identifiers to mint nonces for, plus the optional
|
|
23
|
+
* entropy source ID used to scope the bearer token.
|
|
24
|
+
* @returns A map of identifier -> nonce.
|
|
25
|
+
* @throws {RangeError} if no identifiers are provided.
|
|
26
|
+
*/
|
|
27
|
+
export type ProfileMetricsServiceFetchNoncesAction = {
|
|
28
|
+
type: `ProfileMetricsService:fetchNonces`;
|
|
29
|
+
handler: ProfileMetricsService['fetchNonces'];
|
|
30
|
+
};
|
|
6
31
|
/**
|
|
7
32
|
* Submit metrics to the API.
|
|
8
33
|
*
|
|
@@ -16,5 +41,5 @@ export type ProfileMetricsServiceSubmitMetricsAction = {
|
|
|
16
41
|
/**
|
|
17
42
|
* Union of all ProfileMetricsService action types.
|
|
18
43
|
*/
|
|
19
|
-
export type ProfileMetricsServiceMethodActions = ProfileMetricsServiceSubmitMetricsAction;
|
|
44
|
+
export type ProfileMetricsServiceMethodActions = ProfileMetricsServiceFetchNoncesAction | ProfileMetricsServiceSubmitMetricsAction;
|
|
20
45
|
//# sourceMappingURL=ProfileMetricsService-method-action-types.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService-method-action-types.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oCAAgC;AAErE;;;;;GAKG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,qCAAqC,CAAC;IAC5C,OAAO,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService-method-action-types.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oCAAgC;AAErE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD,IAAI,EAAE,mCAAmC,CAAC;IAC1C,OAAO,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;CAC/C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,qCAAqC,CAAC;IAC5C,OAAO,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAC1C,sCAAsC,GACtC,wCAAwC,CAAC"}
|
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
*/
|
|
5
5
|
import type { ProfileMetricsService } from "./ProfileMetricsService.mjs";
|
|
6
|
+
/**
|
|
7
|
+
* Fetch single-use nonces from the auth API, one per identifier.
|
|
8
|
+
*
|
|
9
|
+
* Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple
|
|
10
|
+
* `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are
|
|
11
|
+
* merged into a single record. Each chunk independently goes through the
|
|
12
|
+
* service policy (retry, circuit-breaker, degraded). If any chunk ultimately
|
|
13
|
+
* fails, the whole call rejects so the caller can soft-degrade the entire
|
|
14
|
+
* entropy-source batch consistently.
|
|
15
|
+
*
|
|
16
|
+
* The returned record is keyed by the auth API's echoed `identifier` field
|
|
17
|
+
* (`response[i].identifier -> response[i].nonce`). The call asserts that
|
|
18
|
+
* the response identifier set is exactly the requested set; any mismatch
|
|
19
|
+
* (missing, extra, or duplicated identifier) causes the chunk to throw so
|
|
20
|
+
* the caller never silently proceeds with partial nonces.
|
|
21
|
+
*
|
|
22
|
+
* @param data - The identifiers to mint nonces for, plus the optional
|
|
23
|
+
* entropy source ID used to scope the bearer token.
|
|
24
|
+
* @returns A map of identifier -> nonce.
|
|
25
|
+
* @throws {RangeError} if no identifiers are provided.
|
|
26
|
+
*/
|
|
27
|
+
export type ProfileMetricsServiceFetchNoncesAction = {
|
|
28
|
+
type: `ProfileMetricsService:fetchNonces`;
|
|
29
|
+
handler: ProfileMetricsService['fetchNonces'];
|
|
30
|
+
};
|
|
6
31
|
/**
|
|
7
32
|
* Submit metrics to the API.
|
|
8
33
|
*
|
|
@@ -16,5 +41,5 @@ export type ProfileMetricsServiceSubmitMetricsAction = {
|
|
|
16
41
|
/**
|
|
17
42
|
* Union of all ProfileMetricsService action types.
|
|
18
43
|
*/
|
|
19
|
-
export type ProfileMetricsServiceMethodActions = ProfileMetricsServiceSubmitMetricsAction;
|
|
44
|
+
export type ProfileMetricsServiceMethodActions = ProfileMetricsServiceFetchNoncesAction | ProfileMetricsServiceSubmitMetricsAction;
|
|
20
45
|
//# sourceMappingURL=ProfileMetricsService-method-action-types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService-method-action-types.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oCAAgC;AAErE;;;;;GAKG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,qCAAqC,CAAC;IAC5C,OAAO,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService-method-action-types.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oCAAgC;AAErE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD,IAAI,EAAE,mCAAmC,CAAC;IAC1C,OAAO,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;CAC/C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,qCAAqC,CAAC;IAC5C,OAAO,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAC1C,sCAAsC,GACtC,wCAAwC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService-method-action-types.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { ProfileMetricsService } from './ProfileMetricsService';\n\n/**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\nexport type ProfileMetricsServiceSubmitMetricsAction = {\n type: `ProfileMetricsService:submitMetrics`;\n handler: ProfileMetricsService['submitMetrics'];\n};\n\n/**\n * Union of all ProfileMetricsService action types.\n */\nexport type ProfileMetricsServiceMethodActions =\n ProfileMetricsServiceSubmitMetricsAction;\n"]}
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService-method-action-types.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { ProfileMetricsService } from './ProfileMetricsService';\n\n/**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\nexport type ProfileMetricsServiceFetchNoncesAction = {\n type: `ProfileMetricsService:fetchNonces`;\n handler: ProfileMetricsService['fetchNonces'];\n};\n\n/**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\nexport type ProfileMetricsServiceSubmitMetricsAction = {\n type: `ProfileMetricsService:submitMetrics`;\n handler: ProfileMetricsService['submitMetrics'];\n};\n\n/**\n * Union of all ProfileMetricsService action types.\n */\nexport type ProfileMetricsServiceMethodActions =\n | ProfileMetricsServiceFetchNoncesAction\n | ProfileMetricsServiceSubmitMetricsAction;\n"]}
|
|
@@ -10,19 +10,40 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
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");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _ProfileMetricsService_baseURL;
|
|
13
|
+
var _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _ProfileMetricsService_baseURL, _ProfileMetricsService_fetchNoncesChunk;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.getAuthUrl = exports.ProfileMetricsService = exports.serviceName = void 0;
|
|
15
|
+
exports.getAuthUrl = exports.ProfileMetricsService = exports.MAX_NONCE_BATCH_SIZE = exports.serviceName = void 0;
|
|
16
16
|
const controller_utils_1 = require("@metamask/controller-utils");
|
|
17
17
|
const profile_sync_controller_1 = require("@metamask/profile-sync-controller");
|
|
18
|
+
const superstruct_1 = require("@metamask/superstruct");
|
|
19
|
+
/**
|
|
20
|
+
* The shape of an entry in the `POST /api/v2/nonce/batch` response body.
|
|
21
|
+
*
|
|
22
|
+
* `identifier` echoes the request identifier verbatim, mirroring the
|
|
23
|
+
* documented behavior of the single-account `GET /api/v2/nonce` endpoint on
|
|
24
|
+
* the same auth service. Defined with `type()` (not `object()`) so the
|
|
25
|
+
* client tolerates additive server-side schema changes.
|
|
26
|
+
*/
|
|
27
|
+
const NonceBatchResponseStruct = (0, superstruct_1.array)((0, superstruct_1.type)({
|
|
28
|
+
expires_in: (0, superstruct_1.number)(),
|
|
29
|
+
identifier: (0, superstruct_1.string)(),
|
|
30
|
+
nonce: (0, superstruct_1.string)(),
|
|
31
|
+
}));
|
|
18
32
|
// === GENERAL ===
|
|
19
33
|
/**
|
|
20
34
|
* The name of the {@link ProfileMetricsService}, used to namespace the
|
|
21
35
|
* service's actions and events.
|
|
22
36
|
*/
|
|
23
37
|
exports.serviceName = 'ProfileMetricsService';
|
|
38
|
+
/**
|
|
39
|
+
* Maximum number of identifiers the auth API will mint nonces for in a single
|
|
40
|
+
* `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}
|
|
41
|
+
* uses this as the chunk size when the caller requests more than this many
|
|
42
|
+
* nonces at once.
|
|
43
|
+
*/
|
|
44
|
+
exports.MAX_NONCE_BATCH_SIZE = 50;
|
|
24
45
|
// === MESSENGER ===
|
|
25
|
-
const MESSENGER_EXPOSED_METHODS = ['submitMetrics'];
|
|
46
|
+
const MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'];
|
|
26
47
|
// === SERVICE DEFINITION ===
|
|
27
48
|
/**
|
|
28
49
|
* A service for submitting user profile metrics (metrics ID and accounts).
|
|
@@ -42,6 +63,7 @@ class ProfileMetricsService {
|
|
|
42
63
|
* @param args.env - The environment to determine the correct API endpoints.
|
|
43
64
|
*/
|
|
44
65
|
constructor({ messenger, fetch: fetchFunction, policyOptions = {}, env = profile_sync_controller_1.SDK.Env.DEV, }) {
|
|
66
|
+
_ProfileMetricsService_instances.add(this);
|
|
45
67
|
/**
|
|
46
68
|
* The messenger suited for this service.
|
|
47
69
|
*/
|
|
@@ -112,6 +134,38 @@ class ProfileMetricsService {
|
|
|
112
134
|
onDegraded(listener) {
|
|
113
135
|
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onDegraded(listener);
|
|
114
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Fetch single-use nonces from the auth API, one per identifier.
|
|
139
|
+
*
|
|
140
|
+
* Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple
|
|
141
|
+
* `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are
|
|
142
|
+
* merged into a single record. Each chunk independently goes through the
|
|
143
|
+
* service policy (retry, circuit-breaker, degraded). If any chunk ultimately
|
|
144
|
+
* fails, the whole call rejects so the caller can soft-degrade the entire
|
|
145
|
+
* entropy-source batch consistently.
|
|
146
|
+
*
|
|
147
|
+
* The returned record is keyed by the auth API's echoed `identifier` field
|
|
148
|
+
* (`response[i].identifier -> response[i].nonce`). The call asserts that
|
|
149
|
+
* the response identifier set is exactly the requested set; any mismatch
|
|
150
|
+
* (missing, extra, or duplicated identifier) causes the chunk to throw so
|
|
151
|
+
* the caller never silently proceeds with partial nonces.
|
|
152
|
+
*
|
|
153
|
+
* @param data - The identifiers to mint nonces for, plus the optional
|
|
154
|
+
* entropy source ID used to scope the bearer token.
|
|
155
|
+
* @returns A map of identifier -> nonce.
|
|
156
|
+
* @throws {RangeError} if no identifiers are provided.
|
|
157
|
+
*/
|
|
158
|
+
async fetchNonces(data) {
|
|
159
|
+
if (data.identifiers.length === 0) {
|
|
160
|
+
throw new RangeError('ProfileMetricsService.fetchNonces requires at least 1 identifier.');
|
|
161
|
+
}
|
|
162
|
+
const chunks = [];
|
|
163
|
+
for (let i = 0; i < data.identifiers.length; i += exports.MAX_NONCE_BATCH_SIZE) {
|
|
164
|
+
chunks.push(data.identifiers.slice(i, i + exports.MAX_NONCE_BATCH_SIZE));
|
|
165
|
+
}
|
|
166
|
+
const chunkResults = await Promise.all(chunks.map((identifiers) => __classPrivateFieldGet(this, _ProfileMetricsService_instances, "m", _ProfileMetricsService_fetchNoncesChunk).call(this, identifiers, data.entropySourceId)));
|
|
167
|
+
return Object.assign({}, ...chunkResults);
|
|
168
|
+
}
|
|
115
169
|
/**
|
|
116
170
|
* Submit metrics to the API.
|
|
117
171
|
*
|
|
@@ -146,7 +200,49 @@ class ProfileMetricsService {
|
|
|
146
200
|
}
|
|
147
201
|
}
|
|
148
202
|
exports.ProfileMetricsService = ProfileMetricsService;
|
|
149
|
-
_a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_policy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap()
|
|
203
|
+
_a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_policy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap(), _ProfileMetricsService_instances = new WeakSet(), _ProfileMetricsService_fetchNoncesChunk =
|
|
204
|
+
/**
|
|
205
|
+
* Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of
|
|
206
|
+
* identifiers. Wrapped in {@link #policy} for retry / degraded / circuit
|
|
207
|
+
* semantics consistent with the rest of the service.
|
|
208
|
+
*
|
|
209
|
+
* @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.
|
|
210
|
+
* @param entropySourceId - The entropy source ID forwarded to the bearer
|
|
211
|
+
* token resolver.
|
|
212
|
+
* @returns A map of identifier -> nonce for this chunk.
|
|
213
|
+
*/
|
|
214
|
+
async function _ProfileMetricsService_fetchNoncesChunk(identifiers, entropySourceId) {
|
|
215
|
+
return await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
|
|
216
|
+
const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', entropySourceId ?? undefined);
|
|
217
|
+
const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/nonce/batch`);
|
|
218
|
+
const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
|
|
219
|
+
method: 'POST',
|
|
220
|
+
headers: {
|
|
221
|
+
Authorization: `Bearer ${authToken}`,
|
|
222
|
+
'Content-Type': 'application/json',
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify({ identifiers }),
|
|
225
|
+
credentials: 'omit',
|
|
226
|
+
});
|
|
227
|
+
if (!localResponse.ok) {
|
|
228
|
+
throw new controller_utils_1.HttpError(localResponse.status, `Fetching '${url.toString()}' failed with status '${localResponse.status}'`);
|
|
229
|
+
}
|
|
230
|
+
const body = await localResponse.json();
|
|
231
|
+
if (!NonceBatchResponseStruct.is(body)) {
|
|
232
|
+
throw new Error(`Malformed response received from '${url.toString()}'`);
|
|
233
|
+
}
|
|
234
|
+
const result = {};
|
|
235
|
+
for (const entry of body) {
|
|
236
|
+
result[entry.identifier] = entry.nonce;
|
|
237
|
+
}
|
|
238
|
+
const echoesRequest = body.length === identifiers.length &&
|
|
239
|
+
identifiers.every((id) => Object.prototype.hasOwnProperty.call(result, id));
|
|
240
|
+
if (!echoesRequest) {
|
|
241
|
+
throw new Error(`Fetching '${url.toString()}' returned a response whose identifier set does not match the request`);
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
});
|
|
245
|
+
};
|
|
150
246
|
/**
|
|
151
247
|
* Returns the base URL for the given environment.
|
|
152
248
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService.cjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * An account address along with its associated scopes.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * The policy that wraps the request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#policy = createServicePolicy(policyOptions);\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService.cjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport {\n array,\n number,\n string,\n type as structType,\n} from '@metamask/superstruct';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\n\n/**\n * The shape of an entry in the `POST /api/v2/nonce/batch` response body.\n *\n * `identifier` echoes the request identifier verbatim, mirroring the\n * documented behavior of the single-account `GET /api/v2/nonce` endpoint on\n * the same auth service. Defined with `type()` (not `object()`) so the\n * client tolerates additive server-side schema changes.\n */\nconst NonceBatchResponseStruct = array(\n structType({\n expires_in: number(),\n identifier: string(),\n nonce: string(),\n }),\n);\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * A cryptographic proof that the caller controls the private key of an\n * account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the\n * auth API. When present, the server verifies the signature against\n * `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently\n * marks the account as `verified: true`.\n */\nexport type AccountOwnershipProof = {\n /**\n * Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.\n * Consumed by the server on verification; replay is not possible.\n */\n nonce: string;\n /**\n * Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,\n * always 0x-prefixed. The exact format varies by chain (see the auth API\n * spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,\n * BIP-322 for `bip122`).\n */\n signature: string;\n};\n\n/**\n * An account address along with its associated scopes and an optional\n * ownership proof.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n proof?: AccountOwnershipProof;\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n/**\n * The shape of the request object for fetching a batch of single-use nonces.\n */\nexport type ProfileMetricsFetchNoncesRequest = {\n /**\n * The identifiers (canonical addresses) to mint a nonce for. The auth API\n * accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.\n */\n identifiers: string[];\n /**\n * The entropy source ID to use when fetching a bearer token. Pass `null` or\n * omit for accounts that do not belong to any entropy source.\n */\n entropySourceId?: string | null;\n};\n\n/**\n * Maximum number of identifiers the auth API will mint nonces for in a single\n * `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}\n * uses this as the chunk size when the caller requests more than this many\n * nonces at once.\n */\nexport const MAX_NONCE_BATCH_SIZE = 50;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * The policy that wraps the request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#policy = createServicePolicy(policyOptions);\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\n async fetchNonces(\n data: ProfileMetricsFetchNoncesRequest,\n ): Promise<Record<string, string>> {\n if (data.identifiers.length === 0) {\n throw new RangeError(\n 'ProfileMetricsService.fetchNonces requires at least 1 identifier.',\n );\n }\n const chunks: string[][] = [];\n for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {\n chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));\n }\n const chunkResults = await Promise.all(\n chunks.map((identifiers) =>\n this.#fetchNoncesChunk(identifiers, data.entropySourceId),\n ),\n );\n return Object.assign({}, ...chunkResults);\n }\n\n /**\n * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of\n * identifiers. Wrapped in {@link #policy} for retry / degraded / circuit\n * semantics consistent with the rest of the service.\n *\n * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.\n * @param entropySourceId - The entropy source ID forwarded to the bearer\n * token resolver.\n * @returns A map of identifier -> nonce for this chunk.\n */\n async #fetchNoncesChunk(\n identifiers: string[],\n entropySourceId: string | null | undefined,\n ): Promise<Record<string, string>> {\n return await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/nonce/batch`);\n const localResponse = await this.#fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ identifiers }),\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n const body: unknown = await localResponse.json();\n if (!NonceBatchResponseStruct.is(body)) {\n throw new Error(`Malformed response received from '${url.toString()}'`);\n }\n const result: Record<string, string> = {};\n for (const entry of body) {\n result[entry.identifier] = entry.nonce;\n }\n const echoesRequest =\n body.length === identifiers.length &&\n identifiers.every((id) =>\n Object.prototype.hasOwnProperty.call(result, id),\n );\n if (!echoesRequest) {\n throw new Error(\n `Fetching '${url.toString()}' returned a response whose identifier set does not match the request`,\n );\n }\n return result;\n });\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
|
|
@@ -10,11 +10,34 @@ import type { ProfileMetricsServiceMethodActions } from "./index.cjs";
|
|
|
10
10
|
*/
|
|
11
11
|
export declare const serviceName = "ProfileMetricsService";
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* A cryptographic proof that the caller controls the private key of an
|
|
14
|
+
* account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the
|
|
15
|
+
* auth API. When present, the server verifies the signature against
|
|
16
|
+
* `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently
|
|
17
|
+
* marks the account as `verified: true`.
|
|
18
|
+
*/
|
|
19
|
+
export type AccountOwnershipProof = {
|
|
20
|
+
/**
|
|
21
|
+
* Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.
|
|
22
|
+
* Consumed by the server on verification; replay is not possible.
|
|
23
|
+
*/
|
|
24
|
+
nonce: string;
|
|
25
|
+
/**
|
|
26
|
+
* Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,
|
|
27
|
+
* always 0x-prefixed. The exact format varies by chain (see the auth API
|
|
28
|
+
* spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,
|
|
29
|
+
* BIP-322 for `bip122`).
|
|
30
|
+
*/
|
|
31
|
+
signature: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* An account address along with its associated scopes and an optional
|
|
35
|
+
* ownership proof.
|
|
14
36
|
*/
|
|
15
37
|
export type AccountWithScopes = {
|
|
16
38
|
address: string;
|
|
17
39
|
scopes: `${string}:${string}`[];
|
|
40
|
+
proof?: AccountOwnershipProof;
|
|
18
41
|
};
|
|
19
42
|
/**
|
|
20
43
|
* The shape of the request object for submitting metrics.
|
|
@@ -24,6 +47,28 @@ export type ProfileMetricsSubmitMetricsRequest = {
|
|
|
24
47
|
entropySourceId?: string | null;
|
|
25
48
|
accounts: AccountWithScopes[];
|
|
26
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The shape of the request object for fetching a batch of single-use nonces.
|
|
52
|
+
*/
|
|
53
|
+
export type ProfileMetricsFetchNoncesRequest = {
|
|
54
|
+
/**
|
|
55
|
+
* The identifiers (canonical addresses) to mint a nonce for. The auth API
|
|
56
|
+
* accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.
|
|
57
|
+
*/
|
|
58
|
+
identifiers: string[];
|
|
59
|
+
/**
|
|
60
|
+
* The entropy source ID to use when fetching a bearer token. Pass `null` or
|
|
61
|
+
* omit for accounts that do not belong to any entropy source.
|
|
62
|
+
*/
|
|
63
|
+
entropySourceId?: string | null;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Maximum number of identifiers the auth API will mint nonces for in a single
|
|
67
|
+
* `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}
|
|
68
|
+
* uses this as the chunk size when the caller requests more than this many
|
|
69
|
+
* nonces at once.
|
|
70
|
+
*/
|
|
71
|
+
export declare const MAX_NONCE_BATCH_SIZE = 50;
|
|
27
72
|
/**
|
|
28
73
|
* Actions that {@link ProfileMetricsService} exposes to other consumers.
|
|
29
74
|
*/
|
|
@@ -113,6 +158,28 @@ export declare class ProfileMetricsService {
|
|
|
113
158
|
* {@link CockatielEvent}.
|
|
114
159
|
*/
|
|
115
160
|
onDegraded(listener: Parameters<ServicePolicy['onDegraded']>[0]): IDisposable;
|
|
161
|
+
/**
|
|
162
|
+
* Fetch single-use nonces from the auth API, one per identifier.
|
|
163
|
+
*
|
|
164
|
+
* Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple
|
|
165
|
+
* `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are
|
|
166
|
+
* merged into a single record. Each chunk independently goes through the
|
|
167
|
+
* service policy (retry, circuit-breaker, degraded). If any chunk ultimately
|
|
168
|
+
* fails, the whole call rejects so the caller can soft-degrade the entire
|
|
169
|
+
* entropy-source batch consistently.
|
|
170
|
+
*
|
|
171
|
+
* The returned record is keyed by the auth API's echoed `identifier` field
|
|
172
|
+
* (`response[i].identifier -> response[i].nonce`). The call asserts that
|
|
173
|
+
* the response identifier set is exactly the requested set; any mismatch
|
|
174
|
+
* (missing, extra, or duplicated identifier) causes the chunk to throw so
|
|
175
|
+
* the caller never silently proceeds with partial nonces.
|
|
176
|
+
*
|
|
177
|
+
* @param data - The identifiers to mint nonces for, plus the optional
|
|
178
|
+
* entropy source ID used to scope the bearer token.
|
|
179
|
+
* @returns A map of identifier -> nonce.
|
|
180
|
+
* @throws {RangeError} if no identifiers are provided.
|
|
181
|
+
*/
|
|
182
|
+
fetchNonces(data: ProfileMetricsFetchNoncesRequest): Promise<Record<string, string>>;
|
|
116
183
|
/**
|
|
117
184
|
* Submit metrics to the API.
|
|
118
185
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,EAAE,GAAG,EAAE,0CAA0C;AACxD,OAAO,KAAK,EAAE,wBAAwB,EAAE,0CAA0C;
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,11 +10,34 @@ import type { ProfileMetricsServiceMethodActions } from "./index.mjs";
|
|
|
10
10
|
*/
|
|
11
11
|
export declare const serviceName = "ProfileMetricsService";
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* A cryptographic proof that the caller controls the private key of an
|
|
14
|
+
* account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the
|
|
15
|
+
* auth API. When present, the server verifies the signature against
|
|
16
|
+
* `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently
|
|
17
|
+
* marks the account as `verified: true`.
|
|
18
|
+
*/
|
|
19
|
+
export type AccountOwnershipProof = {
|
|
20
|
+
/**
|
|
21
|
+
* Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.
|
|
22
|
+
* Consumed by the server on verification; replay is not possible.
|
|
23
|
+
*/
|
|
24
|
+
nonce: string;
|
|
25
|
+
/**
|
|
26
|
+
* Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,
|
|
27
|
+
* always 0x-prefixed. The exact format varies by chain (see the auth API
|
|
28
|
+
* spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,
|
|
29
|
+
* BIP-322 for `bip122`).
|
|
30
|
+
*/
|
|
31
|
+
signature: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* An account address along with its associated scopes and an optional
|
|
35
|
+
* ownership proof.
|
|
14
36
|
*/
|
|
15
37
|
export type AccountWithScopes = {
|
|
16
38
|
address: string;
|
|
17
39
|
scopes: `${string}:${string}`[];
|
|
40
|
+
proof?: AccountOwnershipProof;
|
|
18
41
|
};
|
|
19
42
|
/**
|
|
20
43
|
* The shape of the request object for submitting metrics.
|
|
@@ -24,6 +47,28 @@ export type ProfileMetricsSubmitMetricsRequest = {
|
|
|
24
47
|
entropySourceId?: string | null;
|
|
25
48
|
accounts: AccountWithScopes[];
|
|
26
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The shape of the request object for fetching a batch of single-use nonces.
|
|
52
|
+
*/
|
|
53
|
+
export type ProfileMetricsFetchNoncesRequest = {
|
|
54
|
+
/**
|
|
55
|
+
* The identifiers (canonical addresses) to mint a nonce for. The auth API
|
|
56
|
+
* accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.
|
|
57
|
+
*/
|
|
58
|
+
identifiers: string[];
|
|
59
|
+
/**
|
|
60
|
+
* The entropy source ID to use when fetching a bearer token. Pass `null` or
|
|
61
|
+
* omit for accounts that do not belong to any entropy source.
|
|
62
|
+
*/
|
|
63
|
+
entropySourceId?: string | null;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Maximum number of identifiers the auth API will mint nonces for in a single
|
|
67
|
+
* `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}
|
|
68
|
+
* uses this as the chunk size when the caller requests more than this many
|
|
69
|
+
* nonces at once.
|
|
70
|
+
*/
|
|
71
|
+
export declare const MAX_NONCE_BATCH_SIZE = 50;
|
|
27
72
|
/**
|
|
28
73
|
* Actions that {@link ProfileMetricsService} exposes to other consumers.
|
|
29
74
|
*/
|
|
@@ -113,6 +158,28 @@ export declare class ProfileMetricsService {
|
|
|
113
158
|
* {@link CockatielEvent}.
|
|
114
159
|
*/
|
|
115
160
|
onDegraded(listener: Parameters<ServicePolicy['onDegraded']>[0]): IDisposable;
|
|
161
|
+
/**
|
|
162
|
+
* Fetch single-use nonces from the auth API, one per identifier.
|
|
163
|
+
*
|
|
164
|
+
* Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple
|
|
165
|
+
* `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are
|
|
166
|
+
* merged into a single record. Each chunk independently goes through the
|
|
167
|
+
* service policy (retry, circuit-breaker, degraded). If any chunk ultimately
|
|
168
|
+
* fails, the whole call rejects so the caller can soft-degrade the entire
|
|
169
|
+
* entropy-source batch consistently.
|
|
170
|
+
*
|
|
171
|
+
* The returned record is keyed by the auth API's echoed `identifier` field
|
|
172
|
+
* (`response[i].identifier -> response[i].nonce`). The call asserts that
|
|
173
|
+
* the response identifier set is exactly the requested set; any mismatch
|
|
174
|
+
* (missing, extra, or duplicated identifier) causes the chunk to throw so
|
|
175
|
+
* the caller never silently proceeds with partial nonces.
|
|
176
|
+
*
|
|
177
|
+
* @param data - The identifiers to mint nonces for, plus the optional
|
|
178
|
+
* entropy source ID used to scope the bearer token.
|
|
179
|
+
* @returns A map of identifier -> nonce.
|
|
180
|
+
* @throws {RangeError} if no identifiers are provided.
|
|
181
|
+
*/
|
|
182
|
+
fetchNonces(data: ProfileMetricsFetchNoncesRequest): Promise<Record<string, string>>;
|
|
116
183
|
/**
|
|
117
184
|
* Submit metrics to the API.
|
|
118
185
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,EAAE,GAAG,EAAE,0CAA0C;AACxD,OAAO,KAAK,EAAE,wBAAwB,EAAE,0CAA0C;
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,17 +9,38 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
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
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _ProfileMetricsService_baseURL;
|
|
12
|
+
var _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _ProfileMetricsService_baseURL, _ProfileMetricsService_fetchNoncesChunk;
|
|
13
13
|
import { createServicePolicy, HttpError } from "@metamask/controller-utils";
|
|
14
14
|
import { SDK } from "@metamask/profile-sync-controller";
|
|
15
|
+
import { array, number, string, type as structType } from "@metamask/superstruct";
|
|
16
|
+
/**
|
|
17
|
+
* The shape of an entry in the `POST /api/v2/nonce/batch` response body.
|
|
18
|
+
*
|
|
19
|
+
* `identifier` echoes the request identifier verbatim, mirroring the
|
|
20
|
+
* documented behavior of the single-account `GET /api/v2/nonce` endpoint on
|
|
21
|
+
* the same auth service. Defined with `type()` (not `object()`) so the
|
|
22
|
+
* client tolerates additive server-side schema changes.
|
|
23
|
+
*/
|
|
24
|
+
const NonceBatchResponseStruct = array(structType({
|
|
25
|
+
expires_in: number(),
|
|
26
|
+
identifier: string(),
|
|
27
|
+
nonce: string(),
|
|
28
|
+
}));
|
|
15
29
|
// === GENERAL ===
|
|
16
30
|
/**
|
|
17
31
|
* The name of the {@link ProfileMetricsService}, used to namespace the
|
|
18
32
|
* service's actions and events.
|
|
19
33
|
*/
|
|
20
34
|
export const serviceName = 'ProfileMetricsService';
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of identifiers the auth API will mint nonces for in a single
|
|
37
|
+
* `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}
|
|
38
|
+
* uses this as the chunk size when the caller requests more than this many
|
|
39
|
+
* nonces at once.
|
|
40
|
+
*/
|
|
41
|
+
export const MAX_NONCE_BATCH_SIZE = 50;
|
|
21
42
|
// === MESSENGER ===
|
|
22
|
-
const MESSENGER_EXPOSED_METHODS = ['submitMetrics'];
|
|
43
|
+
const MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'];
|
|
23
44
|
// === SERVICE DEFINITION ===
|
|
24
45
|
/**
|
|
25
46
|
* A service for submitting user profile metrics (metrics ID and accounts).
|
|
@@ -39,6 +60,7 @@ export class ProfileMetricsService {
|
|
|
39
60
|
* @param args.env - The environment to determine the correct API endpoints.
|
|
40
61
|
*/
|
|
41
62
|
constructor({ messenger, fetch: fetchFunction, policyOptions = {}, env = SDK.Env.DEV, }) {
|
|
63
|
+
_ProfileMetricsService_instances.add(this);
|
|
42
64
|
/**
|
|
43
65
|
* The messenger suited for this service.
|
|
44
66
|
*/
|
|
@@ -109,6 +131,38 @@ export class ProfileMetricsService {
|
|
|
109
131
|
onDegraded(listener) {
|
|
110
132
|
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onDegraded(listener);
|
|
111
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Fetch single-use nonces from the auth API, one per identifier.
|
|
136
|
+
*
|
|
137
|
+
* Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple
|
|
138
|
+
* `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are
|
|
139
|
+
* merged into a single record. Each chunk independently goes through the
|
|
140
|
+
* service policy (retry, circuit-breaker, degraded). If any chunk ultimately
|
|
141
|
+
* fails, the whole call rejects so the caller can soft-degrade the entire
|
|
142
|
+
* entropy-source batch consistently.
|
|
143
|
+
*
|
|
144
|
+
* The returned record is keyed by the auth API's echoed `identifier` field
|
|
145
|
+
* (`response[i].identifier -> response[i].nonce`). The call asserts that
|
|
146
|
+
* the response identifier set is exactly the requested set; any mismatch
|
|
147
|
+
* (missing, extra, or duplicated identifier) causes the chunk to throw so
|
|
148
|
+
* the caller never silently proceeds with partial nonces.
|
|
149
|
+
*
|
|
150
|
+
* @param data - The identifiers to mint nonces for, plus the optional
|
|
151
|
+
* entropy source ID used to scope the bearer token.
|
|
152
|
+
* @returns A map of identifier -> nonce.
|
|
153
|
+
* @throws {RangeError} if no identifiers are provided.
|
|
154
|
+
*/
|
|
155
|
+
async fetchNonces(data) {
|
|
156
|
+
if (data.identifiers.length === 0) {
|
|
157
|
+
throw new RangeError('ProfileMetricsService.fetchNonces requires at least 1 identifier.');
|
|
158
|
+
}
|
|
159
|
+
const chunks = [];
|
|
160
|
+
for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {
|
|
161
|
+
chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));
|
|
162
|
+
}
|
|
163
|
+
const chunkResults = await Promise.all(chunks.map((identifiers) => __classPrivateFieldGet(this, _ProfileMetricsService_instances, "m", _ProfileMetricsService_fetchNoncesChunk).call(this, identifiers, data.entropySourceId)));
|
|
164
|
+
return Object.assign({}, ...chunkResults);
|
|
165
|
+
}
|
|
112
166
|
/**
|
|
113
167
|
* Submit metrics to the API.
|
|
114
168
|
*
|
|
@@ -142,7 +196,49 @@ export class ProfileMetricsService {
|
|
|
142
196
|
});
|
|
143
197
|
}
|
|
144
198
|
}
|
|
145
|
-
_a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_policy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap()
|
|
199
|
+
_a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_policy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap(), _ProfileMetricsService_instances = new WeakSet(), _ProfileMetricsService_fetchNoncesChunk =
|
|
200
|
+
/**
|
|
201
|
+
* Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of
|
|
202
|
+
* identifiers. Wrapped in {@link #policy} for retry / degraded / circuit
|
|
203
|
+
* semantics consistent with the rest of the service.
|
|
204
|
+
*
|
|
205
|
+
* @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.
|
|
206
|
+
* @param entropySourceId - The entropy source ID forwarded to the bearer
|
|
207
|
+
* token resolver.
|
|
208
|
+
* @returns A map of identifier -> nonce for this chunk.
|
|
209
|
+
*/
|
|
210
|
+
async function _ProfileMetricsService_fetchNoncesChunk(identifiers, entropySourceId) {
|
|
211
|
+
return await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
|
|
212
|
+
const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', entropySourceId ?? undefined);
|
|
213
|
+
const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/nonce/batch`);
|
|
214
|
+
const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: {
|
|
217
|
+
Authorization: `Bearer ${authToken}`,
|
|
218
|
+
'Content-Type': 'application/json',
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({ identifiers }),
|
|
221
|
+
credentials: 'omit',
|
|
222
|
+
});
|
|
223
|
+
if (!localResponse.ok) {
|
|
224
|
+
throw new HttpError(localResponse.status, `Fetching '${url.toString()}' failed with status '${localResponse.status}'`);
|
|
225
|
+
}
|
|
226
|
+
const body = await localResponse.json();
|
|
227
|
+
if (!NonceBatchResponseStruct.is(body)) {
|
|
228
|
+
throw new Error(`Malformed response received from '${url.toString()}'`);
|
|
229
|
+
}
|
|
230
|
+
const result = {};
|
|
231
|
+
for (const entry of body) {
|
|
232
|
+
result[entry.identifier] = entry.nonce;
|
|
233
|
+
}
|
|
234
|
+
const echoesRequest = body.length === identifiers.length &&
|
|
235
|
+
identifiers.every((id) => Object.prototype.hasOwnProperty.call(result, id));
|
|
236
|
+
if (!echoesRequest) {
|
|
237
|
+
throw new Error(`Fetching '${url.toString()}' returned a response whose identifier set does not match the request`);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
});
|
|
241
|
+
};
|
|
146
242
|
/**
|
|
147
243
|
* Returns the base URL for the given environment.
|
|
148
244
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * An account address along with its associated scopes.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * The policy that wraps the request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#policy = createServicePolicy(policyOptions);\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport {\n array,\n number,\n string,\n type as structType,\n} from '@metamask/superstruct';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\n\n/**\n * The shape of an entry in the `POST /api/v2/nonce/batch` response body.\n *\n * `identifier` echoes the request identifier verbatim, mirroring the\n * documented behavior of the single-account `GET /api/v2/nonce` endpoint on\n * the same auth service. Defined with `type()` (not `object()`) so the\n * client tolerates additive server-side schema changes.\n */\nconst NonceBatchResponseStruct = array(\n structType({\n expires_in: number(),\n identifier: string(),\n nonce: string(),\n }),\n);\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * A cryptographic proof that the caller controls the private key of an\n * account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the\n * auth API. When present, the server verifies the signature against\n * `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently\n * marks the account as `verified: true`.\n */\nexport type AccountOwnershipProof = {\n /**\n * Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.\n * Consumed by the server on verification; replay is not possible.\n */\n nonce: string;\n /**\n * Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,\n * always 0x-prefixed. The exact format varies by chain (see the auth API\n * spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,\n * BIP-322 for `bip122`).\n */\n signature: string;\n};\n\n/**\n * An account address along with its associated scopes and an optional\n * ownership proof.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n proof?: AccountOwnershipProof;\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n/**\n * The shape of the request object for fetching a batch of single-use nonces.\n */\nexport type ProfileMetricsFetchNoncesRequest = {\n /**\n * The identifiers (canonical addresses) to mint a nonce for. The auth API\n * accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.\n */\n identifiers: string[];\n /**\n * The entropy source ID to use when fetching a bearer token. Pass `null` or\n * omit for accounts that do not belong to any entropy source.\n */\n entropySourceId?: string | null;\n};\n\n/**\n * Maximum number of identifiers the auth API will mint nonces for in a single\n * `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}\n * uses this as the chunk size when the caller requests more than this many\n * nonces at once.\n */\nexport const MAX_NONCE_BATCH_SIZE = 50;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * The policy that wraps the request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#policy = createServicePolicy(policyOptions);\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\n async fetchNonces(\n data: ProfileMetricsFetchNoncesRequest,\n ): Promise<Record<string, string>> {\n if (data.identifiers.length === 0) {\n throw new RangeError(\n 'ProfileMetricsService.fetchNonces requires at least 1 identifier.',\n );\n }\n const chunks: string[][] = [];\n for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {\n chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));\n }\n const chunkResults = await Promise.all(\n chunks.map((identifiers) =>\n this.#fetchNoncesChunk(identifiers, data.entropySourceId),\n ),\n );\n return Object.assign({}, ...chunkResults);\n }\n\n /**\n * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of\n * identifiers. Wrapped in {@link #policy} for retry / degraded / circuit\n * semantics consistent with the rest of the service.\n *\n * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.\n * @param entropySourceId - The entropy source ID forwarded to the bearer\n * token resolver.\n * @returns A map of identifier -> nonce for this chunk.\n */\n async #fetchNoncesChunk(\n identifiers: string[],\n entropySourceId: string | null | undefined,\n ): Promise<Record<string, string>> {\n return await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/nonce/batch`);\n const localResponse = await this.#fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ identifiers }),\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n const body: unknown = await localResponse.json();\n if (!NonceBatchResponseStruct.is(body)) {\n throw new Error(`Malformed response received from '${url.toString()}'`);\n }\n const result: Record<string, string> = {};\n for (const entry of body) {\n result[entry.identifier] = entry.nonce;\n }\n const echoesRequest =\n body.length === identifiers.length &&\n identifiers.every((id) =>\n Object.prototype.hasOwnProperty.call(result, id),\n );\n if (!echoesRequest) {\n throw new Error(\n `Fetching '${url.toString()}' returned a response whose identifier set does not match the request`,\n );\n }\n return result;\n });\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalizeAddress = exports.ProofUnsupportedNamespaceError = void 0;
|
|
4
|
+
const controller_utils_1 = require("@metamask/controller-utils");
|
|
5
|
+
const utils_1 = require("@metamask/utils");
|
|
6
|
+
/**
|
|
7
|
+
* Bitcoin bech32 / bech32m address prefixes that we lowercase per the auth
|
|
8
|
+
* API canonicalization rules. The prefix is the network identifier (`bc`,
|
|
9
|
+
* `tb`, `bcrt`) plus the bech32 separator (`1`); matching against this form
|
|
10
|
+
* pins the check to actual bech32 addresses and avoids accidentally
|
|
11
|
+
* matching legacy base58check addresses that happen to start with the same
|
|
12
|
+
* letters. Both segwit (`…q…`) and taproot (`…p…`) variants are subsumed.
|
|
13
|
+
* Legacy base58check P2PKH addresses (mainnet `1…`, testnet `m…`/`n…`) are
|
|
14
|
+
* case-sensitive and intentionally not in this list.
|
|
15
|
+
*
|
|
16
|
+
* Today the wallet only creates `BtcScope.Mainnet` accounts, but the
|
|
17
|
+
* non-mainnet prefixes are kept here as cheap forward-compat: per the auth
|
|
18
|
+
* API spec the lowercase rule is shape-based, not network-based.
|
|
19
|
+
*/
|
|
20
|
+
const BECH32_BITCOIN_ADDRESS_PREFIXES = ['bc1', 'tb1', 'bcrt1'];
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when {@link canonicalizeAddress} is given a namespace it does
|
|
23
|
+
* not know how to handle.
|
|
24
|
+
* Callers in the polling pipeline use this to fall back to submitting the
|
|
25
|
+
* account without a proof rather than blocking the batch.
|
|
26
|
+
*/
|
|
27
|
+
class ProofUnsupportedNamespaceError extends Error {
|
|
28
|
+
constructor(namespace) {
|
|
29
|
+
super(`Proof of ownership is not supported for namespace '${namespace}'.`);
|
|
30
|
+
this.name = 'ProofUnsupportedNamespaceError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ProofUnsupportedNamespaceError = ProofUnsupportedNamespaceError;
|
|
34
|
+
/**
|
|
35
|
+
* Returns the address in the canonical encoding the auth API expects for the
|
|
36
|
+
* given CAIP-2 namespace.
|
|
37
|
+
*
|
|
38
|
+
* Encoding rules (per the `PUT /api/v2/profile/accounts` spec):
|
|
39
|
+
*
|
|
40
|
+
* - `eip155` — EIP-55 mixed-case hex checksum.
|
|
41
|
+
* - `solana`, `tron` — base58 / base58check, single canonical encoding; returned
|
|
42
|
+
* as-is (the server rejects malformed inputs with 400).
|
|
43
|
+
* - `bip122` — bech32 / bech32m addresses (mainnet `bc1…`, testnet
|
|
44
|
+
* `tb1…`, regtest `bcrt1…`) must be all-lowercase; legacy base58check
|
|
45
|
+
* P2PKH addresses (`1…`, `m…`, `n…`) are accepted as-is.
|
|
46
|
+
*
|
|
47
|
+
* @param address - The address to canonicalize.
|
|
48
|
+
* @param namespace - The CAIP-2 namespace of the chain the address belongs to.
|
|
49
|
+
* @returns The address in its canonical form for `namespace`.
|
|
50
|
+
* @throws {ProofUnsupportedNamespaceError} if `namespace` is not one of
|
|
51
|
+
* `eip155`, `solana`, `tron`, or `bip122`.
|
|
52
|
+
*/
|
|
53
|
+
function canonicalizeAddress(address, namespace) {
|
|
54
|
+
switch (namespace) {
|
|
55
|
+
case utils_1.KnownCaipNamespace.Eip155:
|
|
56
|
+
return (0, controller_utils_1.toChecksumHexAddress)(address);
|
|
57
|
+
case utils_1.KnownCaipNamespace.Solana:
|
|
58
|
+
case utils_1.KnownCaipNamespace.Tron:
|
|
59
|
+
return address;
|
|
60
|
+
case utils_1.KnownCaipNamespace.Bip122: {
|
|
61
|
+
const lowercased = address.toLowerCase();
|
|
62
|
+
if (BECH32_BITCOIN_ADDRESS_PREFIXES.some((prefix) => lowercased.startsWith(prefix))) {
|
|
63
|
+
return lowercased;
|
|
64
|
+
}
|
|
65
|
+
return address;
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
throw new ProofUnsupportedNamespaceError(namespace);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.canonicalizeAddress = canonicalizeAddress;
|
|
72
|
+
//# sourceMappingURL=canonicalize.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.cjs","sourceRoot":"","sources":["../../src/utils/canonicalize.ts"],"names":[],"mappings":";;;AAAA,iEAAkE;AAClE,2CAAqD;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,+BAA+B,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;AAEzE;;;;;GAKG;AACH,MAAa,8BAA+B,SAAQ,KAAK;IACvD,YAAY,SAAiB;QAC3B,KAAK,CAAC,sDAAsD,SAAS,IAAI,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF;AALD,wEAKC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,mBAAmB,CACjC,OAAe,EACf,SAAiB;IAEjB,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,0BAAkB,CAAC,MAAM;YAC5B,OAAO,IAAA,uCAAoB,EAAC,OAAO,CAAC,CAAC;QACvC,KAAK,0BAAkB,CAAC,MAAM,CAAC;QAC/B,KAAK,0BAAkB,CAAC,IAAI;YAC1B,OAAO,OAAO,CAAC;QACjB,KAAK,0BAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;YACzC,IACE,+BAA+B,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAC9C,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAC9B,EACD,CAAC;gBACD,OAAO,UAAU,CAAC;YACpB,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD;YACE,MAAM,IAAI,8BAA8B,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAxBD,kDAwBC","sourcesContent":["import { toChecksumHexAddress } from '@metamask/controller-utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\n\n/**\n * Bitcoin bech32 / bech32m address prefixes that we lowercase per the auth\n * API canonicalization rules. The prefix is the network identifier (`bc`,\n * `tb`, `bcrt`) plus the bech32 separator (`1`); matching against this form\n * pins the check to actual bech32 addresses and avoids accidentally\n * matching legacy base58check addresses that happen to start with the same\n * letters. Both segwit (`…q…`) and taproot (`…p…`) variants are subsumed.\n * Legacy base58check P2PKH addresses (mainnet `1…`, testnet `m…`/`n…`) are\n * case-sensitive and intentionally not in this list.\n *\n * Today the wallet only creates `BtcScope.Mainnet` accounts, but the\n * non-mainnet prefixes are kept here as cheap forward-compat: per the auth\n * API spec the lowercase rule is shape-based, not network-based.\n */\nconst BECH32_BITCOIN_ADDRESS_PREFIXES = ['bc1', 'tb1', 'bcrt1'] as const;\n\n/**\n * Thrown when {@link canonicalizeAddress} is given a namespace it does\n * not know how to handle.\n * Callers in the polling pipeline use this to fall back to submitting the\n * account without a proof rather than blocking the batch.\n */\nexport class ProofUnsupportedNamespaceError extends Error {\n constructor(namespace: string) {\n super(`Proof of ownership is not supported for namespace '${namespace}'.`);\n this.name = 'ProofUnsupportedNamespaceError';\n }\n}\n\n/**\n * Returns the address in the canonical encoding the auth API expects for the\n * given CAIP-2 namespace.\n *\n * Encoding rules (per the `PUT /api/v2/profile/accounts` spec):\n *\n * - `eip155` — EIP-55 mixed-case hex checksum.\n * - `solana`, `tron` — base58 / base58check, single canonical encoding; returned\n * as-is (the server rejects malformed inputs with 400).\n * - `bip122` — bech32 / bech32m addresses (mainnet `bc1…`, testnet\n * `tb1…`, regtest `bcrt1…`) must be all-lowercase; legacy base58check\n * P2PKH addresses (`1…`, `m…`, `n…`) are accepted as-is.\n *\n * @param address - The address to canonicalize.\n * @param namespace - The CAIP-2 namespace of the chain the address belongs to.\n * @returns The address in its canonical form for `namespace`.\n * @throws {ProofUnsupportedNamespaceError} if `namespace` is not one of\n * `eip155`, `solana`, `tron`, or `bip122`.\n */\nexport function canonicalizeAddress(\n address: string,\n namespace: string,\n): string {\n switch (namespace) {\n case KnownCaipNamespace.Eip155:\n return toChecksumHexAddress(address);\n case KnownCaipNamespace.Solana:\n case KnownCaipNamespace.Tron:\n return address;\n case KnownCaipNamespace.Bip122: {\n const lowercased = address.toLowerCase();\n if (\n BECH32_BITCOIN_ADDRESS_PREFIXES.some((prefix) =>\n lowercased.startsWith(prefix),\n )\n ) {\n return lowercased;\n }\n return address;\n }\n default:\n throw new ProofUnsupportedNamespaceError(namespace);\n }\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when {@link canonicalizeAddress} is given a namespace it does
|
|
3
|
+
* not know how to handle.
|
|
4
|
+
* Callers in the polling pipeline use this to fall back to submitting the
|
|
5
|
+
* account without a proof rather than blocking the batch.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ProofUnsupportedNamespaceError extends Error {
|
|
8
|
+
constructor(namespace: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns the address in the canonical encoding the auth API expects for the
|
|
12
|
+
* given CAIP-2 namespace.
|
|
13
|
+
*
|
|
14
|
+
* Encoding rules (per the `PUT /api/v2/profile/accounts` spec):
|
|
15
|
+
*
|
|
16
|
+
* - `eip155` — EIP-55 mixed-case hex checksum.
|
|
17
|
+
* - `solana`, `tron` — base58 / base58check, single canonical encoding; returned
|
|
18
|
+
* as-is (the server rejects malformed inputs with 400).
|
|
19
|
+
* - `bip122` — bech32 / bech32m addresses (mainnet `bc1…`, testnet
|
|
20
|
+
* `tb1…`, regtest `bcrt1…`) must be all-lowercase; legacy base58check
|
|
21
|
+
* P2PKH addresses (`1…`, `m…`, `n…`) are accepted as-is.
|
|
22
|
+
*
|
|
23
|
+
* @param address - The address to canonicalize.
|
|
24
|
+
* @param namespace - The CAIP-2 namespace of the chain the address belongs to.
|
|
25
|
+
* @returns The address in its canonical form for `namespace`.
|
|
26
|
+
* @throws {ProofUnsupportedNamespaceError} if `namespace` is not one of
|
|
27
|
+
* `eip155`, `solana`, `tron`, or `bip122`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function canonicalizeAddress(address: string, namespace: string): string;
|
|
30
|
+
//# sourceMappingURL=canonicalize.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.d.cts","sourceRoot":"","sources":["../../src/utils/canonicalize.ts"],"names":[],"mappings":"AAmBA;;;;;GAKG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,SAAS,EAAE,MAAM;CAI9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM,CAqBR"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when {@link canonicalizeAddress} is given a namespace it does
|
|
3
|
+
* not know how to handle.
|
|
4
|
+
* Callers in the polling pipeline use this to fall back to submitting the
|
|
5
|
+
* account without a proof rather than blocking the batch.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ProofUnsupportedNamespaceError extends Error {
|
|
8
|
+
constructor(namespace: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns the address in the canonical encoding the auth API expects for the
|
|
12
|
+
* given CAIP-2 namespace.
|
|
13
|
+
*
|
|
14
|
+
* Encoding rules (per the `PUT /api/v2/profile/accounts` spec):
|
|
15
|
+
*
|
|
16
|
+
* - `eip155` — EIP-55 mixed-case hex checksum.
|
|
17
|
+
* - `solana`, `tron` — base58 / base58check, single canonical encoding; returned
|
|
18
|
+
* as-is (the server rejects malformed inputs with 400).
|
|
19
|
+
* - `bip122` — bech32 / bech32m addresses (mainnet `bc1…`, testnet
|
|
20
|
+
* `tb1…`, regtest `bcrt1…`) must be all-lowercase; legacy base58check
|
|
21
|
+
* P2PKH addresses (`1…`, `m…`, `n…`) are accepted as-is.
|
|
22
|
+
*
|
|
23
|
+
* @param address - The address to canonicalize.
|
|
24
|
+
* @param namespace - The CAIP-2 namespace of the chain the address belongs to.
|
|
25
|
+
* @returns The address in its canonical form for `namespace`.
|
|
26
|
+
* @throws {ProofUnsupportedNamespaceError} if `namespace` is not one of
|
|
27
|
+
* `eip155`, `solana`, `tron`, or `bip122`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function canonicalizeAddress(address: string, namespace: string): string;
|
|
30
|
+
//# sourceMappingURL=canonicalize.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.d.mts","sourceRoot":"","sources":["../../src/utils/canonicalize.ts"],"names":[],"mappings":"AAmBA;;;;;GAKG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,SAAS,EAAE,MAAM;CAI9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM,CAqBR"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { toChecksumHexAddress } from "@metamask/controller-utils";
|
|
2
|
+
import { KnownCaipNamespace } from "@metamask/utils";
|
|
3
|
+
/**
|
|
4
|
+
* Bitcoin bech32 / bech32m address prefixes that we lowercase per the auth
|
|
5
|
+
* API canonicalization rules. The prefix is the network identifier (`bc`,
|
|
6
|
+
* `tb`, `bcrt`) plus the bech32 separator (`1`); matching against this form
|
|
7
|
+
* pins the check to actual bech32 addresses and avoids accidentally
|
|
8
|
+
* matching legacy base58check addresses that happen to start with the same
|
|
9
|
+
* letters. Both segwit (`…q…`) and taproot (`…p…`) variants are subsumed.
|
|
10
|
+
* Legacy base58check P2PKH addresses (mainnet `1…`, testnet `m…`/`n…`) are
|
|
11
|
+
* case-sensitive and intentionally not in this list.
|
|
12
|
+
*
|
|
13
|
+
* Today the wallet only creates `BtcScope.Mainnet` accounts, but the
|
|
14
|
+
* non-mainnet prefixes are kept here as cheap forward-compat: per the auth
|
|
15
|
+
* API spec the lowercase rule is shape-based, not network-based.
|
|
16
|
+
*/
|
|
17
|
+
const BECH32_BITCOIN_ADDRESS_PREFIXES = ['bc1', 'tb1', 'bcrt1'];
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when {@link canonicalizeAddress} is given a namespace it does
|
|
20
|
+
* not know how to handle.
|
|
21
|
+
* Callers in the polling pipeline use this to fall back to submitting the
|
|
22
|
+
* account without a proof rather than blocking the batch.
|
|
23
|
+
*/
|
|
24
|
+
export class ProofUnsupportedNamespaceError extends Error {
|
|
25
|
+
constructor(namespace) {
|
|
26
|
+
super(`Proof of ownership is not supported for namespace '${namespace}'.`);
|
|
27
|
+
this.name = 'ProofUnsupportedNamespaceError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the address in the canonical encoding the auth API expects for the
|
|
32
|
+
* given CAIP-2 namespace.
|
|
33
|
+
*
|
|
34
|
+
* Encoding rules (per the `PUT /api/v2/profile/accounts` spec):
|
|
35
|
+
*
|
|
36
|
+
* - `eip155` — EIP-55 mixed-case hex checksum.
|
|
37
|
+
* - `solana`, `tron` — base58 / base58check, single canonical encoding; returned
|
|
38
|
+
* as-is (the server rejects malformed inputs with 400).
|
|
39
|
+
* - `bip122` — bech32 / bech32m addresses (mainnet `bc1…`, testnet
|
|
40
|
+
* `tb1…`, regtest `bcrt1…`) must be all-lowercase; legacy base58check
|
|
41
|
+
* P2PKH addresses (`1…`, `m…`, `n…`) are accepted as-is.
|
|
42
|
+
*
|
|
43
|
+
* @param address - The address to canonicalize.
|
|
44
|
+
* @param namespace - The CAIP-2 namespace of the chain the address belongs to.
|
|
45
|
+
* @returns The address in its canonical form for `namespace`.
|
|
46
|
+
* @throws {ProofUnsupportedNamespaceError} if `namespace` is not one of
|
|
47
|
+
* `eip155`, `solana`, `tron`, or `bip122`.
|
|
48
|
+
*/
|
|
49
|
+
export function canonicalizeAddress(address, namespace) {
|
|
50
|
+
switch (namespace) {
|
|
51
|
+
case KnownCaipNamespace.Eip155:
|
|
52
|
+
return toChecksumHexAddress(address);
|
|
53
|
+
case KnownCaipNamespace.Solana:
|
|
54
|
+
case KnownCaipNamespace.Tron:
|
|
55
|
+
return address;
|
|
56
|
+
case KnownCaipNamespace.Bip122: {
|
|
57
|
+
const lowercased = address.toLowerCase();
|
|
58
|
+
if (BECH32_BITCOIN_ADDRESS_PREFIXES.some((prefix) => lowercased.startsWith(prefix))) {
|
|
59
|
+
return lowercased;
|
|
60
|
+
}
|
|
61
|
+
return address;
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
throw new ProofUnsupportedNamespaceError(namespace);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=canonicalize.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.mjs","sourceRoot":"","sources":["../../src/utils/canonicalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,mCAAmC;AAClE,OAAO,EAAE,kBAAkB,EAAE,wBAAwB;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,+BAA+B,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IACvD,YAAY,SAAiB;QAC3B,KAAK,CAAC,sDAAsD,SAAS,IAAI,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,SAAiB;IAEjB,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,kBAAkB,CAAC,MAAM;YAC5B,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACvC,KAAK,kBAAkB,CAAC,MAAM,CAAC;QAC/B,KAAK,kBAAkB,CAAC,IAAI;YAC1B,OAAO,OAAO,CAAC;QACjB,KAAK,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;YACzC,IACE,+BAA+B,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAC9C,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAC9B,EACD,CAAC;gBACD,OAAO,UAAU,CAAC;YACpB,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD;YACE,MAAM,IAAI,8BAA8B,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC;AACH,CAAC","sourcesContent":["import { toChecksumHexAddress } from '@metamask/controller-utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\n\n/**\n * Bitcoin bech32 / bech32m address prefixes that we lowercase per the auth\n * API canonicalization rules. The prefix is the network identifier (`bc`,\n * `tb`, `bcrt`) plus the bech32 separator (`1`); matching against this form\n * pins the check to actual bech32 addresses and avoids accidentally\n * matching legacy base58check addresses that happen to start with the same\n * letters. Both segwit (`…q…`) and taproot (`…p…`) variants are subsumed.\n * Legacy base58check P2PKH addresses (mainnet `1…`, testnet `m…`/`n…`) are\n * case-sensitive and intentionally not in this list.\n *\n * Today the wallet only creates `BtcScope.Mainnet` accounts, but the\n * non-mainnet prefixes are kept here as cheap forward-compat: per the auth\n * API spec the lowercase rule is shape-based, not network-based.\n */\nconst BECH32_BITCOIN_ADDRESS_PREFIXES = ['bc1', 'tb1', 'bcrt1'] as const;\n\n/**\n * Thrown when {@link canonicalizeAddress} is given a namespace it does\n * not know how to handle.\n * Callers in the polling pipeline use this to fall back to submitting the\n * account without a proof rather than blocking the batch.\n */\nexport class ProofUnsupportedNamespaceError extends Error {\n constructor(namespace: string) {\n super(`Proof of ownership is not supported for namespace '${namespace}'.`);\n this.name = 'ProofUnsupportedNamespaceError';\n }\n}\n\n/**\n * Returns the address in the canonical encoding the auth API expects for the\n * given CAIP-2 namespace.\n *\n * Encoding rules (per the `PUT /api/v2/profile/accounts` spec):\n *\n * - `eip155` — EIP-55 mixed-case hex checksum.\n * - `solana`, `tron` — base58 / base58check, single canonical encoding; returned\n * as-is (the server rejects malformed inputs with 400).\n * - `bip122` — bech32 / bech32m addresses (mainnet `bc1…`, testnet\n * `tb1…`, regtest `bcrt1…`) must be all-lowercase; legacy base58check\n * P2PKH addresses (`1…`, `m…`, `n…`) are accepted as-is.\n *\n * @param address - The address to canonicalize.\n * @param namespace - The CAIP-2 namespace of the chain the address belongs to.\n * @returns The address in its canonical form for `namespace`.\n * @throws {ProofUnsupportedNamespaceError} if `namespace` is not one of\n * `eip155`, `solana`, `tron`, or `bip122`.\n */\nexport function canonicalizeAddress(\n address: string,\n namespace: string,\n): string {\n switch (namespace) {\n case KnownCaipNamespace.Eip155:\n return toChecksumHexAddress(address);\n case KnownCaipNamespace.Solana:\n case KnownCaipNamespace.Tron:\n return address;\n case KnownCaipNamespace.Bip122: {\n const lowercased = address.toLowerCase();\n if (\n BECH32_BITCOIN_ADDRESS_PREFIXES.some((prefix) =>\n lowercased.startsWith(prefix),\n )\n ) {\n return lowercased;\n }\n return address;\n }\n default:\n throw new ProofUnsupportedNamespaceError(namespace);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/profile-metrics-controller",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.6-preview-a65eb72",
|
|
4
4
|
"description": "Manages user profile metrics",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -53,14 +53,15 @@
|
|
|
53
53
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@metamask/accounts-controller": "^
|
|
56
|
+
"@metamask/accounts-controller": "^39.0.0",
|
|
57
57
|
"@metamask/base-controller": "^9.1.0",
|
|
58
58
|
"@metamask/controller-utils": "^12.1.0",
|
|
59
59
|
"@metamask/keyring-controller": "^26.0.0",
|
|
60
60
|
"@metamask/messenger": "^1.2.0",
|
|
61
61
|
"@metamask/polling-controller": "^16.0.6",
|
|
62
62
|
"@metamask/profile-sync-controller": "^28.1.1",
|
|
63
|
-
"@metamask/
|
|
63
|
+
"@metamask/superstruct": "^3.1.0",
|
|
64
|
+
"@metamask/transaction-controller": "^66.0.1",
|
|
64
65
|
"@metamask/utils": "^11.9.0",
|
|
65
66
|
"async-mutex": "^0.5.0"
|
|
66
67
|
},
|