@formo/analytics 1.26.0 → 1.27.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 (37) hide show
  1. package/README.md +6 -4
  2. package/dist/cjs/src/FormoAnalytics.d.ts +3 -1
  3. package/dist/cjs/src/FormoAnalytics.js +3 -2
  4. package/dist/cjs/src/event/EventFactory.d.ts +1 -1
  5. package/dist/cjs/src/event/EventFactory.js +3 -3
  6. package/dist/cjs/src/fetch/index.d.ts +10 -2
  7. package/dist/cjs/src/fetch/index.js +122 -4
  8. package/dist/cjs/src/queue/EventQueue.d.ts +24 -2
  9. package/dist/cjs/src/queue/EventQueue.js +158 -49
  10. package/dist/cjs/src/types/base.d.ts +8 -0
  11. package/dist/cjs/src/types/events.d.ts +5 -3
  12. package/dist/cjs/src/version.d.ts +1 -1
  13. package/dist/cjs/src/version.js +1 -1
  14. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +24 -0
  15. package/dist/cjs/src/wagmi/WagmiEventHandler.js +236 -16
  16. package/dist/cjs/src/wagmi/types.d.ts +31 -0
  17. package/dist/cjs/src/wagmi/utils.d.ts +74 -0
  18. package/dist/cjs/src/wagmi/utils.js +198 -0
  19. package/dist/esm/src/FormoAnalytics.d.ts +3 -1
  20. package/dist/esm/src/FormoAnalytics.js +3 -2
  21. package/dist/esm/src/event/EventFactory.d.ts +1 -1
  22. package/dist/esm/src/event/EventFactory.js +3 -3
  23. package/dist/esm/src/fetch/index.d.ts +10 -2
  24. package/dist/esm/src/fetch/index.js +123 -2
  25. package/dist/esm/src/queue/EventQueue.d.ts +24 -2
  26. package/dist/esm/src/queue/EventQueue.js +158 -49
  27. package/dist/esm/src/types/base.d.ts +8 -0
  28. package/dist/esm/src/types/events.d.ts +5 -3
  29. package/dist/esm/src/version.d.ts +1 -1
  30. package/dist/esm/src/version.js +1 -1
  31. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +24 -0
  32. package/dist/esm/src/wagmi/WagmiEventHandler.js +236 -16
  33. package/dist/esm/src/wagmi/types.d.ts +31 -0
  34. package/dist/esm/src/wagmi/utils.d.ts +74 -0
  35. package/dist/esm/src/wagmi/utils.js +192 -0
  36. package/dist/index.umd.min.js +1 -1
  37. package/package.json +6 -2
@@ -57,6 +57,40 @@ Object.defineProperty(exports, "__esModule", { value: true });
57
57
  exports.WagmiEventHandler = void 0;
58
58
  var events_1 = require("../types/events");
59
59
  var logger_1 = require("../logger");
