@formo/analytics 1.35.2 → 1.37.0

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 (51) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +79 -363
  2. package/dist/cjs/src/FormoAnalytics.js +368 -2234
  3. package/dist/cjs/src/event/EventManager.d.ts +3 -1
  4. package/dist/cjs/src/event/EventManager.js +5 -1
  5. package/dist/cjs/src/event/type.d.ts +1 -0
  6. package/dist/cjs/src/evm/EvmEventTracker.d.ts +175 -0
  7. package/dist/cjs/src/evm/EvmEventTracker.js +1030 -0
  8. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +132 -0
  9. package/dist/cjs/src/evm/EvmProviderRegistry.js +348 -0
  10. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +98 -0
  11. package/dist/cjs/src/evm/EvmRequestTracker.js +713 -0
  12. package/dist/cjs/src/evm/batch.d.ts +94 -0
  13. package/dist/cjs/src/evm/batch.js +130 -0
  14. package/dist/cjs/src/queue/EventQueue.d.ts +28 -0
  15. package/dist/cjs/src/queue/EventQueue.js +97 -19
  16. package/dist/cjs/src/queue/type.d.ts +1 -0
  17. package/dist/cjs/src/tracking/TrackingPolicy.d.ts +146 -0
  18. package/dist/cjs/src/tracking/TrackingPolicy.js +200 -0
  19. package/dist/cjs/src/validators/address.d.ts +1 -1
  20. package/dist/cjs/src/version.d.ts +1 -1
  21. package/dist/cjs/src/version.js +1 -1
  22. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +43 -0
  23. package/dist/cjs/src/wagmi/WagmiEventHandler.js +253 -2
  24. package/dist/cjs/src/wallet/WalletStateStore.d.ts +223 -0
  25. package/dist/cjs/src/wallet/WalletStateStore.js +515 -0
  26. package/dist/esm/src/FormoAnalytics.d.ts +79 -363
  27. package/dist/esm/src/FormoAnalytics.js +369 -2235
  28. package/dist/esm/src/event/EventManager.d.ts +3 -1
  29. package/dist/esm/src/event/EventManager.js +5 -1
  30. package/dist/esm/src/event/type.d.ts +1 -0
  31. package/dist/esm/src/evm/EvmEventTracker.d.ts +175 -0
  32. package/dist/esm/src/evm/EvmEventTracker.js +1027 -0
  33. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +132 -0
  34. package/dist/esm/src/evm/EvmProviderRegistry.js +345 -0
  35. package/dist/esm/src/evm/EvmRequestTracker.d.ts +98 -0
  36. package/dist/esm/src/evm/EvmRequestTracker.js +710 -0
  37. package/dist/esm/src/evm/batch.d.ts +94 -0
  38. package/dist/esm/src/evm/batch.js +123 -0
  39. package/dist/esm/src/queue/EventQueue.d.ts +28 -0
  40. package/dist/esm/src/queue/EventQueue.js +97 -19
  41. package/dist/esm/src/queue/type.d.ts +1 -0
  42. package/dist/esm/src/tracking/TrackingPolicy.d.ts +146 -0
  43. package/dist/esm/src/tracking/TrackingPolicy.js +197 -0
  44. package/dist/esm/src/version.d.ts +1 -1
  45. package/dist/esm/src/version.js +1 -1
  46. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +43 -0
  47. package/dist/esm/src/wagmi/WagmiEventHandler.js +253 -2
  48. package/dist/esm/src/wallet/WalletStateStore.d.ts +223 -0
  49. package/dist/esm/src/wallet/WalletStateStore.js +512 -0
  50. package/dist/index.umd.min.js +1 -1
  51. package/package.json +6 -4
