@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 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.5...HEAD
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,GAC5C,wCAAwC,CAAC"}
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,GAC5C,wCAAwC,CAAC"}
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
- * An account address along with its associated scopes.
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;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,KAAK,EAAE,kCAAkC,EAAE,oBAAU;AAI5D;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,EAAE,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAAG;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;GAEG;AACH,KAAK,cAAc,GACjB,wBAAwB,CAAC,4CAA4C,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,WAAW,EAClB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AAIF;;GAEG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IA4BlC;;;;;;;;;;;;OAYG;gBACS,EACV,SAAS,EACT,KAAK,EAAE,aAAa,EACpB,aAAkB,EAClB,GAAiB,GAClB,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,aAAa,CAAC,EAAE,0BAA0B,CAAC;QAC3C,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;KACf;IAaD;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAId;;;;;OAKG;IACG,aAAa,CAAC,IAAI,EAAE,kCAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC7E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,MAAM,CAE/C"}
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
- * An account address along with its associated scopes.
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;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,KAAK,EAAE,kCAAkC,EAAE,oBAAU;AAI5D;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,EAAE,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAAG;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;GAEG;AACH,KAAK,cAAc,GACjB,wBAAwB,CAAC,4CAA4C,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,WAAW,EAClB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AAIF;;GAEG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IA4BlC;;;;;;;;;;;;OAYG;gBACS,EACV,SAAS,EACT,KAAK,EAAE,aAAa,EACpB,aAAkB,EAClB,GAAiB,GAClB,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,aAAa,CAAC,EAAE,0BAA0B,CAAC;QAC3C,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;KACf;IAaD;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAId;;;;;OAKG;IACG,aAAa,CAAC,IAAI,EAAE,kCAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC7E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,MAAM,CAE/C"}
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.5-preview-a302a81d3",
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": "^38.1.2",
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/transaction-controller": "^66.0.0",
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
  },