60
+ var utils_1 = require("./utils");
61
+ /**
62
+ * Built-in transaction fields that could collide with function args.
63
+ * Defined at module level to avoid recreating on every method call.
64
+ */
65
+ var RESERVED_FIELDS = new Set([
66
+ "status",
67
+ "chainId",
68
+ "address",
69
+ "data",
70
+ "to",
71
+ "value",
72
+ "transactionHash",
73
+ "function_name",
74
+ "function_args",
75
+ ]);
76
+ /**
77
+ * Clean up old entries from a Set to prevent memory leaks.
78
+ * Removes oldest entries when size exceeds maxSize.
79
+ *
80
+ * @param set - The Set to clean up
81
+ * @param maxSize - Maximum allowed size before cleanup (default: 1000)
82
+ * @param removeCount - Number of entries to remove (default: 500)
83
+ */
84
+ function cleanupOldEntries(set, maxSize, removeCount) {
85
+ if (maxSize === void 0) { maxSize = 1000; }
86
+ if (removeCount === void 0) { removeCount = 500; }
87
+ if (set.size > maxSize) {
88
+ var entries = Array.from(set);
89
+ for (var i = 0; i < removeCount && i < entries.length; i++) {
90
+ set.delete(entries[i]);
91
+ }
92
+ }
93
+ }
60
94
  var WagmiEventHandler = /** @class */ (function () {
61
95
  function WagmiEventHandler(formoAnalytics, wagmiConfig, queryClient) {
62
96
  this.unsubscribers = [];
@@ -68,15 +102,26 @@ var WagmiEventHandler = /** @class */ (function () {
68
102
  * Key format: `${mutationId}:${status}`
69
103
  */
70
104
  this.processedMutations = new Set();
105
+ /**
106
+ * Track processed query states to prevent duplicate event emissions
107
+ * Key format: `${queryHash}:${status}`
108
+ */
109
+ this.processedQueries = new Set();
110
+ /**
111
+ * Store transaction details from BROADCASTED events for use in CONFIRMED/REVERTED
112
+ * Key: transactionHash, Value: transaction details including the original sender address
113
+ */
114
+ this.pendingTransactions = new Map();
71
115
  this.formo = formoAnalytics;
72
116
  this.wagmiConfig = wagmiConfig;
73
117
  this.queryClient = queryClient;
74
118
  logger_1.logger.info("WagmiEventHandler: Initializing Wagmi integration");
75
119
  // Set up connection/disconnection/chain listeners
76
120
  this.setupConnectionListeners();
77
- // Set up mutation tracking if QueryClient is provided
121
+ // Set up mutation and query tracking if QueryClient is provided
78
122
  if (this.queryClient) {
79
123
  this.setupMutationTracking();
124
+ this.setupQueryTracking();
80
125
  }
81
126
  else {
82
127
  logger_1.logger.warn("WagmiEventHandler: QueryClient not provided, signature and transaction events will not be tracked");
@@ -226,6 +271,131 @@ var WagmiEventHandler = /** @class */ (function () {
226
271
  this.unsubscribers.push(unsubscribe);
227
272
  logger_1.logger.info("WagmiEventHandler: Mutation tracking set up successfully");
228
273
  };
274
+ /**
275
+ * Set up query tracking for transaction confirmations
276
+ * Listens for waitForTransactionReceipt queries to detect CONFIRMED status
277
+ */
278
+ WagmiEventHandler.prototype.setupQueryTracking = function () {
279
+ var _this = this;
280
+ if (!this.queryClient) {
281
+ return;
282
+ }
283
+ logger_1.logger.info("WagmiEventHandler: Setting up query tracking");
284
+ var queryCache = this.queryClient.getQueryCache();
285
+ var unsubscribe = queryCache.subscribe(function (event) {
286
+ _this.handleQueryEvent(event);
287
+ });
288
+ this.unsubscribers.push(unsubscribe);
289
+ logger_1.logger.info("WagmiEventHandler: Query tracking set up successfully");
290
+ };
291
+ /**
292
+ * Handle query cache events (transaction confirmations)
293
+ */
294
+ WagmiEventHandler.prototype.handleQueryEvent = function (event) {
295
+ if (event.type !== "updated") {
296
+ return;
297
+ }
298
+ var query = event.query;
299
+ var queryKey = query.queryKey;
300
+ if (!queryKey || queryKey.length === 0) {
301
+ return;
302
+ }
303
+ var queryType = queryKey[0];
304
+ // Only handle waitForTransactionReceipt queries
305
+ if (queryType !== "waitForTransactionReceipt") {
306
+ return;
307
+ }
308
+ var state = query.state;
309
+ // Extract receipt status early to include in deduplication key
310
+ // This ensures CONFIRMED vs REVERTED outcomes are processed separately
311
+ var receipt = state.data;
312
+ var receiptStatus = receipt === null || receipt === void 0 ? void 0 : receipt.status;
313
+ // Create a unique key for this query state to prevent duplicate processing
314
+ // Include receipt status to distinguish between CONFIRMED and REVERTED outcomes
315
+ var queryStateKey = "".concat(query.queryHash, ":").concat(state.status, ":").concat(receiptStatus || "");
316
+ // Skip if we've already processed this query state
317
+ if (this.processedQueries.has(queryStateKey)) {
318
+ logger_1.logger.debug("WagmiEventHandler: Skipping duplicate query event", {
319
+ queryType: queryType,
320
+ queryHash: query.queryHash,
321
+ status: state.status,
322
+ receiptStatus: receiptStatus,
323
+ });
324
+ return;
325
+ }
326
+ // Mark this query state as processed
327
+ this.processedQueries.add(queryStateKey);
328
+ logger_1.logger.debug("WagmiEventHandler: Query event", {
329
+ queryType: queryType,
330
+ queryHash: query.queryHash,
331
+ status: state.status,
332
+ });
333
+ // Handle transaction receipt queries
334
+ this.handleTransactionReceiptQuery(query);
335
+ // Clean up old processed queries to prevent memory leaks
336
+ cleanupOldEntries(this.processedQueries);
337
+ };
338
+ /**
339
+ * Handle waitForTransactionReceipt query completion
340
+ * Emits CONFIRMED or REVERTED transaction status
341
+ */
342
+ WagmiEventHandler.prototype.handleTransactionReceiptQuery = function (query) {
343
+ var _a;
344
+ if (!this.formo.isAutocaptureEnabled("transaction")) {
345
+ return;
346
+ }
347
+ var state = query.state;
348
+ var queryKey = query.queryKey;
349
+ // Only handle successful queries (transaction confirmed on chain)
350
+ if (state.status !== "success") {
351
+ return;
352
+ }
353
+ // Extract hash and chainId from query key
354
+ // Query key format: ['waitForTransactionReceipt', { hash, chainId, ... }]
355
+ var params = queryKey[1];
356
+ var transactionHash = params === null || params === void 0 ? void 0 : params.hash;
357
+ var chainId = (params === null || params === void 0 ? void 0 : params.chainId) || this.trackingState.lastChainId;
358
+ if (!transactionHash) {
359
+ logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no hash found");
360
+ return;
361
+ }
362
+ // Retrieve stored transaction details from BROADCASTED event
363
+ // Normalize hash to lowercase for consistent lookup
364
+ var normalizedHash = transactionHash.toLowerCase();
365
+ var pendingTx = this.pendingTransactions.get(normalizedHash);
366
+ // Use the original sender address from BROADCASTED event if available,
367
+ // otherwise fall back to current connected address.
368
+ // This handles wallet switches between broadcast and confirmation.
369
+ var address = (pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.address) || this.trackingState.lastAddress;
370
+ if (!address) {
371
+ logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no address available");
372
+ return;
373
+ }
374
+ try {
375
+ // Extract receipt data
376
+ var receipt = state.data;
377
+ // Determine transaction status from receipt
378
+ // receipt.status is 'success' or 'reverted' in viem
379
+ var txStatus = (receipt === null || receipt === void 0 ? void 0 : receipt.status) === "reverted"
380
+ ? events_1.TransactionStatus.REVERTED
381
+ : events_1.TransactionStatus.CONFIRMED;
382
+ logger_1.logger.info("WagmiEventHandler: Tracking transaction confirmation", {
383
+ status: txStatus,
384
+ transactionHash: transactionHash,
385
+ address: address,
386
+ chainId: chainId,
387
+ blockNumber: (_a = receipt === null || receipt === void 0 ? void 0 : receipt.blockNumber) === null || _a === void 0 ? void 0 : _a.toString(),
388
+ });
389
+ this.formo.transaction(__assign(__assign(__assign(__assign(__assign({ status: txStatus, chainId: chainId || 0, address: address, transactionHash: transactionHash }, ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.data) && { data: pendingTx.data })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.to) && { to: pendingTx.to })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.value) && { value: pendingTx.value })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_name) && { function_name: pendingTx.function_name })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_args) && { function_args: pendingTx.function_args })),
390
+ // Spread function args as additional properties (only colliding keys are prefixed)
391
+ pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.safeFunctionArgs);
392
+ // Clean up the pending transaction after confirmation
393
+ this.pendingTransactions.delete(normalizedHash);
394
+ }
395
+ catch (error) {
396
+ logger_1.logger.error("WagmiEventHandler: Error handling transaction receipt query:", error);
397
+ }
398
+ };
229
399
  /**
230
400
  * Handle mutation cache events (signatures, transactions)
231
401
  */
@@ -267,14 +437,7 @@ var WagmiEventHandler = /** @class */ (function () {
267
437
  this.handleTransactionMutation(mutationType, mutation);
268
438
  }
269
439
  // Clean up old processed mutations to prevent memory leaks
270
- // Keep only recent mutations (max 1000 entries)
271
- if (this.processedMutations.size > 1000) {
272
- var entries = Array.from(this.processedMutations);
273
- // Remove oldest 500 entries
274
- for (var i = 0; i < 500; i++) {
275
- this.processedMutations.delete(entries[i]);
276
- }
277
- }
440
+ cleanupOldEntries(this.processedMutations);
278
441
  };
279
442
  /**
280
443
  * Handle signature mutations (signMessage, signTypedData)
@@ -340,8 +503,15 @@ var WagmiEventHandler = /** @class */ (function () {
340
503
  var state = mutation.state;
341
504
  var variables = state.variables || {};
342
505
  var chainId = this.trackingState.lastChainId || variables.chainId;
343
- var address = this.trackingState.lastAddress || variables.account || variables.address;
344
- if (!address) {
506
+ // For sendTransaction, user's address is the 'from'
507
+ // For writeContract, variables.address is the contract address, not the user
508
+ // variables.account can be a string address or an Account object with an address property
509
+ var accountValue = variables.account;
510
+ var accountAddress = typeof accountValue === "string"
511
+ ? accountValue
512
+ : accountValue === null || accountValue === void 0 ? void 0 : accountValue.address;
513
+ var userAddress = this.trackingState.lastAddress || accountAddress || variables.from;
514
+ if (!userAddress) {
345
515
  logger_1.logger.warn("WagmiEventHandler: Transaction event but no address available");
346
516
  return;
347
517
  }
@@ -362,18 +532,66 @@ var WagmiEventHandler = /** @class */ (function () {
362
532
  else {
363
533
  return; // Ignore idle state
364
534
  }
365
- // Extract transaction details from variables
366
- var data = variables.data;
367
- var to = variables.to || variables.address;
535
+ // Extract transaction details based on mutation type
536
+ var data = void 0;
537
+ var to = void 0;
538
+ var function_name = void 0;
539
+ var function_args = void 0;
368
540
  var value = (_a = variables.value) === null || _a === void 0 ? void 0 : _a.toString();
541
+ if (mutationType === "writeContract") {
542
+ // For writeContract, extract function info and encode data
543
+ var abi = variables.abi, fnName = variables.functionName, args = variables.args, contractAddress = variables.address;
544
+ to = contractAddress;
545
+ function_name = fnName;
546
+ if (abi && fnName) {
547
+ // Extract function arguments as a name-value map
548
+ function_args = (0, utils_1.extractFunctionArgs)(abi, fnName, args);
549
+ // Encode the function data synchronously if viem is available
550
+ var encodedData = (0, utils_1.encodeWriteContractData)(abi, fnName, args);
551
+ if (encodedData) {
552
+ data = encodedData;
553
+ logger_1.logger.debug("WagmiEventHandler: Encoded writeContract data", data.substring(0, 10));
554
+ }
555
+ }
556
+ }
557
+ else {
558
+ // For sendTransaction, use variables directly
559
+ // Only data is available, function_name and function_args are not sent
560
+ data = variables.data;
561
+ to = variables.to;
562
+ }
369
563
  logger_1.logger.info("WagmiEventHandler: Tracking transaction event", {
370
564
  status: status_2,
371
565
  mutationType: mutationType,
372
- address: address,
566
+ address: userAddress,
373
567
  chainId: chainId,
374
568
  transactionHash: transactionHash,
569
+ function_name: function_name,
375
570
  });
376
- this.formo.transaction(__assign(__assign(__assign(__assign({ status: status_2, chainId: chainId || 0, address: address }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (transactionHash && { transactionHash: transactionHash })));
571
+ // Build safeFunctionArgs with collision handling and struct flattening
572
+ var safeFunctionArgs = (0, utils_1.buildSafeFunctionArgs)(function_args, RESERVED_FIELDS);
573
+ // Store transaction details for BROADCASTED status to use in CONFIRMED/REVERTED
574
+ // Normalize hash to lowercase for consistent lookup
575
+ // Include the sender address to handle wallet switches between broadcast and confirmation
576
+ if (status_2 === events_1.TransactionStatus.BROADCASTED && transactionHash) {
577
+ var normalizedHash = transactionHash.toLowerCase();
578
+ var txDetails = __assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
579
+ this.pendingTransactions.set(normalizedHash, txDetails);
580
+ logger_1.logger.debug("WagmiEventHandler: Stored pending transaction for confirmation", {
581
+ transactionHash: normalizedHash,
582
+ });
583
+ // Clean up old pending transactions to prevent memory leaks (keep max 100)
584
+ // Remove oldest 50 entries when limit exceeded to handle high-throughput scenarios
585
+ if (this.pendingTransactions.size > 100) {
586
+ var keys = Array.from(this.pendingTransactions.keys());
587
+ for (var i = 0; i < 50 && i < keys.length; i++) {
588
+ this.pendingTransactions.delete(keys[i]);
589
+ }
590
+ }
591
+ }
592
+ this.formo.transaction(__assign(__assign(__assign(__assign(__assign(__assign({ status: status_2, chainId: chainId || 0, address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })),
593
+ // Spread function args as additional properties (only colliding keys are prefixed)
594
+ safeFunctionArgs);
377
595
  }
378
596
  catch (error) {
379
597
  logger_1.logger.error("WagmiEventHandler: Error handling transaction mutation:", error);
@@ -441,6 +659,8 @@ var WagmiEventHandler = /** @class */ (function () {
441
659
  }
442
660
  this.unsubscribers = [];
443
661
  this.processedMutations.clear();
662
+ this.processedQueries.clear();
663
+ this.pendingTransactions.clear();
444
664
  logger_1.logger.info("WagmiEventHandler: Cleanup complete");
445
665
  };
446
666
  return WagmiEventHandler;
@@ -89,11 +89,42 @@ export interface MutationCacheEvent {
89
89
  export interface MutationCache {
90
90
  subscribe(listener: (event: MutationCacheEvent) => void): () => void;
91
91
  }
92
+ /**
93
+ * React Query query state
94
+ */
95
+ export interface QueryState {
96
+ status: 'pending' | 'success' | 'error';
97
+ data?: any;
98
+ error?: Error | null;
99
+ fetchStatus: 'fetching' | 'paused' | 'idle';
100
+ }
101
+ /**
102
+ * React Query query object
103
+ */
104
+ export interface Query {
105
+ state: QueryState;
106
+ queryKey: readonly unknown[];
107
+ queryHash: string;
108
+ }
109
+ /**
110
+ * React Query query cache event
111
+ */
112
+ export interface QueryCacheEvent {
113
+ type: 'added' | 'removed' | 'updated';
114
+ query: Query;
115
+ }
116
+ /**
117
+ * React Query QueryCache interface
118
+ */
119
+ export interface QueryCache {
120
+ subscribe(listener: (event: QueryCacheEvent) => void): () => void;
121
+ }
92
122
  /**
93
123
  * React Query QueryClient interface
94
124
  */
95
125
  export interface QueryClient {
96
126
  getMutationCache(): MutationCache;
127
+ getQueryCache(): QueryCache;
97
128
  }
98
129
  /**
99
130
  * Unsubscribe function returned by subscriptions
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Utility functions for Wagmi event handling
3
+ *
4
+ * Provides ABI encoding utilities for extracting transaction data from
5
+ * writeContract mutations without requiring viem as a direct dependency.
6
+ */
7
+ /**
8
+ * Flatten a nested object into a flat object with underscore-separated keys.
9
+ * Only leaf values (primitives) are included; intermediate objects are not.
10
+ *
11
+ * Example:
12
+ * Input: { o: { x: "100", inner: { a: "42", b: "0xRecipient" } } }
13
+ * Output: { o_x: "100", o_inner_a: "42", o_inner_b: "0xRecipient" }
14
+ *
15
+ * @param obj - The object to flatten
16
+ * @param prefix - Optional prefix for keys (used in recursion)
17
+ * @returns A flat object with underscore-separated keys
18
+ */
19
+ export declare function flattenObject(obj: Record<string, unknown>, prefix?: string): Record<string, unknown>;
20
+ /**
21
+ * ABI function item type
22
+ */
23
+ export interface AbiItem {
24
+ type: string;
25
+ name?: string;
26
+ inputs?: AbiInput[];
27
+ outputs?: AbiOutput[];
28
+ stateMutability?: string;
29
+ }
30
+ export interface AbiInput {
31
+ name: string;
32
+ type: string;
33
+ indexed?: boolean;
34
+ components?: AbiInput[];
35
+ internalType?: string;
36
+ }
37
+ export interface AbiOutput {
38
+ name: string;
39
+ type: string;
40
+ components?: AbiOutput[];
41
+ internalType?: string;
42
+ }
43
+ /**
44
+ * Encode writeContract data using viem's encodeFunctionData
45
+ *
46
+ * @param abi - The contract ABI
47
+ * @param functionName - The function name to encode
48
+ * @param args - The function arguments
49
+ * @returns The encoded calldata or undefined if encoding fails
50
+ */
51
+ export declare function encodeWriteContractData(abi: AbiItem[], functionName: string, args?: unknown[]): string | undefined;
52
+ /**
53
+ * Extract function arguments as a name-value map from ABI and args array
54
+ *
55
+ * @param abi - The contract ABI
56
+ * @param functionName - The function name
57
+ * @param args - The function arguments array
58
+ * @returns A map of argument names to values, or undefined if extraction fails
59
+ */
60
+ export declare function extractFunctionArgs(abi: AbiItem[], functionName: string, args?: unknown[]): Record<string, unknown> | undefined;
61
+ /**
62
+ * Build safe function args with collision handling and struct flattening.
63
+ *
64
+ * This function:
65
+ * 1. Prefixes top-level args that collide with reserved fields (e.g., 'to' -> 'arg_to')
66
+ * 2. Flattens nested struct values for easier querying (e.g., order.maker -> order_maker)
67
+ * 3. Skips flattened keys that would collide with existing top-level args
68
+ *
69
+ * @param functionArgs - The extracted function arguments
70
+ * @param reservedFields - Set of reserved field names that need prefixing
71
+ * @returns Safe function args object, or undefined if input is undefined
72
+ */
73
+ export declare function buildSafeFunctionArgs(functionArgs: Record<string, unknown> | undefined, reservedFields: Set<string>): Record<string, unknown> | undefined;
74
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ /**
3
+ * Utility functions for Wagmi event handling
4
+ *
5
+ * Provides ABI encoding utilities for extracting transaction data from
6
+ * writeContract mutations without requiring viem as a direct dependency.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.flattenObject = flattenObject;
10
+ exports.encodeWriteContractData = encodeWriteContractData;
11
+ exports.extractFunctionArgs = extractFunctionArgs;
12
+ exports.buildSafeFunctionArgs = buildSafeFunctionArgs;
13
+ var logger_1 = require("../logger");
14
+ /**
15
+ * Flatten a nested object into a flat object with underscore-separated keys.
16
+ * Only leaf values (primitives) are included; intermediate objects are not.
17
+ *
18
+ * Example:
19
+ * Input: { o: { x: "100", inner: { a: "42", b: "0xRecipient" } } }
20
+ * Output: { o_x: "100", o_inner_a: "42", o_inner_b: "0xRecipient" }
21
+ *
22
+ * @param obj - The object to flatten
23
+ * @param prefix - Optional prefix for keys (used in recursion)
24
+ * @returns A flat object with underscore-separated keys
25
+ */
26
+ function flattenObject(obj, prefix) {
27
+ if (prefix === void 0) { prefix = ""; }
28
+ var result = {};
29
+ for (var _i = 0, _a = Object.entries(obj); _i < _a.length; _i++) {
30
+ var _b = _a[_i], key = _b[0], value = _b[1];
31
+ var newKey = prefix ? "".concat(prefix, "_").concat(key) : key;
32
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
33
+ // Recursively flatten nested objects
34
+ var nested = flattenObject(value, newKey);
35
+ Object.assign(result, nested);
36
+ }
37
+ else {
38
+ // Leaf value (primitive or array) - add directly
39
+ result[newKey] = value;
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ /**
45
+ * Recursively convert all BigInt values to strings for JSON serialization
46
+ * Handles nested objects, arrays, and deeply nested structures (e.g., Solidity structs)
47
+ *
48
+ * @param value - The value to convert
49
+ * @returns The value with all BigInt converted to strings
50
+ */
51
+ function convertBigIntToString(value) {
52
+ if (typeof value === "bigint") {
53
+ return value.toString();
54
+ }
55
+ if (Array.isArray(value)) {
56
+ return value.map(convertBigIntToString);
57
+ }
58
+ if (value !== null && typeof value === "object") {
59
+ var result = {};
60
+ for (var _i = 0, _a = Object.entries(value); _i < _a.length; _i++) {
61
+ var _b = _a[_i], key = _b[0], val = _b[1];
62
+ result[key] = convertBigIntToString(val);
63
+ }
64
+ return result;
65
+ }
66
+ return value;
67
+ }
68
+ // Cached viem module reference
69
+ var viemModule;
70
+ /**
71
+ * Try to load viem synchronously via require
72
+ * Returns null if viem is not available
73
+ */
74
+ function tryLoadViem() {
75
+ if (viemModule !== undefined) {
76
+ return viemModule;
77
+ }
78
+ try {
79
+ // Use require to load viem synchronously
80
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
81
+ var viem = require("viem");
82
+ if (viem === null || viem === void 0 ? void 0 : viem.encodeFunctionData) {
83
+ viemModule = {
84
+ encodeFunctionData: viem.encodeFunctionData,
85
+ };
86
+ return viemModule;
87
+ }
88
+ }
89
+ catch (_a) {
90
+ // viem is not available
91
+ }
92
+ viemModule = null;
93
+ return null;
94
+ }
95
+ /**
96
+ * Encode writeContract data using viem's encodeFunctionData
97
+ *
98
+ * @param abi - The contract ABI
99
+ * @param functionName - The function name to encode
100
+ * @param args - The function arguments
101
+ * @returns The encoded calldata or undefined if encoding fails
102
+ */
103
+ function encodeWriteContractData(abi, functionName, args) {
104
+ try {
105
+ var viem = tryLoadViem();
106
+ if (!viem) {
107
+ logger_1.logger.debug("WagmiEventHandler: viem not available, cannot encode function data");
108
+ return undefined;
109
+ }
110
+ var data = viem.encodeFunctionData({
111
+ abi: abi,
112
+ functionName: functionName,
113
+ args: args || [],
114
+ });
115
+ return data;
116
+ }
117
+ catch (error) {
118
+ logger_1.logger.warn("WagmiEventHandler: Failed to encode function data", error);
119
+ return undefined;
120
+ }
121
+ }
122
+ /**
123
+ * Extract function arguments as a name-value map from ABI and args array
124
+ *
125
+ * @param abi - The contract ABI
126
+ * @param functionName - The function name
127
+ * @param args - The function arguments array
128
+ * @returns A map of argument names to values, or undefined if extraction fails
129
+ */
130
+ function extractFunctionArgs(abi, functionName, args) {
131
+ if (!abi || !functionName || !args || !Array.isArray(args)) {
132
+ return undefined;
133
+ }
134
+ try {
135
+ // Find the function in the ABI
136
+ var abiItem = abi.find(function (item) { return item.type === "function" && item.name === functionName; });
137
+ if (!(abiItem === null || abiItem === void 0 ? void 0 : abiItem.inputs) || !Array.isArray(abiItem.inputs)) {
138
+ return undefined;
139
+ }
140
+ var result_1 = {};
141
+ abiItem.inputs.forEach(function (input, index) {
142
+ if (index < args.length) {
143
+ var argValue = args[index];
144
+ var argName = input.name || "arg".concat(index);
145
+ // Recursively convert BigInt to string for JSON serialization
146
+ // Handles: direct BigInt, arrays with BigInt, nested objects/structs with BigInt
147
+ result_1[argName] = convertBigIntToString(argValue);
148
+ }
149
+ });
150
+ return result_1;
151
+ }
152
+ catch (error) {
153
+ logger_1.logger.warn("WagmiEventHandler: Failed to extract function args", error);
154
+ return undefined;
155
+ }
156
+ }
157
+ /**
158
+ * Build safe function args with collision handling and struct flattening.
159
+ *
160
+ * This function:
161
+ * 1. Prefixes top-level args that collide with reserved fields (e.g., 'to' -> 'arg_to')
162
+ * 2. Flattens nested struct values for easier querying (e.g., order.maker -> order_maker)
163
+ * 3. Skips flattened keys that would collide with existing top-level args
164
+ *
165
+ * @param functionArgs - The extracted function arguments
166
+ * @param reservedFields - Set of reserved field names that need prefixing
167
+ * @returns Safe function args object, or undefined if input is undefined
168
+ */
169
+ function buildSafeFunctionArgs(functionArgs, reservedFields) {
170
+ if (!functionArgs) {
171
+ return undefined;
172
+ }
173
+ var result = {};
174
+ for (var _i = 0, _a = Object.entries(functionArgs); _i < _a.length; _i++) {
175
+ var _b = _a[_i], key = _b[0], val = _b[1];
176
+ var safeKey = reservedFields.has(key) ? "arg_".concat(key) : key;
177
+ result[safeKey] = val;
178
+ // If the value is a nested object (struct), flatten it
179
+ // Skip flattened keys that would overwrite existing top-level args
180
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
181
+ var flattened = flattenObject(val, safeKey);
182
+ for (var _c = 0, _d = Object.entries(flattened); _c < _d.length; _c++) {
183
+ var _e = _d[_c], flatKey = _e[0], flatVal = _e[1];
184
+ if (!(flatKey in result)) {
185
+ result[flatKey] = flatVal;
186
+ }
187
+ else {
188
+ logger_1.logger.debug("WagmiEventHandler: Skipping flattened key collision", {
189
+ flatKey: flatKey,
190
+ existingValue: result[flatKey],
191
+ });
192
+ }
193
+ }
194
+ }
195
+ }
196
+ return result;
197
+ }
198
+ //# sourceMappingURL=utils.js.map
@@ -139,7 +139,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
139
139
  * @param {(...args: unknown[]) => void} callback
140
140
  * @returns {Promise<void>}
141
141
  */
142
- transaction({ status, chainId, address, data, to, value, transactionHash, }: {
142
+ transaction({ status, chainId, address, data, to, value, transactionHash, function_name, function_args, }: {
143
143
  status: TransactionStatus;
144
144
  chainId: ChainID;
145
145
  address: Address;
@@ -147,6 +147,8 @@ export declare class FormoAnalytics implements IFormoAnalytics {
147
147
  to?: string;
148
148
  value?: string;
149
149
  transactionHash?: string;
150
+ function_name?: string;
151
+ function_args?: Record<string, unknown>;
150
152
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
151
153
  /**
152
154
  * Emits an identify event with current wallet address and provider info.
@@ -137,6 +137,7 @@ var FormoAnalytics = /** @class */ (function () {
137
137
  retryCount: options.retryCount,
138
138
  maxQueueSize: options.maxQueueSize,
139
139
  flushInterval: options.flushInterval,
140
+ errorHandler: options.errorHandler,
140
141
  }), options);
141
142
  // Check consent status on initialization
142
143
  if (this.hasOptedOutTracking()) {
@@ -422,10 +423,10 @@ var FormoAnalytics = /** @class */ (function () {
422
423
  */
423
424
  FormoAnalytics.prototype.transaction = function (_a, properties_1, context_1, callback_1) {
424
425
  return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
425
- var status = _b.status, chainId = _b.chainId, address = _b.address, data = _b.data, to = _b.to, value = _b.value, transactionHash = _b.transactionHash;
426
+ var status = _b.status, chainId = _b.chainId, address = _b.address, data = _b.data, to = _b.to, value = _b.value, transactionHash = _b.transactionHash, function_name = _b.function_name, function_args = _b.function_args;
426
427
  return __generator(this, function (_c) {
427
428
  switch (_c.label) {
428
- case 0: return [4 /*yield*/, this.trackEvent(EventType.TRANSACTION, __assign({ status: status, chainId: chainId, address: address, data: data, to: to, value: value }, (transactionHash && { transactionHash: transactionHash })), properties, context, callback)];
429
+ case 0: return [4 /*yield*/, this.trackEvent(EventType.TRANSACTION, __assign(__assign(__assign({ status: status, chainId: chainId, address: address, data: data, to: to, value: value }, (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), properties, context, callback)];
429
430
  case 1:
430
431
  _c.sent();
431
432
  return [2 /*return*/];