@@ -0,0 +1,710 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
23
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ var __rest = (this && this.__rest) || function (s, e) {
49
+ var t = {};
50
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
51
+ t[p] = s[p];
52
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
53
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
54
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
55
+ t[p[i]] = s[p[i]];
56
+ }
57
+ return t;
58
+ };
59
+ import { logger } from "../logger";
60
+ import { parseChainId } from "../utils/chain";
61
+ import { validateAndChecksumAddress } from "../utils/address";
62
+ import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "../types";
63
+ import { readBatchId, readBatchStatusCode, batchCallOutcome, batchReceiptForCall, } from "./batch";
64
+ /**
65
+ * Decode a hex-encoded `personal_sign` message.
66
+ *
67
+ * Deliberately not `Buffer.from(hex, "hex")`. `Buffer` is a Node global with
68
+ * no polyfill in this bundle, so in a browser that line threw and the
69
+ * signature event was dropped with it - silently, which is the worst way for
70
+ * analytics to fail. `TextDecoder` is available everywhere this SDK runs, and
71
+ * `TextEncoder` is already used elsewhere in the queue.
72
+ *
73
+ * Malformed input decodes to as much as can be read rather than throwing: a
74
+ * message we cannot fully read is still worth reporting the signature for.
75
+ */
76
+ function hexToUtf8(hex) {
77
+ var clean = typeof hex === "string" && hex.startsWith("0x") ? hex.slice(2) : hex;
78
+ if (typeof clean !== "string" || clean.length === 0)
79
+ return "";
80
+ var byteCount = Math.floor(clean.length / 2);
81
+ var bytes = new Uint8Array(byteCount);
82
+ for (var i = 0; i < byteCount; i++) {
83
+ var byte = Number.parseInt(clean.substring(i * 2, i * 2 + 2), 16);
84
+ if (Number.isNaN(byte))
85
+ return new TextDecoder().decode(bytes.subarray(0, i));
86
+ bytes[i] = byte;
87
+ }
88
+ return new TextDecoder().decode(bytes);
89
+ }
90
+ /**
91
+ * Autocapture for signatures and transactions, by wrapping a provider's
92
+ * `request`.
93
+ *
94
+ * The wrapper is deliberately thin: it observes the call the dapp was already
95
+ * making and never issues one of its own. That rule is why the chain a
96
+ * request ran on is read from `eth_chainId` calls the app makes, rather than
97
+ * probed - an SDK-issued lookup on a serialising transport can wedge the
98
+ * user's wallet, which is never an acceptable price for a label.
99
+ */
100
+ var EvmRequestTracker = /** @class */ (function () {
101
+ function EvmRequestTracker(wallet, registry, deps) {
102
+ this.wallet = wallet;
103
+ this.registry = registry;
104
+ this.deps = deps;
105
+ /**
106
+ * Timers for receipt and batch-status polling.
107
+ *
108
+ * A poll re-arms for up to thirty seconds, so a torn-down instance would
109
+ * otherwise keep asking a wallet about transactions nobody is listening
110
+ * for, and hold the process open for the whole window. That is the same
111
+ * shape as the batch timer that hung the test suite in #338.
112
+ */
113
+ this.polls = new Set();
114
+ this.disposed = false;
115
+ }
116
+ /** Stop every poll in flight. Terminal, like the event queue's close(). */
117
+ EvmRequestTracker.prototype.cleanup = function () {
118
+ this.disposed = true;
119
+ this.polls.forEach(function (timer) { return clearTimeout(timer); });
120
+ this.polls.clear();
121
+ };
122
+ /** Re-arm a poll, unless this tracker has been torn down. */
123
+ EvmRequestTracker.prototype.schedulePoll = function (fn, delayMs) {
124
+ var _this = this;
125
+ if (this.disposed)
126
+ return;
127
+ var timer = setTimeout(function () {
128
+ _this.polls.delete(timer);
129
+ fn();
130
+ }, delayMs);
131
+ this.polls.add(timer);
132
+ };
133
+ /**
134
+ * Wrap a provider's `request`. Returns whether the wrapper is installed:
135
+ * a caller that marks the provider as tracked on a false return would
136
+ * never retry it, and every signature and transaction from that wallet
137
+ * would be missed for the rest of the session.
138
+ */
139
+ EvmRequestTracker.prototype.registerRequestListeners = function (provider) {
140
+ var _this = this;
141
+ logger.info("registerRequestListeners");
142
+ if (!provider) {
143
+ logger.error("Provider not found for request (signature, transaction) tracking");
144
+ return false;
145
+ }
146
+ // Check if the provider is already wrapped with our SDK's wrapper
147
+ var currentRequest = provider.request;
148
+ if (this.registry.isWrapped(provider, currentRequest)) {
149
+ logger.info("Provider already wrapped with our SDK; skipping request wrapping.");
150
+ return true;
151
+ }
152
+ var request = provider.request.bind(provider);
153
+ var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
154
+ var generation_1, responsePromise, capturedChainId_1, response_1, error_1, rpcError, txPromise, txChainId_1, transactionHash_1, error_2, rpcError;
155
+ var _this = this;
156
+ var method = _b.method, params = _b.params;
157
+ return __generator(this, function (_c) {
158
+ switch (_c.label) {
159
+ case 0:
160
+ // Learn the chain from a call the APP was making anyway.
161
+ //
162
+ // A standards-compliant provider need not expose a synchronous `chainId`
163
+ // property, and if it connected before the SDK initialised, its
164
+ // `connect` event is never replayed. Such a provider stayed unknown -
165
+ // reported as chain 0, and with `excludeChains` configured its events
166
+ // were dropped even on an allowed chain. This adds no request of its
167
+ // own; it only reads the answer to one the dapp already sent.
168
+ if (method === "eth_chainId") {
169
+ generation_1 = this.registry.chainGeneration(provider);
170
+ return [2 /*return*/, request({ method: method, params: params }).then(function (result) {
171
+ // A `chainChanged` for THIS provider may have landed while this was
172
+ // in flight. It is newer by definition, so it must not be
173
+ // overwritten by this answer.
174
+ if (generation_1 === (_this.registry.chainGeneration(provider)) &&
175
+ typeof result === "string") {
176
+ _this.registry.rememberChain(provider, parseChainId(result));
177
+ }
178
+ return result;
179
+ })];
180
+ }
181
+ if (!(Array.isArray(params) &&
182
+ ["eth_signTypedData_v4", "personal_sign"].includes(method))) return [3 /*break*/, 4];
183
+ if (!this.deps.isAutocaptureEnabled("signature")) {
184
+ logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
185
+ return [2 /*return*/, request({ method: method, params: params })];
186
+ }
187
+ responsePromise = request({ method: method, params: params });
188
+ // Attach a no-op handler now so a rejection arriving before the await
189
+ // below is never reported as unhandled. The real handling is there.
190
+ responsePromise.catch(function () { return undefined; });
191
+ capturedChainId_1 = this.registry.resolveChainId(provider);
192
+ // Fire-and-forget tracking
193
+ (function () { return __awaiter(_this, void 0, void 0, function () {
194
+ var e_1;
195
+ return __generator(this, function (_a) {
196
+ switch (_a.label) {
197
+ case 0:
198
+ _a.trys.push([0, 2, , 3]);
199
+ return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
200
+ case 1:
201
+ _a.sent();
202
+ return [3 /*break*/, 3];
203
+ case 2:
204
+ e_1 = _a.sent();
205
+ logger.error("Formo: Failed to track signature request", e_1);
206
+ return [3 /*break*/, 3];
207
+ case 3: return [2 /*return*/];
208
+ }
209
+ });
210
+ }); })();
211
+ _c.label = 1;
212
+ case 1:
213
+ _c.trys.push([1, 3, , 4]);
214
+ return [4 /*yield*/, responsePromise];
215
+ case 2:
216
+ response_1 = _c.sent();
217
+ // Track signature confirmation only for truthy responses
218
+ if (response_1) {
219
+ (function () { return __awaiter(_this, void 0, void 0, function () {
220
+ var e_2;
221
+ return __generator(this, function (_a) {
222
+ switch (_a.label) {
223
+ case 0:
224
+ _a.trys.push([0, 2, , 3]);
225
+ return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)))];
226
+ case 1:
227
+ _a.sent();
228
+ return [3 /*break*/, 3];
229
+ case 2:
230
+ e_2 = _a.sent();
231
+ logger.error("Formo: Failed to track signature confirmation", e_2);
232
+ return [3 /*break*/, 3];
233
+ case 3: return [2 /*return*/];
234
+ }
235
+ });
236
+ }); })();
237
+ }
238
+ return [2 /*return*/, response_1];
239
+ case 3:
240
+ error_1 = _c.sent();
241
+ rpcError = error_1;
242
+ if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
243
+ // Use the already cast rpcError to avoid duplication
244
+ (function () { return __awaiter(_this, void 0, void 0, function () {
245
+ var e_3;
246
+ return __generator(this, function (_a) {
247
+ switch (_a.label) {
248
+ case 0:
249
+ _a.trys.push([0, 2, , 3]);
250
+ return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
251
+ case 1:
252
+ _a.sent();
253
+ return [3 /*break*/, 3];
254
+ case 2:
255
+ e_3 = _a.sent();
256
+ logger.error("Formo: Failed to track signature rejection", e_3);
257
+ return [3 /*break*/, 3];
258
+ case 3: return [2 /*return*/];
259
+ }
260
+ });
261
+ }); })();
262
+ }
263
+ throw error_1;
264
+ case 4:
265
+ // Handle EIP-5792 batched calls.
266
+ //
267
+ // Smart accounts send through `wallet_sendCalls` rather than
268
+ // `eth_sendTransaction`, and until now the SDK understood only the
269
+ // latter: those transactions were not captured at all, silently. That
270
+ // is the same failure shape as the missing-connect bug that started
271
+ // this work - nothing errors, the data is simply absent.
272
+ if (method === "wallet_sendCalls" && Array.isArray(params) && params[0]) {
273
+ return [2 /*return*/, this.trackBatchedCalls(provider, request, params)];
274
+ }
275
+ if (!(Array.isArray(params) &&
276
+ method === "eth_sendTransaction" &&
277
+ params[0])) return [3 /*break*/, 8];
278
+ if (!this.deps.isAutocaptureEnabled("transaction")) {
279
+ logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
280
+ return [2 /*return*/, request({ method: method, params: params })];
281
+ }
282
+ txPromise = request({ method: method, params: params });
283
+ txPromise.catch(function () { return undefined; });
284
+ txChainId_1 = this.registry.resolveChainId(provider);
285
+ (function () { return __awaiter(_this, void 0, void 0, function () {
286
+ var payload, e_4;
287
+ return __generator(this, function (_a) {
288
+ switch (_a.label) {
289
+ case 0:
290
+ _a.trys.push([0, 3, , 4]);
291
+ return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
292
+ case 1:
293
+ payload = _a.sent();
294
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.STARTED }, payload))];
295
+ case 2:
296
+ _a.sent();
297
+ return [3 /*break*/, 4];
298
+ case 3:
299
+ e_4 = _a.sent();
300
+ logger.error("Formo: Failed to track transaction start", e_4);
301
+ return [3 /*break*/, 4];
302
+ case 4: return [2 /*return*/];
303
+ }
304
+ });
305
+ }); })();
306
+ _c.label = 5;
307
+ case 5:
308
+ _c.trys.push([5, 7, , 8]);
309
+ return [4 /*yield*/, txPromise];
310
+ case 6:
311
+ transactionHash_1 = _c.sent();
312
+ (function () { return __awaiter(_this, void 0, void 0, function () {
313
+ var payload, e_5;
314
+ return __generator(this, function (_a) {
315
+ switch (_a.label) {
316
+ case 0:
317
+ _a.trys.push([0, 3, , 4]);
318
+ return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
319
+ case 1:
320
+ payload = _a.sent();
321
+ return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }))];
322
+ case 2:
323
+ _a.sent();
324
+ // Start async polling for transaction receipt
325
+ this.pollTransactionReceipt(provider, transactionHash_1, payload);
326
+ return [3 /*break*/, 4];
327
+ case 3:
328
+ e_5 = _a.sent();
329
+ logger.error("Formo: Failed to track transaction broadcast", e_5);
330
+ return [3 /*break*/, 4];
331
+ case 4: return [2 /*return*/];
332
+ }
333
+ });
334
+ }); })();
335
+ return [2 /*return*/, transactionHash_1];
336
+ case 7:
337
+ error_2 = _c.sent();
338
+ rpcError = error_2;
339
+ if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
340
+ // Use the already cast rpcError to avoid duplication
341
+ (function () { return __awaiter(_this, void 0, void 0, function () {
342
+ var payload, e_6;
343
+ return __generator(this, function (_a) {
344
+ switch (_a.label) {
345
+ case 0:
346
+ _a.trys.push([0, 3, , 4]);
347
+ return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
348
+ case 1:
349
+ payload = _a.sent();
350
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.REJECTED }, payload))];
351
+ case 2:
352
+ _a.sent();
353
+ return [3 /*break*/, 4];
354
+ case 3:
355
+ e_6 = _a.sent();
356
+ logger.error("Formo: Failed to track transaction rejection", e_6);
357
+ return [3 /*break*/, 4];
358
+ case 4: return [2 /*return*/];
359
+ }
360
+ });
361
+ }); })();
362
+ }
363
+ throw error_2;
364
+ case 8: return [2 /*return*/, request({ method: method, params: params })];
365
+ }
366
+ });
367
+ }); };
368
+ // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
369
+ wrappedRequest[WRAPPED_REQUEST_SYMBOL] = true;
370
+ // Both writes go inside the try. A frozen or non-extensible provider
371
+ // throws on the symbol assignment just as readily as on `request`, and
372
+ // that one used to sit outside the guard, so an unwrappable provider
373
+ // aborted registration instead of being skipped.
374
+ try {
375
+ provider[WRAPPED_REQUEST_REF_SYMBOL] =
376
+ wrappedRequest;
377
+ provider.request = wrappedRequest;
378
+ return true;
379
+ }
380
+ catch (e) {
381
+ logger.warn("Failed to wrap provider.request; skipping", e);
382
+ return false;
383
+ }
384
+ };
385
+ EvmRequestTracker.prototype.buildSignatureEventPayload = function (method, params,
386
+ // Intentionally not read. Kept for positional call-site arity.
387
+ _response, chainId, provider) {
388
+ var _a;
389
+ var rawAddress = method === "personal_sign"
390
+ ? params[1]
391
+ : params[0];
392
+ var validAddress = validateAndChecksumAddress(rawAddress);
393
+ if (!validAddress) {
394
+ throw new Error("Invalid address in signature payload: ".concat(rawAddress));
395
+ }
396
+ var effectiveChainId = (_a = chainId !== null && chainId !== void 0 ? chainId : this.wallet.evmChainId) !== null && _a !== void 0 ? _a : undefined;
397
+ // Only the active provider may write central wallet state - see the same
398
+ // guard in buildTransactionEventPayload.
399
+ if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
400
+ this.wallet.backfill(validAddress, effectiveChainId, provider);
401
+ }
402
+ var basePayload = {
403
+ chainId: effectiveChainId,
404
+ address: validAddress,
405
+ };
406
+ if (method === "personal_sign") {
407
+ return __assign(__assign({}, basePayload), { message: hexToUtf8(params[0]) });
408
+ }
409
+ // eth_signTypedData*: params[1] is the full EIP-712 struct.
410
+ return __assign(__assign({}, basePayload), { message: params[1] });
411
+ };
412
+ EvmRequestTracker.prototype.buildTransactionEventPayload = function (params, provider,
413
+ /**
414
+ * Chain resolved once for this request's whole lifecycle. Passing it keeps
415
+ * every status of one transaction on the same chain even if the user
416
+ * switches network while the wallet prompt is open.
417
+ */
418
+ capturedChainId) {
419
+ return __awaiter(this, void 0, void 0, function () {
420
+ var _a, data, from, to, value, validAddress, chainId;
421
+ return __generator(this, function (_b) {
422
+ _a = params[0], data = _a.data, from = _a.from, to = _a.to, value = _a.value;
423
+ validAddress = validateAndChecksumAddress(from);
424
+ if (!validAddress) {
425
+ throw new Error("Invalid address in transaction payload: ".concat(from));
426
+ }
427
+ chainId = capturedChainId !== null && capturedChainId !== void 0 ? capturedChainId : this.registry.resolveChainId(provider);
428
+ // Only the ACTIVE provider may write central wallet state. A request from
429
+ // a second, non-active wallet would otherwise overwrite the active
430
+ // provider's address and chain, and every later request through the active
431
+ // provider would then trust the other wallet's chain - persistent
432
+ // mis-attribution, and a way around `excludeChains`.
433
+ if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
434
+ this.wallet.backfill(validAddress, chainId, provider);
435
+ }
436
+ return [2 /*return*/, {
437
+ chainId: chainId,
438
+ data: data,
439
+ address: validAddress,
440
+ to: to,
441
+ value: value,
442
+ }];
443
+ });
444
+ });
445
+ };
446
+ /**
447
+ * Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
448
+ */
449
+ EvmRequestTracker.prototype.pollTransactionReceipt = function (provider_1, transactionHash_2, payload_1) {
450
+ return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
451
+ var attempts, poll;
452
+ var _this = this;
453
+ if (maxAttempts === void 0) { maxAttempts = 10; }
454
+ if (intervalMs === void 0) { intervalMs = 3000; }
455
+ return __generator(this, function (_a) {
456
+ attempts = 0;
457
+ if (!provider)
458
+ return [2 /*return*/];
459
+ poll = function () { return __awaiter(_this, void 0, void 0, function () {
460
+ var receipt, e_7;
461
+ return __generator(this, function (_a) {
462
+ switch (_a.label) {
463
+ case 0:
464
+ if (this.disposed)
465
+ return [2 /*return*/];
466
+ _a.label = 1;
467
+ case 1:
468
+ _a.trys.push([1, 3, , 4]);
469
+ return [4 /*yield*/, provider.request({
470
+ method: "eth_getTransactionReceipt",
471
+ params: [transactionHash],
472
+ })];
473
+ case 2:
474
+ receipt = (_a.sent());
475
+ if (receipt) {
476
+ // status: 1 = success, 0 = reverted
477
+ if (receipt.status === "0x1" || receipt.status === 1) {
478
+ this.deps
479
+ .transaction(__assign(__assign({ status: TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }))
480
+ .catch(function (e) {
481
+ return logger.error("Formo: Failed to track transaction confirmation", e);
482
+ });
483
+ return [2 /*return*/];
484
+ }
485
+ else if (receipt.status === "0x0" || receipt.status === 0) {
486
+ this.deps
487
+ .transaction(__assign(__assign({ status: TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }))
488
+ .catch(function (e) {
489
+ return logger.error("Formo: Failed to track transaction revert", e);
490
+ });
491
+ return [2 /*return*/];
492
+ }
493
+ }
494
+ return [3 /*break*/, 4];
495
+ case 3:
496
+ e_7 = _a.sent();
497
+ logger.error("Error polling transaction receipt", e_7);
498
+ return [3 /*break*/, 4];
499
+ case 4:
500
+ attempts++;
501
+ if (attempts < maxAttempts)
502
+ this.schedulePoll(poll, intervalMs);
503
+ return [2 /*return*/];
504
+ }
505
+ });
506
+ }); };
507
+ poll();
508
+ return [2 /*return*/];
509
+ });
510
+ });
511
+ };
512
+ /**
513
+ * One `transaction` event per call in an EIP-5792 batch.
514
+ *
515
+ * The CALL is the unit of attribution: each has its own target, calldata,
516
+ * and value, and folding a batch into one event would misattribute revenue
517
+ * and per-contract activity for every app that adopts smart accounts. How
518
+ * many on-chain transactions a batch becomes depends on execution - an
519
+ * atomic batch lands as ONE transaction, a non-atomic fallback as several -
520
+ * so on-chain volume is `count(distinct transaction_hash)`, wallet actions
521
+ * `count(distinct batch_id)`, never the event count. Each call is reported
522
+ * on its own, carrying the batch id so the calls reassemble downstream.
523
+ *
524
+ * Status is per BATCH, because that is what `wallet_getCallsStatus` reports.
525
+ * When it resolves, every call in the batch moves together, except where
526
+ * per-call receipts say otherwise on a non-atomic batch.
527
+ */
528
+ EvmRequestTracker.prototype.trackBatchedCalls = function (provider, request, params) {
529
+ return __awaiter(this, void 0, void 0, function () {
530
+ var sendPromise, batch, calls, declared, chainId, address, payloads, _i, payloads_1, p, properties, rest, result, batchId, _a, payloads_2, p, properties, rest, error_3, rpcError, _b, payloads_3, p, properties, rest;
531
+ var _c;
532
+ return __generator(this, function (_d) {
533
+ switch (_d.label) {
534
+ case 0:
535
+ if (!this.deps.isAutocaptureEnabled("transaction")) {
536
+ logger.debug("Transaction event skipped (autocapture.transaction: false)", {
537
+ method: "wallet_sendCalls",
538
+ });
539
+ return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
540
+ }
541
+ sendPromise = request({ method: "wallet_sendCalls", params: params });
542
+ sendPromise.catch(function () { return undefined; });
543
+ batch = params[0];
544
+ calls = Array.isArray(batch === null || batch === void 0 ? void 0 : batch.calls) ? batch.calls : [];
545
+ declared = typeof (batch === null || batch === void 0 ? void 0 : batch.chainId) === "string" ? parseChainId(batch.chainId) : undefined;
546
+ chainId = declared || this.registry.resolveChainId(provider);
547
+ address = validateAndChecksumAddress((_c = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _c !== void 0 ? _c : "");
548
+ if (!address) {
549
+ // Nothing can be attributed without a sender, and inventing one would
550
+ // be worse than reporting nothing. The user's call still goes through.
551
+ logger.warn("Formo: wallet_sendCalls has no valid `from`; not tracking", {
552
+ from: batch === null || batch === void 0 ? void 0 : batch.from,
553
+ });
554
+ return [2 /*return*/, sendPromise];
555
+ }
556
+ if (calls.length === 0) {
557
+ logger.debug("Formo: wallet_sendCalls carried no calls");
558
+ return [2 /*return*/, sendPromise];
559
+ }
560
+ // Only the ACTIVE provider may write central wallet state, for the same
561
+ // reason as the single-transaction path: a second wallet would otherwise
562
+ // overwrite the active provider's address and chain.
563
+ if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
564
+ this.wallet.backfill(address, chainId, provider);
565
+ }
566
+ payloads = calls.map(function (call, index) { return ({
567
+ chainId: chainId,
568
+ address: address,
569
+ to: call === null || call === void 0 ? void 0 : call.to,
570
+ value: call === null || call === void 0 ? void 0 : call.value,
571
+ data: call === null || call === void 0 ? void 0 : call.data,
572
+ properties: {
573
+ batch_size: calls.length,
574
+ batch_index: index,
575
+ },
576
+ }); });
577
+ // STARTED carries no batch id: the wallet has not issued one yet, exactly
578
+ // as `eth_sendTransaction` has no hash at this point. Position within the
579
+ // batch is known, and is what groups these until the id arrives.
580
+ for (_i = 0, payloads_1 = payloads; _i < payloads_1.length; _i++) {
581
+ p = payloads_1[_i];
582
+ properties = p.properties, rest = __rest(p, ["properties"]);
583
+ this.deps
584
+ .transaction(__assign({ status: TransactionStatus.STARTED }, rest), properties)
585
+ .catch(function (e) { return logger.error("Formo: Failed to track batch call start", e); });
586
+ }
587
+ _d.label = 1;
588
+ case 1:
589
+ _d.trys.push([1, 3, , 4]);
590
+ return [4 /*yield*/, sendPromise];
591
+ case 2:
592
+ result = _d.sent();
593
+ batchId = readBatchId(result);
594
+ for (_a = 0, payloads_2 = payloads; _a < payloads_2.length; _a++) {
595
+ p = payloads_2[_a];
596
+ properties = p.properties, rest = __rest(p, ["properties"]);
597
+ this.deps
598
+ .transaction(__assign({ status: TransactionStatus.BROADCASTED }, rest), __assign(__assign({}, properties), (batchId ? { batch_id: batchId } : {})))
599
+ .catch(function (e) {
600
+ return logger.error("Formo: Failed to track batch call broadcast", e);
601
+ });
602
+ }
603
+ if (batchId)
604
+ this.pollBatchStatus(provider, batchId, payloads);
605
+ return [2 /*return*/, result];
606
+ case 3:
607
+ error_3 = _d.sent();
608
+ rpcError = error_3;
609
+ if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
610
+ // One rejection dismisses the whole prompt, so every call in it is
611
+ // rejected. Reporting only the first would undercount.
612
+ for (_b = 0, payloads_3 = payloads; _b < payloads_3.length; _b++) {
613
+ p = payloads_3[_b];
614
+ properties = p.properties, rest = __rest(p, ["properties"]);
615
+ this.deps
616
+ .transaction(__assign({ status: TransactionStatus.REJECTED }, rest), properties)
617
+ .catch(function (e) {
618
+ return logger.error("Formo: Failed to track batch call rejection", e);
619
+ });
620
+ }
621
+ }
622
+ throw error_3;
623
+ case 4: return [2 /*return*/];
624
+ }
625
+ });
626
+ });
627
+ };
628
+ /**
629
+ * Resolve a batch through `wallet_getCallsStatus`.
630
+ *
631
+ * The status codes are EIP-5792's: 100 pending, 200 confirmed, 400 failed
632
+ * before landing, 500 reverted, 600 partially reverted. Anything below 200
633
+ * means keep waiting.
634
+ *
635
+ * A per-call receipt wins over the batch verdict where one exists, which is
636
+ * what makes a partially reverted non-atomic batch report honestly instead
637
+ * of marking every call with the batch's worst outcome.
638
+ */
639
+ EvmRequestTracker.prototype.pollBatchStatus = function (provider_1, batchId_1, payloads_4) {
640
+ return __awaiter(this, arguments, void 0, function (provider, batchId, payloads, maxAttempts, intervalMs) {
641
+ var attempts, poll;
642
+ var _this = this;
643
+ if (maxAttempts === void 0) { maxAttempts = 10; }
644
+ if (intervalMs === void 0) { intervalMs = 3000; }
645
+ return __generator(this, function (_a) {
646
+ if (!provider)
647
+ return [2 /*return*/];
648
+ attempts = 0;
649
+ poll = function () { return __awaiter(_this, void 0, void 0, function () {
650
+ var res_1, code_1, e_8;
651
+ var _this = this;
652
+ return __generator(this, function (_a) {
653
+ switch (_a.label) {
654
+ case 0:
655
+ if (this.disposed)
656
+ return [2 /*return*/];
657
+ _a.label = 1;
658
+ case 1:
659
+ _a.trys.push([1, 3, , 4]);
660
+ return [4 /*yield*/, provider.request({
661
+ method: "wallet_getCallsStatus",
662
+ params: [batchId],
663
+ })];
664
+ case 2:
665
+ res_1 = (_a.sent());
666
+ code_1 = readBatchStatusCode(res_1);
667
+ if (code_1 !== undefined && code_1 >= 200) {
668
+ payloads.forEach(function (p, index) {
669
+ // Atomic-aware: one receipt covering the whole batch reaches
670
+ // every call, hash included, not just call 0.
671
+ var receipt = batchReceiptForCall(res_1, index, payloads.length);
672
+ var outcome = batchCallOutcome(code_1, receipt);
673
+ // 600 means SOME calls reverted, so a call with no receipt of its
674
+ // own has not been decided. Reporting it either way would invent
675
+ // a result; leaving it unsettled is the honest answer.
676
+ if (outcome === undefined)
677
+ return;
678
+ var properties = p.properties, rest = __rest(p, ["properties"]);
679
+ _this.deps
680
+ .transaction(__assign(__assign({ status: outcome }, rest), ((receipt === null || receipt === void 0 ? void 0 : receipt.transactionHash)
681
+ ? { transactionHash: receipt.transactionHash }
682
+ : {})), __assign(__assign({}, properties), { batch_id: batchId }))
683
+ .catch(function (e) {
684
+ return logger.error("Formo: Failed to track batch call outcome", e);
685
+ });
686
+ });
687
+ return [2 /*return*/];
688
+ }
689
+ return [3 /*break*/, 4];
690
+ case 3:
691
+ e_8 = _a.sent();
692
+ logger.error("Error polling batch call status", e_8);
693
+ return [3 /*break*/, 4];
694
+ case 4:
695
+ attempts++;
696
+ if (attempts < maxAttempts)
697
+ this.schedulePoll(poll, intervalMs);
698
+ return [2 /*return*/];
699
+ }
700
+ });
701
+ }); };
702
+ poll();
703
+ return [2 /*return*/];
704
+ });
705
+ });
706
+ };
707
+ return EvmRequestTracker;
708
+ }());
709
+ export { EvmRequestTracker };
710
+ //# sourceMappingURL=EvmRequestTracker.js.map