@metamask-previews/sentinel-api-service 0.0.0-preview-d34fa10c8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +6 -0
  3. package/LICENSE.APACHE2 +201 -0
  4. package/LICENSE.MIT +21 -0
  5. package/README.md +15 -0
  6. package/dist/constants.cjs +71 -0
  7. package/dist/constants.cjs.map +1 -0
  8. package/dist/constants.d.cts +63 -0
  9. package/dist/constants.d.cts.map +1 -0
  10. package/dist/constants.d.mts +63 -0
  11. package/dist/constants.d.mts.map +1 -0
  12. package/dist/constants.mjs +68 -0
  13. package/dist/constants.mjs.map +1 -0
  14. package/dist/errors.cjs +43 -0
  15. package/dist/errors.cjs.map +1 -0
  16. package/dist/errors.d.cts +27 -0
  17. package/dist/errors.d.cts.map +1 -0
  18. package/dist/errors.d.mts +27 -0
  19. package/dist/errors.d.mts.map +1 -0
  20. package/dist/errors.mjs +37 -0
  21. package/dist/errors.mjs.map +1 -0
  22. package/dist/index.cjs +17 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +7 -0
  25. package/dist/index.d.cts.map +1 -0
  26. package/dist/index.d.mts +7 -0
  27. package/dist/index.d.mts.map +1 -0
  28. package/dist/index.mjs +5 -0
  29. package/dist/index.mjs.map +1 -0
  30. package/dist/logger.cjs +7 -0
  31. package/dist/logger.cjs.map +1 -0
  32. package/dist/logger.d.cts +5 -0
  33. package/dist/logger.d.cts.map +1 -0
  34. package/dist/logger.d.mts +5 -0
  35. package/dist/logger.d.mts.map +1 -0
  36. package/dist/logger.mjs +4 -0
  37. package/dist/logger.mjs.map +1 -0
  38. package/dist/sentinel-api-service-method-action-types.cjs +7 -0
  39. package/dist/sentinel-api-service-method-action-types.cjs.map +1 -0
  40. package/dist/sentinel-api-service-method-action-types.d.cts +62 -0
  41. package/dist/sentinel-api-service-method-action-types.d.cts.map +1 -0
  42. package/dist/sentinel-api-service-method-action-types.d.mts +62 -0
  43. package/dist/sentinel-api-service-method-action-types.d.mts.map +1 -0
  44. package/dist/sentinel-api-service-method-action-types.mjs +6 -0
  45. package/dist/sentinel-api-service-method-action-types.mjs.map +1 -0
  46. package/dist/sentinel-api-service.cjs +316 -0
  47. package/dist/sentinel-api-service.cjs.map +1 -0
  48. package/dist/sentinel-api-service.d.cts +90 -0
  49. package/dist/sentinel-api-service.d.cts.map +1 -0
  50. package/dist/sentinel-api-service.d.mts +90 -0
  51. package/dist/sentinel-api-service.d.mts.map +1 -0
  52. package/dist/sentinel-api-service.mjs +312 -0
  53. package/dist/sentinel-api-service.mjs.map +1 -0
  54. package/dist/structs.cjs +58 -0
  55. package/dist/structs.cjs.map +1 -0
  56. package/dist/structs.d.cts +63 -0
  57. package/dist/structs.d.cts.map +1 -0
  58. package/dist/structs.d.mts +63 -0
  59. package/dist/structs.d.mts.map +1 -0
  60. package/dist/structs.mjs +55 -0
  61. package/dist/structs.mjs.map +1 -0
  62. package/dist/types.cjs +36 -0
  63. package/dist/types.cjs.map +1 -0
  64. package/dist/types.d.cts +420 -0
  65. package/dist/types.d.cts.map +1 -0
  66. package/dist/types.d.mts +420 -0
  67. package/dist/types.d.mts.map +1 -0
  68. package/dist/types.mjs +33 -0
  69. package/dist/types.mjs.map +1 -0
  70. package/package.json +79 -0
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _SentinelApiService_instances, _SentinelApiService_fetch, _SentinelApiService_clientId, _SentinelApiService_clientVersion, _SentinelApiService_environmentDomain, _SentinelApiService_jsonRpc, _SentinelApiService_getHeaders, _SentinelApiService_resolveUrl, _SentinelApiService_getNetwork, _SentinelApiService_buildUrl;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SentinelApiService = void 0;
16
+ const base_data_service_1 = require("@metamask/base-data-service");
17
+ const controller_utils_1 = require("@metamask/controller-utils");
18
+ const superstruct_1 = require("@metamask/superstruct");
19
+ const constants_1 = require("./constants.cjs");
20
+ const errors_1 = require("./errors.cjs");
21
+ const logger_1 = require("./logger.cjs");
22
+ const structs_1 = require("./structs.cjs");
23
+ const log = (0, logger_1.createModuleLogger)(logger_1.projectLogger, constants_1.serviceName);
24
+ const MESSENGER_EXPOSED_METHODS = [
25
+ 'getNetworks',
26
+ 'simulateTransactions',
27
+ 'submitRelayTransaction',
28
+ 'getSmartTransaction',
29
+ ];
30
+ /**
31
+ * Data service that centralises all interactions with the MetaMask Sentinel
32
+ * API (`tx-sentinel-<network>.api.cx.metamask.io`).
33
+ *
34
+ * It exposes one method per Sentinel endpoint:
35
+ * - {@link SentinelApiService.getNetworks} — the supported-network registry
36
+ * (`/networks`), cached since it is stable and identical across subdomains.
37
+ * - {@link SentinelApiService.simulateTransactions} — transaction simulation
38
+ * (`infura_simulateTransactions`), used by `@metamask/transaction-controller`
39
+ * and `@metamask/transaction-pay-controller`.
40
+ * - {@link SentinelApiService.submitRelayTransaction} — gas station relay
41
+ * submission (`eth_sendRelayTransaction`), used by the extension and mobile.
42
+ * - {@link SentinelApiService.getSmartTransaction} — smart-transaction status
43
+ * lookup (`/smart-transactions/{uuid}`).
44
+ *
45
+ * Consumers derive higher-level concerns (whether a chain supports simulation
46
+ * or relay, polling loops, etc.) from the raw endpoint responses.
47
+ */
48
+ class SentinelApiService extends base_data_service_1.BaseDataService {
49
+ /**
50
+ * Constructs a new SentinelApiService.
51
+ *
52
+ * @param options - The constructor options. See
53
+ * {@link SentinelApiServiceOptions}.
54
+ * @param options.messenger - The messenger suited for this service.
55
+ * @param options.fetch - The `fetch` function to use for requests. Defaults
56
+ * to the global `fetch`.
57
+ * @param options.environment - The Sentinel API environment to target
58
+ * (`dev`, `uat`, or `prod`). Defaults to `prod`.
59
+ * @param options.clientId - Identifier for the calling client (for example
60
+ * `extension` or `mobile`), sent as the `X-Client-Id` header.
61
+ * @param options.clientVersion - Version of the calling client, sent as the
62
+ * `X-Client-Version` header when provided.
63
+ * @param options.queryClientConfig - Configuration for the underlying
64
+ * TanStack Query client.
65
+ * @param options.policyOptions - Options to pass to `createServicePolicy`.
66
+ * Retries are disabled by default (`maxRetries: 0`) to preserve the
67
+ * single-attempt behaviour of the clients this service replaces; pass
68
+ * `maxRetries` here to opt in.
69
+ */
70
+ constructor({ messenger, fetch: fetchFunction = globalThis.fetch, environment = constants_1.DEFAULT_ENVIRONMENT, clientId, clientVersion, queryClientConfig = {}, policyOptions = {}, }) {
71
+ super({
72
+ name: constants_1.serviceName,
73
+ messenger,
74
+ queryClientConfig,
75
+ policyOptions: {
76
+ // Disable retries by default so the service is behaviourally
77
+ // backwards-compatible with the single-request clients it replaces.
78
+ // Callers can override via `policyOptions.maxRetries`.
79
+ maxRetries: 0,
80
+ retryFilterPolicy: (0, controller_utils_1.handleWhen)((error) => !(error instanceof errors_1.SentinelApiResponseValidationError) &&
81
+ !(error instanceof errors_1.SentinelChainNotSupportedError) &&
82
+ !(error instanceof errors_1.SentinelJsonRpcError)),
83
+ ...policyOptions,
84
+ },
85
+ });
86
+ _SentinelApiService_instances.add(this);
87
+ _SentinelApiService_fetch.set(this, void 0);
88
+ _SentinelApiService_clientId.set(this, void 0);
89
+ _SentinelApiService_clientVersion.set(this, void 0);
90
+ _SentinelApiService_environmentDomain.set(this, void 0);
91
+ __classPrivateFieldSet(this, _SentinelApiService_fetch, fetchFunction, "f");
92
+ __classPrivateFieldSet(this, _SentinelApiService_environmentDomain, constants_1.ENVIRONMENT_DOMAIN[environment], "f");
93
+ __classPrivateFieldSet(this, _SentinelApiService_clientId, clientId, "f");
94
+ __classPrivateFieldSet(this, _SentinelApiService_clientVersion, clientVersion, "f");
95
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
96
+ log('Initialized');
97
+ }
98
+ /**
99
+ * Fetches the Sentinel supported-network registry. The result is cached, as
100
+ * the registry is stable and identical across network subdomains.
101
+ *
102
+ * @returns The network registry, keyed by decimal chain ID.
103
+ */
104
+ async getNetworks() {
105
+ const url = `${__classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_buildUrl).call(this, constants_1.NETWORKS_SUBDOMAIN)}${constants_1.ENDPOINT_NETWORKS}`;
106
+ const result = await this.fetchQuery({
107
+ queryKey: [`${this.name}:getNetworks`],
108
+ staleTime: constants_1.NETWORKS_STALE_TIME_MS,
109
+ queryFn: async () => {
110
+ const headers = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_getHeaders).call(this);
111
+ log('getNetworks', 'Request', url);
112
+ const response = await __classPrivateFieldGet(this, _SentinelApiService_fetch, "f").call(this, url, { headers });
113
+ if (!response.ok) {
114
+ throw new controller_utils_1.HttpError(response.status, `Sentinel API: Networks request failed with status '${response.status}'`);
115
+ }
116
+ const json = await response.json();
117
+ const [error] = (0, superstruct_1.validate)(json, structs_1.SentinelNetworkRegistryStruct);
118
+ if (error) {
119
+ throw new errors_1.SentinelApiResponseValidationError(`Sentinel API: Malformed response from networks endpoint: ${error.message}`);
120
+ }
121
+ log('getNetworks', 'Response', json);
122
+ return json;
123
+ },
124
+ });
125
+ return result;
126
+ }
127
+ /**
128
+ * Simulates transactions against the Sentinel API via
129
+ * `infura_simulateTransactions`. Not cached, since each request body is
130
+ * unique and stale simulations must not be reused.
131
+ *
132
+ * @param chainId - The chain ID to simulate on.
133
+ * @param request - The simulation request.
134
+ * @param options - Additional options.
135
+ * @param options.getUrl - Optional callback that receives the default
136
+ * Sentinel URL resolved for the chain and returns the URL to use instead.
137
+ * Lets consumers rewrite the request URL (for example to route through the
138
+ * MetaMask Shield proxy) without the service knowing about those concerns.
139
+ * @returns The simulation response.
140
+ */
141
+ async simulateTransactions(chainId, request, options = {}) {
142
+ const defaultUrl = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_resolveUrl).call(this, chainId, 'confirmations');
143
+ const url = options.getUrl ? await options.getUrl(defaultUrl) : defaultUrl;
144
+ const result = await this.fetchQuery({
145
+ queryKey: [`${this.name}:simulateTransactions`, chainId, request, url],
146
+ staleTime: 0,
147
+ queryFn: async () => {
148
+ log('simulateTransactions', 'Request', url, request);
149
+ const rpcResult = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_jsonRpc).call(this, url, constants_1.RPC_METHOD_SIMULATE, [
150
+ request,
151
+ ]);
152
+ const [error] = (0, superstruct_1.validate)(rpcResult, structs_1.SentinelSimulationResponseStruct);
153
+ if (error) {
154
+ throw new errors_1.SentinelApiResponseValidationError(`Sentinel API: Malformed response from simulation endpoint: ${error.message}`);
155
+ }
156
+ log('simulateTransactions', 'Response', rpcResult);
157
+ return rpcResult;
158
+ },
159
+ });
160
+ return result;
161
+ }
162
+ /**
163
+ * Submits a signed relay (gas station) transaction to the Sentinel API via
164
+ * `eth_sendRelayTransaction`. Not cached.
165
+ *
166
+ * @param request - The relay submit request.
167
+ * @returns The relay submit response containing the tracking UUID.
168
+ */
169
+ async submitRelayTransaction(request) {
170
+ const url = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_resolveUrl).call(this, request.chainId, 'relayTransactions');
171
+ const result = await this.fetchQuery({
172
+ queryKey: [`${this.name}:submitRelayTransaction`, request],
173
+ staleTime: 0,
174
+ queryFn: async () => {
175
+ log('submitRelayTransaction', 'Request', url, request);
176
+ const rpcResult = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_jsonRpc).call(this, url, constants_1.RPC_METHOD_SEND_RELAY, [
177
+ request,
178
+ ]);
179
+ const [error] = (0, superstruct_1.validate)(rpcResult, structs_1.SentinelRelaySubmitResponseStruct);
180
+ if (error) {
181
+ throw new errors_1.SentinelApiResponseValidationError(`Sentinel API: Malformed response from relay submit endpoint: ${error.message}`);
182
+ }
183
+ log('submitRelayTransaction', 'Response', rpcResult);
184
+ return rpcResult;
185
+ },
186
+ });
187
+ return result;
188
+ }
189
+ /**
190
+ * Looks up the state of a submitted smart transaction by UUID against the
191
+ * `/smart-transactions/{uuid}` endpoint. Performs a single request; callers
192
+ * own any polling loop. Not cached.
193
+ *
194
+ * @param request - The smart-transaction lookup request.
195
+ * @returns The response envelope containing the smart transaction(s)
196
+ * associated with the requested UUID.
197
+ */
198
+ async getSmartTransaction(request) {
199
+ const { chainId, uuid } = request;
200
+ const baseUrl = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_resolveUrl).call(this, chainId, 'relayTransactions');
201
+ const url = `${baseUrl}${constants_1.ENDPOINT_SMART_TRANSACTIONS}/${uuid}`;
202
+ const result = await this.fetchQuery({
203
+ queryKey: [`${this.name}:getSmartTransaction`, chainId, uuid],
204
+ staleTime: 0,
205
+ queryFn: async () => {
206
+ const headers = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_getHeaders).call(this);
207
+ log('getSmartTransaction', 'Request', url);
208
+ const response = await __classPrivateFieldGet(this, _SentinelApiService_fetch, "f").call(this, url, { headers });
209
+ if (!response.ok) {
210
+ throw new controller_utils_1.HttpError(response.status, `Sentinel API: Smart-transaction request failed with status '${response.status}'`);
211
+ }
212
+ const json = await response.json();
213
+ const [error] = (0, superstruct_1.validate)(json, structs_1.SentinelSmartTransactionResponseStruct);
214
+ if (error) {
215
+ throw new errors_1.SentinelApiResponseValidationError(`Sentinel API: Malformed response from smart-transactions endpoint: ${error.message}`);
216
+ }
217
+ log('getSmartTransaction', 'Response', json);
218
+ return json;
219
+ },
220
+ });
221
+ return result;
222
+ }
223
+ }
224
+ exports.SentinelApiService = SentinelApiService;
225
+ _SentinelApiService_fetch = new WeakMap(), _SentinelApiService_clientId = new WeakMap(), _SentinelApiService_clientVersion = new WeakMap(), _SentinelApiService_environmentDomain = new WeakMap(), _SentinelApiService_instances = new WeakSet(), _SentinelApiService_jsonRpc =
226
+ /**
227
+ * Performs a JSON-RPC POST to the Sentinel API and returns the `result`.
228
+ *
229
+ * @param url - The URL to post to.
230
+ * @param method - The JSON-RPC method name.
231
+ * @param params - The JSON-RPC params.
232
+ * @returns The `result` field of the JSON-RPC response.
233
+ */
234
+ async function _SentinelApiService_jsonRpc(url, method, params) {
235
+ const headers = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_getHeaders).call(this);
236
+ const response = await __classPrivateFieldGet(this, _SentinelApiService_fetch, "f").call(this, url, {
237
+ method: 'POST',
238
+ headers: {
239
+ ...headers,
240
+ 'Content-Type': 'application/json',
241
+ },
242
+ body: JSON.stringify({
243
+ id: '1',
244
+ jsonrpc: '2.0',
245
+ method,
246
+ params,
247
+ }),
248
+ });
249
+ if (!response.ok) {
250
+ throw new controller_utils_1.HttpError(response.status, `Sentinel API: JSON-RPC request '${method}' failed with status '${response.status}'`);
251
+ }
252
+ const responseJson = (await response.json());
253
+ if (responseJson.error) {
254
+ const { code, message } = responseJson.error;
255
+ throw new errors_1.SentinelJsonRpcError(`Sentinel API: JSON-RPC error: ${message}`, code);
256
+ }
257
+ if (responseJson.result === undefined) {
258
+ throw new errors_1.SentinelJsonRpcError('Sentinel API: JSON-RPC response missing result', -32603);
259
+ }
260
+ return responseJson.result;
261
+ }, _SentinelApiService_getHeaders =
262
+ /**
263
+ * Builds the outbound headers for a Sentinel request: the client identity
264
+ * headers plus a best-effort `Authorization` bearer token. Token retrieval
265
+ * failures are swallowed so unauthenticated requests still proceed.
266
+ *
267
+ * @returns The headers to attach to the request.
268
+ */
269
+ async function _SentinelApiService_getHeaders() {
270
+ const headers = {};
271
+ if (__classPrivateFieldGet(this, _SentinelApiService_clientId, "f") !== undefined) {
272
+ headers['X-Client-Id'] = __classPrivateFieldGet(this, _SentinelApiService_clientId, "f");
273
+ }
274
+ if (__classPrivateFieldGet(this, _SentinelApiService_clientVersion, "f") !== undefined) {
275
+ headers['X-Client-Version'] = __classPrivateFieldGet(this, _SentinelApiService_clientVersion, "f");
276
+ }
277
+ try {
278
+ const token = await this.messenger.call('AuthenticationController:getBearerToken');
279
+ if (token) {
280
+ headers.Authorization = `Bearer ${token}`;
281
+ }
282
+ }
283
+ catch (error) {
284
+ // Proceed without auth if token retrieval fails.
285
+ log('getHeaders', 'Auth token unavailable, proceeding unauthenticated', error);
286
+ }
287
+ return headers;
288
+ }, _SentinelApiService_resolveUrl =
289
+ /**
290
+ * Resolves the Sentinel URL for a chain, asserting the required capability.
291
+ *
292
+ * @param chainId - The chain ID to resolve a URL for.
293
+ * @param capability - The capability flag that must be enabled.
294
+ * @returns The resolved base URL.
295
+ */
296
+ async function _SentinelApiService_resolveUrl(chainId, capability) {
297
+ const network = await __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_getNetwork).call(this, chainId);
298
+ if (!network?.[capability]) {
299
+ throw new errors_1.SentinelChainNotSupportedError(chainId, capability);
300
+ }
301
+ return __classPrivateFieldGet(this, _SentinelApiService_instances, "m", _SentinelApiService_buildUrl).call(this, network.network);
302
+ }, _SentinelApiService_getNetwork =
303
+ /**
304
+ * Looks up a single network entry from the registry by chain ID.
305
+ *
306
+ * @param chainId - The chain ID to look up.
307
+ * @returns The network entry, or undefined if not present.
308
+ */
309
+ async function _SentinelApiService_getNetwork(chainId) {
310
+ const registry = await this.getNetworks();
311
+ const chainIdDecimal = BigInt(chainId).toString(10);
312
+ return registry[chainIdDecimal];
313
+ }, _SentinelApiService_buildUrl = function _SentinelApiService_buildUrl(subdomain) {
314
+ return constants_1.BASE_URL_TEMPLATE.replace('{0}', subdomain).replace('{1}', __classPrivateFieldGet(this, _SentinelApiService_environmentDomain, "f"));
315
+ };
316
+ //# sourceMappingURL=sentinel-api-service.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel-api-service.cjs","sourceRoot":"","sources":["../src/sentinel-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseDataService } from '@metamask/base-data-service';\nimport { handleWhen, HttpError } from '@metamask/controller-utils';\nimport { validate } from '@metamask/superstruct';\nimport type { Hex, Json } from '@metamask/utils';\n\nimport {\n BASE_URL_TEMPLATE,\n DEFAULT_ENVIRONMENT,\n ENDPOINT_NETWORKS,\n ENDPOINT_SMART_TRANSACTIONS,\n ENVIRONMENT_DOMAIN,\n NETWORKS_STALE_TIME_MS,\n NETWORKS_SUBDOMAIN,\n RPC_METHOD_SEND_RELAY,\n RPC_METHOD_SIMULATE,\n serviceName,\n} from './constants';\nimport {\n SentinelApiResponseValidationError,\n SentinelChainNotSupportedError,\n SentinelJsonRpcError,\n} from './errors';\nimport { projectLogger, createModuleLogger } from './logger';\nimport {\n SentinelSmartTransactionResponseStruct,\n SentinelNetworkRegistryStruct,\n SentinelRelaySubmitResponseStruct,\n SentinelSimulationResponseStruct,\n} from './structs';\nimport type {\n SentinelApiServiceMessenger,\n SentinelApiServiceOptions,\n SentinelNetwork,\n SentinelNetworkRegistry,\n SentinelRelaySubmitRequest,\n SentinelRelaySubmitResponse,\n SentinelSimulationRequest,\n SentinelSimulationResponse,\n SentinelSmartTransactionRequest,\n SentinelSmartTransactionResponse,\n} from './types';\n\nconst log = createModuleLogger(projectLogger, serviceName);\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'getNetworks',\n 'simulateTransactions',\n 'submitRelayTransaction',\n 'getSmartTransaction',\n] as const;\n\n/**\n * Data service that centralises all interactions with the MetaMask Sentinel\n * API (`tx-sentinel-<network>.api.cx.metamask.io`).\n *\n * It exposes one method per Sentinel endpoint:\n * - {@link SentinelApiService.getNetworks} — the supported-network registry\n * (`/networks`), cached since it is stable and identical across subdomains.\n * - {@link SentinelApiService.simulateTransactions} — transaction simulation\n * (`infura_simulateTransactions`), used by `@metamask/transaction-controller`\n * and `@metamask/transaction-pay-controller`.\n * - {@link SentinelApiService.submitRelayTransaction} — gas station relay\n * submission (`eth_sendRelayTransaction`), used by the extension and mobile.\n * - {@link SentinelApiService.getSmartTransaction} — smart-transaction status\n * lookup (`/smart-transactions/{uuid}`).\n *\n * Consumers derive higher-level concerns (whether a chain supports simulation\n * or relay, polling loops, etc.) from the raw endpoint responses.\n */\nexport class SentinelApiService extends BaseDataService<\n typeof serviceName,\n SentinelApiServiceMessenger\n> {\n readonly #fetch: typeof fetch;\n\n readonly #clientId?: string;\n\n readonly #clientVersion?: string;\n\n readonly #environmentDomain: string;\n\n /**\n * Constructs a new SentinelApiService.\n *\n * @param options - The constructor options. See\n * {@link SentinelApiServiceOptions}.\n * @param options.messenger - The messenger suited for this service.\n * @param options.fetch - The `fetch` function to use for requests. Defaults\n * to the global `fetch`.\n * @param options.environment - The Sentinel API environment to target\n * (`dev`, `uat`, or `prod`). Defaults to `prod`.\n * @param options.clientId - Identifier for the calling client (for example\n * `extension` or `mobile`), sent as the `X-Client-Id` header.\n * @param options.clientVersion - Version of the calling client, sent as the\n * `X-Client-Version` header when provided.\n * @param options.queryClientConfig - Configuration for the underlying\n * TanStack Query client.\n * @param options.policyOptions - Options to pass to `createServicePolicy`.\n * Retries are disabled by default (`maxRetries: 0`) to preserve the\n * single-attempt behaviour of the clients this service replaces; pass\n * `maxRetries` here to opt in.\n */\n constructor({\n messenger,\n fetch: fetchFunction = globalThis.fetch,\n environment = DEFAULT_ENVIRONMENT,\n clientId,\n clientVersion,\n queryClientConfig = {},\n policyOptions = {},\n }: SentinelApiServiceOptions) {\n super({\n name: serviceName,\n messenger,\n queryClientConfig,\n policyOptions: {\n // Disable retries by default so the service is behaviourally\n // backwards-compatible with the single-request clients it replaces.\n // Callers can override via `policyOptions.maxRetries`.\n maxRetries: 0,\n retryFilterPolicy: handleWhen(\n (error) =>\n !(error instanceof SentinelApiResponseValidationError) &&\n !(error instanceof SentinelChainNotSupportedError) &&\n !(error instanceof SentinelJsonRpcError),\n ),\n ...policyOptions,\n },\n });\n\n this.#fetch = fetchFunction;\n this.#environmentDomain = ENVIRONMENT_DOMAIN[environment];\n this.#clientId = clientId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n log('Initialized');\n }\n\n /**\n * Fetches the Sentinel supported-network registry. The result is cached, as\n * the registry is stable and identical across network subdomains.\n *\n * @returns The network registry, keyed by decimal chain ID.\n */\n async getNetworks(): Promise<SentinelNetworkRegistry> {\n const url = `${this.#buildUrl(NETWORKS_SUBDOMAIN)}${ENDPOINT_NETWORKS}`;\n\n const result = await this.fetchQuery({\n queryKey: [`${this.name}:getNetworks`],\n staleTime: NETWORKS_STALE_TIME_MS,\n queryFn: async (): Promise<Json> => {\n const headers = await this.#getHeaders();\n\n log('getNetworks', 'Request', url);\n\n const response = await this.#fetch(url, { headers });\n\n if (!response.ok) {\n throw new HttpError(\n response.status,\n `Sentinel API: Networks request failed with status '${response.status}'`,\n );\n }\n\n const json: Json = await response.json();\n\n const [error] = validate(json, SentinelNetworkRegistryStruct);\n if (error) {\n throw new SentinelApiResponseValidationError(\n `Sentinel API: Malformed response from networks endpoint: ${error.message}`,\n );\n }\n\n log('getNetworks', 'Response', json);\n\n return json;\n },\n });\n\n return result as unknown as SentinelNetworkRegistry;\n }\n\n /**\n * Simulates transactions against the Sentinel API via\n * `infura_simulateTransactions`. Not cached, since each request body is\n * unique and stale simulations must not be reused.\n *\n * @param chainId - The chain ID to simulate on.\n * @param request - The simulation request.\n * @param options - Additional options.\n * @param options.getUrl - Optional callback that receives the default\n * Sentinel URL resolved for the chain and returns the URL to use instead.\n * Lets consumers rewrite the request URL (for example to route through the\n * MetaMask Shield proxy) without the service knowing about those concerns.\n * @returns The simulation response.\n */\n async simulateTransactions(\n chainId: Hex,\n request: SentinelSimulationRequest,\n options: { getUrl?: (defaultUrl: string) => string | Promise<string> } = {},\n ): Promise<SentinelSimulationResponse> {\n const defaultUrl = await this.#resolveUrl(chainId, 'confirmations');\n const url = options.getUrl ? await options.getUrl(defaultUrl) : defaultUrl;\n\n const result = await this.fetchQuery({\n queryKey: [`${this.name}:simulateTransactions`, chainId, request, url],\n staleTime: 0,\n queryFn: async (): Promise<Json> => {\n log('simulateTransactions', 'Request', url, request);\n\n const rpcResult = await this.#jsonRpc(url, RPC_METHOD_SIMULATE, [\n request,\n ]);\n\n const [error] = validate(rpcResult, SentinelSimulationResponseStruct);\n if (error) {\n throw new SentinelApiResponseValidationError(\n `Sentinel API: Malformed response from simulation endpoint: ${error.message}`,\n );\n }\n\n log('simulateTransactions', 'Response', rpcResult);\n\n return rpcResult;\n },\n });\n\n return result as unknown as SentinelSimulationResponse;\n }\n\n /**\n * Submits a signed relay (gas station) transaction to the Sentinel API via\n * `eth_sendRelayTransaction`. Not cached.\n *\n * @param request - The relay submit request.\n * @returns The relay submit response containing the tracking UUID.\n */\n async submitRelayTransaction(\n request: SentinelRelaySubmitRequest,\n ): Promise<SentinelRelaySubmitResponse> {\n const url = await this.#resolveUrl(request.chainId, 'relayTransactions');\n\n const result = await this.fetchQuery({\n queryKey: [`${this.name}:submitRelayTransaction`, request],\n staleTime: 0,\n queryFn: async (): Promise<Json> => {\n log('submitRelayTransaction', 'Request', url, request);\n\n const rpcResult = await this.#jsonRpc(url, RPC_METHOD_SEND_RELAY, [\n request,\n ]);\n\n const [error] = validate(rpcResult, SentinelRelaySubmitResponseStruct);\n if (error) {\n throw new SentinelApiResponseValidationError(\n `Sentinel API: Malformed response from relay submit endpoint: ${error.message}`,\n );\n }\n\n log('submitRelayTransaction', 'Response', rpcResult);\n\n return rpcResult;\n },\n });\n\n return result as unknown as SentinelRelaySubmitResponse;\n }\n\n /**\n * Looks up the state of a submitted smart transaction by UUID against the\n * `/smart-transactions/{uuid}` endpoint. Performs a single request; callers\n * own any polling loop. Not cached.\n *\n * @param request - The smart-transaction lookup request.\n * @returns The response envelope containing the smart transaction(s)\n * associated with the requested UUID.\n */\n async getSmartTransaction(\n request: SentinelSmartTransactionRequest,\n ): Promise<SentinelSmartTransactionResponse> {\n const { chainId, uuid } = request;\n const baseUrl = await this.#resolveUrl(chainId, 'relayTransactions');\n const url = `${baseUrl}${ENDPOINT_SMART_TRANSACTIONS}/${uuid}`;\n\n const result = await this.fetchQuery({\n queryKey: [`${this.name}:getSmartTransaction`, chainId, uuid],\n staleTime: 0,\n queryFn: async (): Promise<Json> => {\n const headers = await this.#getHeaders();\n\n log('getSmartTransaction', 'Request', url);\n\n const response = await this.#fetch(url, { headers });\n\n if (!response.ok) {\n throw new HttpError(\n response.status,\n `Sentinel API: Smart-transaction request failed with status '${response.status}'`,\n );\n }\n\n const json: Json = await response.json();\n\n const [error] = validate(json, SentinelSmartTransactionResponseStruct);\n if (error) {\n throw new SentinelApiResponseValidationError(\n `Sentinel API: Malformed response from smart-transactions endpoint: ${error.message}`,\n );\n }\n\n log('getSmartTransaction', 'Response', json);\n\n return json;\n },\n });\n\n return result as unknown as SentinelSmartTransactionResponse;\n }\n\n /**\n * Performs a JSON-RPC POST to the Sentinel API and returns the `result`.\n *\n * @param url - The URL to post to.\n * @param method - The JSON-RPC method name.\n * @param params - The JSON-RPC params.\n * @returns The `result` field of the JSON-RPC response.\n */\n async #jsonRpc(url: string, method: string, params: Json[]): Promise<Json> {\n const headers = await this.#getHeaders();\n\n const response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n id: '1',\n jsonrpc: '2.0',\n method,\n params,\n }),\n });\n\n if (!response.ok) {\n throw new HttpError(\n response.status,\n `Sentinel API: JSON-RPC request '${method}' failed with status '${response.status}'`,\n );\n }\n\n const responseJson = (await response.json()) as {\n error?: { code?: number; message?: string };\n result?: Json;\n };\n\n if (responseJson.error) {\n const { code, message } = responseJson.error;\n throw new SentinelJsonRpcError(\n `Sentinel API: JSON-RPC error: ${message}`,\n code,\n );\n }\n\n if (responseJson.result === undefined) {\n throw new SentinelJsonRpcError(\n 'Sentinel API: JSON-RPC response missing result',\n -32603,\n );\n }\n\n return responseJson.result;\n }\n\n /**\n * Builds the outbound headers for a Sentinel request: the client identity\n * headers plus a best-effort `Authorization` bearer token. Token retrieval\n * failures are swallowed so unauthenticated requests still proceed.\n *\n * @returns The headers to attach to the request.\n */\n async #getHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = {};\n\n if (this.#clientId !== undefined) {\n headers['X-Client-Id'] = this.#clientId;\n }\n\n if (this.#clientVersion !== undefined) {\n headers['X-Client-Version'] = this.#clientVersion;\n }\n\n try {\n const token = await this.messenger.call(\n 'AuthenticationController:getBearerToken',\n );\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n } catch (error: unknown) {\n // Proceed without auth if token retrieval fails.\n log(\n 'getHeaders',\n 'Auth token unavailable, proceeding unauthenticated',\n error,\n );\n }\n\n return headers;\n }\n\n /**\n * Resolves the Sentinel URL for a chain, asserting the required capability.\n *\n * @param chainId - The chain ID to resolve a URL for.\n * @param capability - The capability flag that must be enabled.\n * @returns The resolved base URL.\n */\n async #resolveUrl(\n chainId: Hex,\n capability: 'confirmations' | 'relayTransactions',\n ): Promise<string> {\n const network = await this.#getNetwork(chainId);\n\n if (!network?.[capability]) {\n throw new SentinelChainNotSupportedError(chainId, capability);\n }\n\n return this.#buildUrl(network.network);\n }\n\n /**\n * Looks up a single network entry from the registry by chain ID.\n *\n * @param chainId - The chain ID to look up.\n * @returns The network entry, or undefined if not present.\n */\n async #getNetwork(chainId: Hex): Promise<SentinelNetwork | undefined> {\n const registry = await this.getNetworks();\n const chainIdDecimal = BigInt(chainId).toString(10);\n return registry[chainIdDecimal];\n }\n\n /**\n * Builds a Sentinel base URL for the given network subdomain, targeting the\n * environment this service was constructed with.\n *\n * @param subdomain - The network subdomain (for example `ethereum-mainnet`).\n * @returns The full base URL.\n */\n #buildUrl(subdomain: string): string {\n return BASE_URL_TEMPLATE.replace('{0}', subdomain).replace(\n '{1}',\n this.#environmentDomain,\n );\n }\n}\n"]}
@@ -0,0 +1,90 @@
1
+ import { BaseDataService } from "@metamask/base-data-service";
2
+ import type { Hex } from "@metamask/utils";
3
+ import { serviceName } from "./constants.cjs";
4
+ import type { SentinelApiServiceMessenger, SentinelApiServiceOptions, SentinelNetworkRegistry, SentinelRelaySubmitRequest, SentinelRelaySubmitResponse, SentinelSimulationRequest, SentinelSimulationResponse, SentinelSmartTransactionRequest, SentinelSmartTransactionResponse } from "./types.cjs";
5
+ /**
6
+ * Data service that centralises all interactions with the MetaMask Sentinel
7
+ * API (`tx-sentinel-<network>.api.cx.metamask.io`).
8
+ *
9
+ * It exposes one method per Sentinel endpoint:
10
+ * - {@link SentinelApiService.getNetworks} — the supported-network registry
11
+ * (`/networks`), cached since it is stable and identical across subdomains.
12
+ * - {@link SentinelApiService.simulateTransactions} — transaction simulation
13
+ * (`infura_simulateTransactions`), used by `@metamask/transaction-controller`
14
+ * and `@metamask/transaction-pay-controller`.
15
+ * - {@link SentinelApiService.submitRelayTransaction} — gas station relay
16
+ * submission (`eth_sendRelayTransaction`), used by the extension and mobile.
17
+ * - {@link SentinelApiService.getSmartTransaction} — smart-transaction status
18
+ * lookup (`/smart-transactions/{uuid}`).
19
+ *
20
+ * Consumers derive higher-level concerns (whether a chain supports simulation
21
+ * or relay, polling loops, etc.) from the raw endpoint responses.
22
+ */
23
+ export declare class SentinelApiService extends BaseDataService<typeof serviceName, SentinelApiServiceMessenger> {
24
+ #private;
25
+ /**
26
+ * Constructs a new SentinelApiService.
27
+ *
28
+ * @param options - The constructor options. See
29
+ * {@link SentinelApiServiceOptions}.
30
+ * @param options.messenger - The messenger suited for this service.
31
+ * @param options.fetch - The `fetch` function to use for requests. Defaults
32
+ * to the global `fetch`.
33
+ * @param options.environment - The Sentinel API environment to target
34
+ * (`dev`, `uat`, or `prod`). Defaults to `prod`.
35
+ * @param options.clientId - Identifier for the calling client (for example
36
+ * `extension` or `mobile`), sent as the `X-Client-Id` header.
37
+ * @param options.clientVersion - Version of the calling client, sent as the
38
+ * `X-Client-Version` header when provided.
39
+ * @param options.queryClientConfig - Configuration for the underlying
40
+ * TanStack Query client.
41
+ * @param options.policyOptions - Options to pass to `createServicePolicy`.
42
+ * Retries are disabled by default (`maxRetries: 0`) to preserve the
43
+ * single-attempt behaviour of the clients this service replaces; pass
44
+ * `maxRetries` here to opt in.
45
+ */
46
+ constructor({ messenger, fetch: fetchFunction, environment, clientId, clientVersion, queryClientConfig, policyOptions, }: SentinelApiServiceOptions);
47
+ /**
48
+ * Fetches the Sentinel supported-network registry. The result is cached, as
49
+ * the registry is stable and identical across network subdomains.
50
+ *
51
+ * @returns The network registry, keyed by decimal chain ID.
52
+ */
53
+ getNetworks(): Promise<SentinelNetworkRegistry>;
54
+ /**
55
+ * Simulates transactions against the Sentinel API via
56
+ * `infura_simulateTransactions`. Not cached, since each request body is
57
+ * unique and stale simulations must not be reused.
58
+ *
59
+ * @param chainId - The chain ID to simulate on.
60
+ * @param request - The simulation request.
61
+ * @param options - Additional options.
62
+ * @param options.getUrl - Optional callback that receives the default
63
+ * Sentinel URL resolved for the chain and returns the URL to use instead.
64
+ * Lets consumers rewrite the request URL (for example to route through the
65
+ * MetaMask Shield proxy) without the service knowing about those concerns.
66
+ * @returns The simulation response.
67
+ */
68
+ simulateTransactions(chainId: Hex, request: SentinelSimulationRequest, options?: {
69
+ getUrl?: (defaultUrl: string) => string | Promise<string>;
70
+ }): Promise<SentinelSimulationResponse>;
71
+ /**
72
+ * Submits a signed relay (gas station) transaction to the Sentinel API via
73
+ * `eth_sendRelayTransaction`. Not cached.
74
+ *
75
+ * @param request - The relay submit request.
76
+ * @returns The relay submit response containing the tracking UUID.
77
+ */
78
+ submitRelayTransaction(request: SentinelRelaySubmitRequest): Promise<SentinelRelaySubmitResponse>;
79
+ /**
80
+ * Looks up the state of a submitted smart transaction by UUID against the
81
+ * `/smart-transactions/{uuid}` endpoint. Performs a single request; callers
82
+ * own any polling loop. Not cached.
83
+ *
84
+ * @param request - The smart-transaction lookup request.
85
+ * @returns The response envelope containing the smart transaction(s)
86
+ * associated with the requested UUID.
87
+ */
88
+ getSmartTransaction(request: SentinelSmartTransactionRequest): Promise<SentinelSmartTransactionResponse>;
89
+ }
90
+ //# sourceMappingURL=sentinel-api-service.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel-api-service.d.cts","sourceRoot":"","sources":["../src/sentinel-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oCAAoC;AAG9D,OAAO,KAAK,EAAE,GAAG,EAAQ,wBAAwB;AAEjD,OAAO,EAUL,WAAW,EACZ,wBAAoB;AAarB,OAAO,KAAK,EACV,2BAA2B,EAC3B,yBAAyB,EAEzB,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,+BAA+B,EAC/B,gCAAgC,EACjC,oBAAgB;AAWjB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAmB,SAAQ,eAAe,CACrD,OAAO,WAAW,EAClB,2BAA2B,CAC5B;;IASC;;;;;;;;;;;;;;;;;;;;OAoBG;gBACS,EACV,SAAS,EACT,KAAK,EAAE,aAAgC,EACvC,WAAiC,EACjC,QAAQ,EACR,aAAa,EACb,iBAAsB,EACtB,aAAkB,GACnB,EAAE,yBAAyB;IAiC5B;;;;;OAKG;IACG,WAAW,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAsCrD;;;;;;;;;;;;;OAaG;IACG,oBAAoB,CACxB,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;KAAO,GAC1E,OAAO,CAAC,0BAA0B,CAAC;IA8BtC;;;;;;OAMG;IACG,sBAAsB,CAC1B,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,2BAA2B,CAAC;IA6BvC;;;;;;;;OAQG;IACG,mBAAmB,CACvB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,gCAAgC,CAAC;CAiL7C"}
@@ -0,0 +1,90 @@
1
+ import { BaseDataService } from "@metamask/base-data-service";
2
+ import type { Hex } from "@metamask/utils";
3
+ import { serviceName } from "./constants.mjs";
4
+ import type { SentinelApiServiceMessenger, SentinelApiServiceOptions, SentinelNetworkRegistry, SentinelRelaySubmitRequest, SentinelRelaySubmitResponse, SentinelSimulationRequest, SentinelSimulationResponse, SentinelSmartTransactionRequest, SentinelSmartTransactionResponse } from "./types.mjs";
5
+ /**
6
+ * Data service that centralises all interactions with the MetaMask Sentinel
7
+ * API (`tx-sentinel-<network>.api.cx.metamask.io`).
8
+ *
9
+ * It exposes one method per Sentinel endpoint:
10
+ * - {@link SentinelApiService.getNetworks} — the supported-network registry
11
+ * (`/networks`), cached since it is stable and identical across subdomains.
12
+ * - {@link SentinelApiService.simulateTransactions} — transaction simulation
13
+ * (`infura_simulateTransactions`), used by `@metamask/transaction-controller`
14
+ * and `@metamask/transaction-pay-controller`.
15
+ * - {@link SentinelApiService.submitRelayTransaction} — gas station relay
16
+ * submission (`eth_sendRelayTransaction`), used by the extension and mobile.
17
+ * - {@link SentinelApiService.getSmartTransaction} — smart-transaction status
18
+ * lookup (`/smart-transactions/{uuid}`).
19
+ *
20
+ * Consumers derive higher-level concerns (whether a chain supports simulation
21
+ * or relay, polling loops, etc.) from the raw endpoint responses.
22
+ */
23
+ export declare class SentinelApiService extends BaseDataService<typeof serviceName, SentinelApiServiceMessenger> {
24
+ #private;
25
+ /**
26
+ * Constructs a new SentinelApiService.
27
+ *
28
+ * @param options - The constructor options. See
29
+ * {@link SentinelApiServiceOptions}.
30
+ * @param options.messenger - The messenger suited for this service.
31
+ * @param options.fetch - The `fetch` function to use for requests. Defaults
32
+ * to the global `fetch`.
33
+ * @param options.environment - The Sentinel API environment to target
34
+ * (`dev`, `uat`, or `prod`). Defaults to `prod`.
35
+ * @param options.clientId - Identifier for the calling client (for example
36
+ * `extension` or `mobile`), sent as the `X-Client-Id` header.
37
+ * @param options.clientVersion - Version of the calling client, sent as the
38
+ * `X-Client-Version` header when provided.
39
+ * @param options.queryClientConfig - Configuration for the underlying
40
+ * TanStack Query client.
41
+ * @param options.policyOptions - Options to pass to `createServicePolicy`.
42
+ * Retries are disabled by default (`maxRetries: 0`) to preserve the
43
+ * single-attempt behaviour of the clients this service replaces; pass
44
+ * `maxRetries` here to opt in.
45
+ */
46
+ constructor({ messenger, fetch: fetchFunction, environment, clientId, clientVersion, queryClientConfig, policyOptions, }: SentinelApiServiceOptions);
47
+ /**
48
+ * Fetches the Sentinel supported-network registry. The result is cached, as
49
+ * the registry is stable and identical across network subdomains.
50
+ *
51
+ * @returns The network registry, keyed by decimal chain ID.
52
+ */
53
+ getNetworks(): Promise<SentinelNetworkRegistry>;
54
+ /**
55
+ * Simulates transactions against the Sentinel API via
56
+ * `infura_simulateTransactions`. Not cached, since each request body is
57
+ * unique and stale simulations must not be reused.
58
+ *
59
+ * @param chainId - The chain ID to simulate on.
60
+ * @param request - The simulation request.
61
+ * @param options - Additional options.
62
+ * @param options.getUrl - Optional callback that receives the default
63
+ * Sentinel URL resolved for the chain and returns the URL to use instead.
64
+ * Lets consumers rewrite the request URL (for example to route through the
65
+ * MetaMask Shield proxy) without the service knowing about those concerns.
66
+ * @returns The simulation response.
67
+ */
68
+ simulateTransactions(chainId: Hex, request: SentinelSimulationRequest, options?: {
69
+ getUrl?: (defaultUrl: string) => string | Promise<string>;
70
+ }): Promise<SentinelSimulationResponse>;
71
+ /**
72
+ * Submits a signed relay (gas station) transaction to the Sentinel API via
73
+ * `eth_sendRelayTransaction`. Not cached.
74
+ *
75
+ * @param request - The relay submit request.
76
+ * @returns The relay submit response containing the tracking UUID.
77
+ */
78
+ submitRelayTransaction(request: SentinelRelaySubmitRequest): Promise<SentinelRelaySubmitResponse>;
79
+ /**
80
+ * Looks up the state of a submitted smart transaction by UUID against the
81
+ * `/smart-transactions/{uuid}` endpoint. Performs a single request; callers
82
+ * own any polling loop. Not cached.
83
+ *
84
+ * @param request - The smart-transaction lookup request.
85
+ * @returns The response envelope containing the smart transaction(s)
86
+ * associated with the requested UUID.
87
+ */
88
+ getSmartTransaction(request: SentinelSmartTransactionRequest): Promise<SentinelSmartTransactionResponse>;
89
+ }
90
+ //# sourceMappingURL=sentinel-api-service.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel-api-service.d.mts","sourceRoot":"","sources":["../src/sentinel-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oCAAoC;AAG9D,OAAO,KAAK,EAAE,GAAG,EAAQ,wBAAwB;AAEjD,OAAO,EAUL,WAAW,EACZ,wBAAoB;AAarB,OAAO,KAAK,EACV,2BAA2B,EAC3B,yBAAyB,EAEzB,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,+BAA+B,EAC/B,gCAAgC,EACjC,oBAAgB;AAWjB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAmB,SAAQ,eAAe,CACrD,OAAO,WAAW,EAClB,2BAA2B,CAC5B;;IASC;;;;;;;;;;;;;;;;;;;;OAoBG;gBACS,EACV,SAAS,EACT,KAAK,EAAE,aAAgC,EACvC,WAAiC,EACjC,QAAQ,EACR,aAAa,EACb,iBAAsB,EACtB,aAAkB,GACnB,EAAE,yBAAyB;IAiC5B;;;;;OAKG;IACG,WAAW,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAsCrD;;;;;;;;;;;;;OAaG;IACG,oBAAoB,CACxB,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;KAAO,GAC1E,OAAO,CAAC,0BAA0B,CAAC;IA8BtC;;;;;;OAMG;IACG,sBAAsB,CAC1B,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,2BAA2B,CAAC;IA6BvC;;;;;;;;OAQG;IACG,mBAAmB,CACvB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,gCAAgC,CAAC;CAiL7C"}