@formo/analytics 1.37.0 → 1.38.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 (40) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +64 -0
  2. package/dist/cjs/src/FormoAnalytics.js +155 -5
  3. package/dist/cjs/src/FormoAnalyticsProvider.js +1 -0
  4. package/dist/cjs/src/evm/EvmEventTracker.d.ts +38 -10
  5. package/dist/cjs/src/evm/EvmEventTracker.js +182 -0
  6. package/dist/cjs/src/evm/EvmProviderRegistry.js +26 -4
  7. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +28 -10
  8. package/dist/cjs/src/evm/EvmRequestTracker.js +199 -55
  9. package/dist/cjs/src/provider/detection.d.ts +30 -0
  10. package/dist/cjs/src/provider/detection.js +62 -0
  11. package/dist/cjs/src/provider/index.d.ts +1 -1
  12. package/dist/cjs/src/provider/index.js +3 -1
  13. package/dist/cjs/src/types/base.d.ts +23 -0
  14. package/dist/cjs/src/types/provider.d.ts +16 -0
  15. package/dist/cjs/src/types/provider.js +17 -1
  16. package/dist/cjs/src/version.d.ts +1 -1
  17. package/dist/cjs/src/version.js +1 -1
  18. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +44 -0
  19. package/dist/cjs/src/wagmi/WagmiEventHandler.js +306 -34
  20. package/dist/esm/src/FormoAnalytics.d.ts +64 -0
  21. package/dist/esm/src/FormoAnalytics.js +155 -5
  22. package/dist/esm/src/FormoAnalyticsProvider.js +1 -0
  23. package/dist/esm/src/evm/EvmEventTracker.d.ts +38 -10
  24. package/dist/esm/src/evm/EvmEventTracker.js +183 -1
  25. package/dist/esm/src/evm/EvmProviderRegistry.js +27 -5
  26. package/dist/esm/src/evm/EvmRequestTracker.d.ts +28 -10
  27. package/dist/esm/src/evm/EvmRequestTracker.js +198 -54
  28. package/dist/esm/src/provider/detection.d.ts +30 -0
  29. package/dist/esm/src/provider/detection.js +60 -0
  30. package/dist/esm/src/provider/index.d.ts +1 -1
  31. package/dist/esm/src/provider/index.js +1 -1
  32. package/dist/esm/src/types/base.d.ts +23 -0
  33. package/dist/esm/src/types/provider.d.ts +16 -0
  34. package/dist/esm/src/types/provider.js +16 -0
  35. package/dist/esm/src/version.d.ts +1 -1
  36. package/dist/esm/src/version.js +1 -1
  37. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +44 -0
  38. package/dist/esm/src/wagmi/WagmiEventHandler.js +306 -34
  39. package/dist/index.umd.min.js +1 -1
  40. package/package.json +2 -2
@@ -19,6 +19,17 @@ export interface IFormoAnalytics {
19
19
  page(category?: string, name?: string, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
20
20
  reset(): void;
21
21
  cleanup(): void;
22
+ /**
23
+ * Track a constructed (non-injected) EIP-1193 provider, such as a
24
+ * WalletConnect or Ledger provider the app built itself. Returns true when
25
+ * the provider is tracked, false when refused (wagmi mode, EVM disabled,
26
+ * or not a valid provider).
27
+ */
28
+ registerProvider(provider: EIP1193Provider, info?: {
29
+ name?: string;
30
+ rdns?: string;
31
+ icon?: `data:image/${string}`;
32
+ }): boolean;
22
33
  detect(params: {
23
34
  rdns: string;
24
35
  providerName: string;
@@ -171,6 +182,18 @@ export interface ReferralOptions {
171
182
  * Allows the SDK to hook into Wagmi v2 wallet events instead of wrapping EIP-1193 providers
172
183
  */
173
184
  export interface WagmiOptions {
185
+ /**
186
+ * OPT-IN: fall back to the EIP-1193 request wrapper for wallet calls the
187
+ * wagmi caches cannot see - imperative viem calls
188
+ * (walletClient.sendTransaction, .signMessage, .writeContract, raw
189
+ * request) that create no mutation. The active connector's provider gets
190
+ * the same wrapper the SDK's default mode uses. Off by default: wagmi
191
+ * mode's baseline contract is observing wagmi state and caches without
192
+ * touching the signing transport, and enabling this is an explicit,
193
+ * auditable decision. Hook-driven calls are never double-counted (a
194
+ * pending wagmi mutation stands the wrapper down).
195
+ */
196
+ eip1193Fallback?: boolean;
174
197
  /**
175
198
  * Wagmi config instance from createConfig()
176
199
  * The SDK will subscribe to this config's state changes to track wallet events
@@ -20,6 +20,22 @@ export type WrappedRequestFunction = (<T>(args: RequestArguments) => Promise<T |
20
20
  [WRAPPED_REQUEST_SYMBOL]?: boolean;
21
21
  };
22
22
  export declare const WRAPPED_REQUEST_REF_SYMBOL: unique symbol;
23
+ /**
24
+ * The SDK instance a provider's installed wrapper currently reports to.
25
+ *
26
+ * The wrapper survives an SDK rebuild (nothing restores `provider.request`),
27
+ * and its closure holds the instance that installed it - whose event queue
28
+ * is CLOSED after cleanup. Without this slot, a rebuilt instance saw
29
+ * "already wrapped", reported success, and every request-derived event
30
+ * silently died in the old instance's queue. The slot holds the LIST of
31
+ * instances that registered this provider, in registration order; the
32
+ * wrapper routes each call to the newest one still live. A list rather
33
+ * than a single ref for two reasons: with several live instances (multi
34
+ * write-key pages) a cleanup degrades to newest-live-wins instead of
35
+ * dead-instance-wins, and the list is attached at install time, so
36
+ * rebinding mutates it and works even on a provider frozen AFTER wrapping.
37
+ */
38
+ export declare const WRAPPED_REQUEST_OWNER_SYMBOL: unique symbol;
23
39
  export interface WrappedEIP1193Provider extends EIP1193Provider {
24
40
  [WRAPPED_REQUEST_REF_SYMBOL]?: WrappedRequestFunction;
25
41
  }
@@ -1,3 +1,19 @@
1
1
  export var WRAPPED_REQUEST_SYMBOL = Symbol("formoWrappedRequest");
2
2
  export var WRAPPED_REQUEST_REF_SYMBOL = Symbol("formoWrappedRequestRef");
3
+ /**
4
+ * The SDK instance a provider's installed wrapper currently reports to.
5
+ *
6
+ * The wrapper survives an SDK rebuild (nothing restores `provider.request`),
7
+ * and its closure holds the instance that installed it - whose event queue
8
+ * is CLOSED after cleanup. Without this slot, a rebuilt instance saw
9
+ * "already wrapped", reported success, and every request-derived event
10
+ * silently died in the old instance's queue. The slot holds the LIST of
11
+ * instances that registered this provider, in registration order; the
12
+ * wrapper routes each call to the newest one still live. A list rather
13
+ * than a single ref for two reasons: with several live instances (multi
14
+ * write-key pages) a cleanup degrades to newest-live-wins instead of
15
+ * dead-instance-wins, and the list is attached at install time, so
16
+ * rebinding mutates it and works even on a provider frozen AFTER wrapping.
17
+ */
18
+ export var WRAPPED_REQUEST_OWNER_SYMBOL = Symbol("formoWrappedRequestOwner");
3
19
  //# sourceMappingURL=provider.js.map
@@ -1,2 +1,2 @@
1
- export declare const version = "1.37.0";
1
+ export declare const version = "1.38.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // This file is auto-generated by scripts/update-version.js during npm version
2
2
  // Do not edit manually - it will be overwritten
3
- export var version = '1.37.0';
3
+ export var version = '1.38.0';
4
4
  //# sourceMappingURL=version.js.map
@@ -204,6 +204,7 @@ export declare class WagmiEventHandler {
204
204
  * already recorded the new address synchronously by the time this runs. The
205
205
  * `lastAddress` check below is what makes the two paths mutually exclusive.
206
206
  */
207
+ private handleActiveAddressChangeEntryKick;
207
208
  private handleActiveAddressChange;
208
209
  /**
209
210
  * Record and emit a chain move for the wallet already being tracked.
@@ -266,6 +267,26 @@ export declare class WagmiEventHandler {
266
267
  /**
267
268
  * Handle signature mutations (signMessage, signTypedData)
268
269
  */
270
+ /**
271
+ * Is a PENDING wagmi mutation already covering this wallet request?
272
+ *
273
+ * The hybrid-capture dedup. TanStack dispatches `pending` BEFORE the
274
+ * mutationFn issues the wallet call (verified against query-core), so a
275
+ * hook-driven request always finds its mutation here and the request
276
+ * wrapper stands down; an imperative viem call never does, and the
277
+ * wrapper captures it. Matching is by mutation type, refined with cheap
278
+ * parameter checks where the shapes allow, and errs toward NOT skipping:
279
+ * a duplicate is visible and diagnosable, a silent loss is neither.
280
+ */
281
+ hasMatchingPendingMutation(method: string, params: unknown[]): boolean;
282
+ /**
283
+ * Wallet attribution for mutation- and query-derived events.
284
+ *
285
+ * Mid-session events (signatures, transactions) fire after the peer
286
+ * lookup has resolved, so a WalletConnect connector names its actual
287
+ * signer here - the first observable consumer of the peer cache.
288
+ */
289
+ private mutationAttribution;
269
290
  private handleSignatureMutation;
270
291
  /**
271
292
  * Handle transaction mutations (sendTransaction, writeContract)
@@ -320,6 +341,29 @@ export declare class WagmiEventHandler {
320
341
  * Get the connector name from Wagmi state
321
342
  */
322
343
  private getConnectorName;
344
+ /**
345
+ * Start resolving the wallet behind a WalletConnect connection.
346
+ *
347
+ * Fire-and-forget on purpose: emission paths are synchronous by design
348
+ * and must never wait (see the connect marker comment). Called at the
349
+ * START of the status/address flows rather than only at read time - the
350
+ * lookup is one microtask for an initialised connector, and the emission
351
+ * sits behind several awaits, so kicking early usually means even the
352
+ * FIRST connect names the real wallet. When the race is lost the event
353
+ * honestly says "WalletConnect" and every later event names the peer.
354
+ */
355
+ /**
356
+ * Install the request wrapper on the active connector's provider.
357
+ *
358
+ * This is what lets wagmi mode capture IMPERATIVE viem calls
359
+ * (walletClient.sendTransaction / .signMessage / .writeContract / raw
360
+ * request), which create no mutation and were silently lost. Hook-driven
361
+ * calls stay owned by the mutation handlers via the pending-mutation
362
+ * dedup. Fire-and-forget per connection; a provider that cannot be
363
+ * produced simply keeps mutation-only capture.
364
+ */
365
+ private wrapActiveConnectorProvider;
366
+ private kickWalletConnectPeerLookup;
323
367
  /**
324
368
  * Clean up all subscriptions
325
369
  */
@@ -54,6 +54,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
54
54
  };
55
55
  import { SignatureStatus, TransactionStatus } from "../types/events";
56
56
  import { logger } from "../logger";
57
+ import { readWalletConnectPeer, isUserRejectionError } from "../provider";
57
58
  import { readBatchId, readBatchStatusCode, readBatchChainId, batchCallOutcome, batchReceiptForCall, } from "../evm/batch";
58
59
  import { encodeWriteContractData, concatCalldataWithSuffix, extractFunctionArgs, buildSafeFunctionArgs, } from "./utils";
59
60
  /**
@@ -197,25 +198,48 @@ var ownerKey = function (writeKey, config) {
197
198
  }
198
199
  return "".concat(writeKey, ":").concat(id);
199
200
  };
201
+ // User-rejection detection is shared with the EIP-1193 path (4001, the
202
+ // WalletConnect 5000-family, and viem's typed error): see
203
+ // provider/detection.ts for the dialects and why 4001 alone missed every
204
+ // WalletConnect rejection.
205
+ var isUserRejection = isUserRejectionError;
200
206
  /**
201
- * Was this mutation error the user dismissing the wallet prompt?
207
+ * Real wallet names behind WalletConnect connectors, resolved lazily.
202
208
  *
203
- * Matches the EIP-1193 path's rule (code 4001), but a wagmi mutation error
204
- * arrives wrapped: viem nests the RPC error under `cause`, sometimes twice.
205
- * Walk the chain rather than trusting the top level, and accept viem's
206
- * `UserRejectedRequestError` by name for wallets that map the rejection to a
207
- * typed error without preserving the numeric code.
209
+ * WalletConnect is a transport: the signing wallet (Ledger Live, MetaMask
210
+ * Mobile, Safe, ...) names itself in the session's peer metadata, reachable
211
+ * only through the connector's async `getProvider()`. Connect emission is
212
+ * deliberately synchronous (see the marker comments below), so the lookup
213
+ * can never be awaited in line - it is kicked fire-and-forget the first
214
+ * time a WalletConnect connector is seen, and every event after resolution
215
+ * carries the real wallet's name. The first connect may still say
216
+ * "WalletConnect"; that is the honest state at that instant.
217
+ *
218
+ * HONEST STATUS: with the new-connection invalidation below, no event the
219
+ * wagmi path emits TODAY observably carries the resolved name - connects
220
+ * fire before resolution and rebuilds suppress re-emission. The cache
221
+ * exists for the attribution work (wallet names on signature and
222
+ * transaction events), which fires mid-session, after resolution.
223
+ *
224
+ * Names are keyed by the CONNECTOR (stable across a page's sessions) so
225
+ * they actually serve reads; lookups are guarded per CONNECTION (wagmi
226
+ * replaces the connection object per session), so every new session
227
+ * re-resolves and OVERWRITES the name. A reconnect through the same
228
+ * connector to a different wallet can therefore mislabel at most the one
229
+ * event that fires between the new session's start and its resolution -
230
+ * one microtask for an initialised connector - and self-corrects. The
231
+ * alternative, keying names by connection, was tried and kept the name
232
+ * from ever surfacing: the only reader that fires per session runs before
233
+ * any lookup can resolve. WeakMaps, so nothing outlives its object.
208
234
  */
209
- function isUserRejection(error) {
210
- var cursor = error;
211
- for (var depth = 0; cursor && depth < 5; depth++) {
212
- if (cursor.code === 4001 || cursor.name === "UserRejectedRequestError") {
213
- return true;
214
- }
215
- cursor = cursor.cause;
216
- }
217
- return false;
218
- }
235
+ var walletConnectPeerNames = new WeakMap();
236
+ var walletConnectPeerLookups = new WeakSet();
237
+ /** The connection whose lookup may write the connector's name: always the
238
+ * newest kicked one, so a slow resolution from a PREVIOUS session cannot
239
+ * land after the current session's and overwrite it. */
240
+ var walletConnectPeerLatest = new WeakMap();
241
+ /** Connections whose provider already has the hybrid-capture wrapper. */
242
+ var wagmiWrappedConnections = new WeakSet();
219
243
  /**
220
244
  * Pending transactions, shared per destination rather than per handler.
221
245
  *
@@ -611,6 +635,17 @@ var WagmiEventHandler = /** @class */ (function () {
611
635
  */
612
636
  WagmiEventHandler.prototype.seedFromCurrentState = function () {
613
637
  var _a, _b;
638
+ // Fire-and-forget here: the seed is synchronous by design, so its own
639
+ // connect may carry the generic name; the status flow awaits instead.
640
+ // A throwing store must not break construction.
641
+ try {
642
+ var snapshot = this.getState();
643
+ this.kickWalletConnectPeerLookup(snapshot);
644
+ this.wrapActiveConnectorProvider(snapshot);
645
+ }
646
+ catch (_c) {
647
+ /* the seed continues; names fall back to the connector's own */
648
+ }
614
649
  try {
615
650
  var state = this.getState();
616
651
  if (state.status !== "connected") {
@@ -863,7 +898,7 @@ var WagmiEventHandler = /** @class */ (function () {
863
898
  */
864
899
  WagmiEventHandler.prototype.handleStatusChange = function (status, prevStatus) {
865
900
  return __awaiter(this, void 0, void 0, function () {
866
- var state, address, chainId, disconnectedAddress, disconnectedChainId, walletKey, connection, connectorName, error_1, error_2;
901
+ var snapshot, state, address, chainId, disconnectedAddress, disconnectedChainId, walletKey, connection, connectorName, error_1, error_2;
867
902
  var _a, _b, _c, _d;
868
903
  return __generator(this, function (_e) {
869
904
  switch (_e.label) {
@@ -884,6 +919,18 @@ var WagmiEventHandler = /** @class */ (function () {
884
919
  return [2 /*return*/];
885
920
  }
886
921
  this.trackingState.isProcessing = true;
922
+ // Start resolving the wallet behind a WalletConnect session. NEVER
923
+ // awaited: every path from a store signal to its emission is
924
+ // synchronous by design, and an await here reorders transitions (it
925
+ // demonstrably drops connects under rapid connect/disconnect cycles).
926
+ try {
927
+ snapshot = this.getState();
928
+ this.kickWalletConnectPeerLookup(snapshot);
929
+ this.wrapActiveConnectorProvider(snapshot);
930
+ }
931
+ catch (_f) {
932
+ // A throwing store must not break the status flow.
933
+ }
887
934
  // A status change outranks any connection transition still in flight. A
888
935
  // full disconnect advances no ticket of its own, so without this an older
889
936
  // fallback continuation could resume and announce a wallet wagmi no longer
@@ -1151,6 +1198,16 @@ var WagmiEventHandler = /** @class */ (function () {
1151
1198
  * already recorded the new address synchronously by the time this runs. The
1152
1199
  * `lastAddress` check below is what makes the two paths mutually exclusive.
1153
1200
  */
1201
+ WagmiEventHandler.prototype.handleActiveAddressChangeEntryKick = function () {
1202
+ // An account switch replaces the connection object; kick a lookup for
1203
+ // the new one so events that follow can name the wallet.
1204
+ try {
1205
+ this.kickWalletConnectPeerLookup(this.getState());
1206
+ }
1207
+ catch (_a) {
1208
+ /* a throwing store must not break the flow */
1209
+ }
1210
+ };
1154
1211
  WagmiEventHandler.prototype.handleActiveAddressChange = function (address, prevAddress) {
1155
1212
  return __awaiter(this, void 0, void 0, function () {
1156
1213
  var state, trackedConnectionId, trackedConnectionGone, connectionChanged, liveChain, generation, chainId, stillConnected, goneChainId, error_3, liveNow, stillCurrent, walletKey, connection, alreadyAnnounced, connectorName, error_4;
@@ -1158,6 +1215,7 @@ var WagmiEventHandler = /** @class */ (function () {
1158
1215
  return __generator(this, function (_g) {
1159
1216
  switch (_g.label) {
1160
1217
  case 0:
1218
+ this.handleActiveAddressChangeEntryKick();
1161
1219
  state = this.getState();
1162
1220
  if (state.status !== "connected")
1163
1221
  return [2 /*return*/];
@@ -1628,11 +1686,9 @@ var WagmiEventHandler = /** @class */ (function () {
1628
1686
  return;
1629
1687
  _this.formo.transaction(__assign(__assign(__assign(__assign({ status: outcome, chainId: settledChainId_1 || 0, address: pending.address }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), ((receipt === null || receipt === void 0 ? void 0 : receipt.transactionHash)
1630
1688
  ? { transactionHash: receipt.transactionHash }
1631
- : {})), {
1632
- batch_size: pending.calls.length,
1633
- batch_index: index,
1634
- batch_id: batchId,
1635
- });
1689
+ : {})), __assign({ batch_size: pending.calls.length, batch_index: index, batch_id: batchId }, (pending.providerName
1690
+ ? { providerName: pending.providerName }
1691
+ : {})));
1636
1692
  });
1637
1693
  // Settled; a later refetch of the same query must not re-emit.
1638
1694
  this.pendingBatches.delete(batchId);
@@ -1715,9 +1771,9 @@ var WagmiEventHandler = /** @class */ (function () {
1715
1771
  chainId: chainId,
1716
1772
  blockNumber: (_b = receipt === null || receipt === void 0 ? void 0 : receipt.blockNumber) === null || _b === void 0 ? void 0 : _b.toString(),
1717
1773
  });
1718
- 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 })),
1719
- // Spread function args as additional properties (only colliding keys are prefixed)
1720
- pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.safeFunctionArgs);
1774
+ 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 })), __assign(__assign({}, ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.providerName)
1775
+ ? { providerName: pendingTx.providerName }
1776
+ : this.mutationAttribution())), pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.safeFunctionArgs));
1721
1777
  // Clean up the pending transaction after confirmation
1722
1778
  this.pendingTransactions.delete(normalizedHash);
1723
1779
  }
@@ -1783,6 +1839,76 @@ var WagmiEventHandler = /** @class */ (function () {
1783
1839
  /**
1784
1840
  * Handle signature mutations (signMessage, signTypedData)
1785
1841
  */
1842
+ /**
1843
+ * Is a PENDING wagmi mutation already covering this wallet request?
1844
+ *
1845
+ * The hybrid-capture dedup. TanStack dispatches `pending` BEFORE the
1846
+ * mutationFn issues the wallet call (verified against query-core), so a
1847
+ * hook-driven request always finds its mutation here and the request
1848
+ * wrapper stands down; an imperative viem call never does, and the
1849
+ * wrapper captures it. Matching is by mutation type, refined with cheap
1850
+ * parameter checks where the shapes allow, and errs toward NOT skipping:
1851
+ * a duplicate is visible and diagnosable, a silent loss is neither.
1852
+ */
1853
+ WagmiEventHandler.prototype.hasMatchingPendingMutation = function (method, params) {
1854
+ var _a, _b, _c, _d, _e, _f;
1855
+ try {
1856
+ var cache = (_a = this.queryClient) === null || _a === void 0 ? void 0 : _a.getMutationCache();
1857
+ var mutations = (_b = cache === null || cache === void 0 ? void 0 : cache.getAll) === null || _b === void 0 ? void 0 : _b.call(cache);
1858
+ if (!Array.isArray(mutations))
1859
+ return false;
1860
+ var wanted = {
1861
+ personal_sign: ["signMessage"],
1862
+ eth_signTypedData_v4: ["signTypedData"],
1863
+ eth_sendTransaction: ["sendTransaction", "writeContract"],
1864
+ wallet_sendCalls: ["sendCalls"],
1865
+ };
1866
+ var types = wanted[method];
1867
+ if (!types)
1868
+ return false;
1869
+ for (var _i = 0, mutations_1 = mutations; _i < mutations_1.length; _i++) {
1870
+ var mutation = mutations_1[_i];
1871
+ if (((_c = mutation === null || mutation === void 0 ? void 0 : mutation.state) === null || _c === void 0 ? void 0 : _c.status) !== "pending")
1872
+ continue;
1873
+ var key = (_e = (_d = mutation === null || mutation === void 0 ? void 0 : mutation.options) === null || _d === void 0 ? void 0 : _d.mutationKey) === null || _e === void 0 ? void 0 : _e[0];
1874
+ if (typeof key !== "string" || !types.includes(key))
1875
+ continue;
1876
+ // Cheap refinements. On mismatch keep scanning; on no basis to
1877
+ // compare, treat the type-level match as decisive.
1878
+ var variables = (_f = mutation.state) === null || _f === void 0 ? void 0 : _f.variables;
1879
+ if (key === "sendTransaction" && variables) {
1880
+ var req = (Array.isArray(params) ? params[0] : undefined);
1881
+ var mutTo = variables.to;
1882
+ if (typeof (req === null || req === void 0 ? void 0 : req.to) === "string" &&
1883
+ typeof mutTo === "string" &&
1884
+ req.to.toLowerCase() !== mutTo.toLowerCase()) {
1885
+ continue;
1886
+ }
1887
+ }
1888
+ return true;
1889
+ }
1890
+ return false;
1891
+ }
1892
+ catch (_g) {
1893
+ return false;
1894
+ }
1895
+ };
1896
+ /**
1897
+ * Wallet attribution for mutation- and query-derived events.
1898
+ *
1899
+ * Mid-session events (signatures, transactions) fire after the peer
1900
+ * lookup has resolved, so a WalletConnect connector names its actual
1901
+ * signer here - the first observable consumer of the peer cache.
1902
+ */
1903
+ WagmiEventHandler.prototype.mutationAttribution = function () {
1904
+ try {
1905
+ var name_1 = this.getConnectorName(this.getState());
1906
+ return name_1 ? { providerName: name_1 } : undefined;
1907
+ }
1908
+ catch (_a) {
1909
+ return undefined;
1910
+ }
1911
+ };
1786
1912
  WagmiEventHandler.prototype.handleSignatureMutation = function (mutationType, mutation) {
1787
1913
  var _a, _b, _c, _d;
1788
1914
  if (!this.formo.isAutocaptureEnabled("signature")) {
@@ -1843,7 +1969,7 @@ var WagmiEventHandler = /** @class */ (function () {
1843
1969
  chainId: chainId,
1844
1970
  address: address,
1845
1971
  message: message,
1846
- });
1972
+ }, this.mutationAttribution());
1847
1973
  }
1848
1974
  catch (error) {
1849
1975
  logger.error("WagmiEventHandler: Error handling signature mutation:", error);
@@ -1853,7 +1979,7 @@ var WagmiEventHandler = /** @class */ (function () {
1853
1979
  * Handle transaction mutations (sendTransaction, writeContract)
1854
1980
  */
1855
1981
  WagmiEventHandler.prototype.handleTransactionMutation = function (mutationType, mutation) {
1856
- var _a;
1982
+ var _a, _b;
1857
1983
  if (!this.formo.isAutocaptureEnabled("transaction")) {
1858
1984
  return;
1859
1985
  }
@@ -1944,7 +2070,7 @@ var WagmiEventHandler = /** @class */ (function () {
1944
2070
  // Include the sender address to handle wallet switches between broadcast and confirmation
1945
2071
  if (status_2 === TransactionStatus.BROADCASTED && transactionHash) {
1946
2072
  var normalizedHash = transactionHash.toLowerCase();
1947
- var txDetails = __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined }), (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
2073
+ var txDetails = __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, ((_b = this.mutationAttribution()) !== null && _b !== void 0 ? _b : {})), (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined }), (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
1948
2074
  this.pendingTransactions.set(normalizedHash, txDetails);
1949
2075
  logger.debug("WagmiEventHandler: Stored pending transaction for confirmation", {
1950
2076
  transactionHash: normalizedHash,
@@ -1958,9 +2084,7 @@ var WagmiEventHandler = /** @class */ (function () {
1958
2084
  }
1959
2085
  }
1960
2086
  }
1961
- 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 })),
1962
- // Spread function args as additional properties (only colliding keys are prefixed)
1963
- safeFunctionArgs);
2087
+ 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 })), __assign(__assign({}, this.mutationAttribution()), safeFunctionArgs));
1964
2088
  }
1965
2089
  catch (error) {
1966
2090
  logger.error("WagmiEventHandler: Error handling transaction mutation:", error);
@@ -2009,9 +2133,10 @@ var WagmiEventHandler = /** @class */ (function () {
2009
2133
  value: (call === null || call === void 0 ? void 0 : call.value) !== undefined ? String(call.value) : undefined,
2010
2134
  data: call === null || call === void 0 ? void 0 : call.data,
2011
2135
  }); });
2136
+ var attribution_1 = this.mutationAttribution();
2012
2137
  var emitAll = function (status, extra) {
2013
2138
  calls_1.forEach(function (call, index) {
2014
- _this.formo.transaction(__assign(__assign(__assign({ status: status, chainId: chainId || 0, address: userAddress }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), __assign({ batch_size: calls_1.length, batch_index: index }, extra));
2139
+ _this.formo.transaction(__assign(__assign(__assign({ status: status, chainId: chainId || 0, address: userAddress }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), __assign(__assign({ batch_size: calls_1.length, batch_index: index }, attribution_1), extra));
2015
2140
  });
2016
2141
  };
2017
2142
  if (state.status === "pending") {
@@ -2025,7 +2150,7 @@ var WagmiEventHandler = /** @class */ (function () {
2025
2150
  logger.debug("WagmiEventHandler: sendCalls broadcast", { batchId: batchId });
2026
2151
  emitAll(TransactionStatus.BROADCASTED, batchId ? { batch_id: batchId } : undefined);
2027
2152
  if (batchId) {
2028
- this.pendingBatches.set(batchId, __assign(__assign({ address: userAddress }, (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined, calls: calls_1 }));
2153
+ this.pendingBatches.set(batchId, __assign(__assign(__assign({ address: userAddress }, (attribution_1 !== null && attribution_1 !== void 0 ? attribution_1 : {})), (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined, calls: calls_1 }));
2029
2154
  // Same bound as pendingTransactions, same reason.
2030
2155
  if (this.pendingBatches.size > 100) {
2031
2156
  var keys = Array.from(this.pendingBatches.keys());
@@ -2129,7 +2254,154 @@ var WagmiEventHandler = /** @class */ (function () {
2129
2254
  return undefined;
2130
2255
  }
2131
2256
  var connection = state.connections.get(state.current);
2132
- return connection === null || connection === void 0 ? void 0 : connection.connector.name;
2257
+ var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2258
+ if (!connection || !connector) {
2259
+ return undefined;
2260
+ }
2261
+ var cached = walletConnectPeerNames.get(connector);
2262
+ if (cached) {
2263
+ return cached;
2264
+ }
2265
+ // Backstop kick; the flow entry points kick earlier so the lookup has
2266
+ // usually resolved by the time an emission reads the name.
2267
+ this.kickWalletConnectPeerLookup(state);
2268
+ return connector.name;
2269
+ };
2270
+ /**
2271
+ * Start resolving the wallet behind a WalletConnect connection.
2272
+ *
2273
+ * Fire-and-forget on purpose: emission paths are synchronous by design
2274
+ * and must never wait (see the connect marker comment). Called at the
2275
+ * START of the status/address flows rather than only at read time - the
2276
+ * lookup is one microtask for an initialised connector, and the emission
2277
+ * sits behind several awaits, so kicking early usually means even the
2278
+ * FIRST connect names the real wallet. When the race is lost the event
2279
+ * honestly says "WalletConnect" and every later event names the peer.
2280
+ */
2281
+ /**
2282
+ * Install the request wrapper on the active connector's provider.
2283
+ *
2284
+ * This is what lets wagmi mode capture IMPERATIVE viem calls
2285
+ * (walletClient.sendTransaction / .signMessage / .writeContract / raw
2286
+ * request), which create no mutation and were silently lost. Hook-driven
2287
+ * calls stay owned by the mutation handlers via the pending-mutation
2288
+ * dedup. Fire-and-forget per connection; a provider that cannot be
2289
+ * produced simply keeps mutation-only capture.
2290
+ */
2291
+ WagmiEventHandler.prototype.wrapActiveConnectorProvider = function (state) {
2292
+ var _this = this;
2293
+ var _a, _b;
2294
+ // OPT-IN only. Wagmi mode's baseline never touches the signing
2295
+ // transport; instrumenting the provider is an explicit integrator
2296
+ // decision (options.wagmi.eip1193Fallback), made auditable in
2297
+ // configuration rather than implied by a version bump.
2298
+ var optedIn = ((_b = (_a = this.formo.options) === null || _a === void 0 ? void 0 : _a.wagmi) === null || _b === void 0 ? void 0 : _b.eip1193Fallback) === true;
2299
+ if (!optedIn) {
2300
+ return;
2301
+ }
2302
+ var connection = state.current
2303
+ ? state.connections.get(state.current)
2304
+ : undefined;
2305
+ var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2306
+ if (!connection ||
2307
+ typeof (connector === null || connector === void 0 ? void 0 : connector.getProvider) !== "function" ||
2308
+ wagmiWrappedConnections.has(connection)) {
2309
+ return;
2310
+ }
2311
+ wagmiWrappedConnections.add(connection);
2312
+ connector
2313
+ .getProvider()
2314
+ .then(function (provider) {
2315
+ var _a, _b;
2316
+ (_b = (_a = _this.formo)._wrapWagmiProvider) === null || _b === void 0 ? void 0 : _b.call(_a, provider);
2317
+ })
2318
+ .catch(function () {
2319
+ // Mutation-only capture remains; retry on the next connection.
2320
+ wagmiWrappedConnections.delete(connection);
2321
+ });
2322
+ };
2323
+ WagmiEventHandler.prototype.kickWalletConnectPeerLookup = function (state) {
2324
+ var _a, _b;
2325
+ var connection = state.current
2326
+ ? state.connections.get(state.current)
2327
+ : undefined;
2328
+ var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2329
+ if (!connection ||
2330
+ !connector ||
2331
+ typeof connector.name !== "string" ||
2332
+ !/walletconnect/i.test(connector.name) ||
2333
+ typeof connector.getProvider !== "function" ||
2334
+ walletConnectPeerLookups.has(connection)) {
2335
+ return;
2336
+ }
2337
+ walletConnectPeerLookups.add(connection);
2338
+ // A NEW connection invalidates the cached name SYNCHRONOUSLY. The
2339
+ // connect flow reads the cache in the same tick it kicks the lookup,
2340
+ // so retaining the previous session's name here deterministically
2341
+ // attributed a reconnect-to-a-different-wallet to the OLD wallet.
2342
+ // Wrong is worse than generic: the new session's connect now says
2343
+ // "WalletConnect" and the resolved peer serves the session's LATER
2344
+ // events (signatures, transactions - the attribution work) instead.
2345
+ // A rebuild over the SAME connection does not re-kick (guard above),
2346
+ // so it keeps its already-proven name.
2347
+ if (walletConnectPeerLatest.get(connector) !== connection) {
2348
+ walletConnectPeerNames.delete(connector);
2349
+ }
2350
+ walletConnectPeerLatest.set(connector, connection);
2351
+ var settled = false;
2352
+ // A cached name from a PREVIOUS session is unproven for this one. It
2353
+ // keeps serving only until this session's lookup settles or the grace
2354
+ // timer fires - whichever ends the uncertainty first - so a hung
2355
+ // lookup cannot leave the old wallet's name attached indefinitely.
2356
+ var staleTimer = setTimeout(function () {
2357
+ if (!settled &&
2358
+ walletConnectPeerLatest.get(connector) === connection) {
2359
+ walletConnectPeerNames.delete(connector);
2360
+ }
2361
+ }, 3000);
2362
+ (_b = (_a = staleTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
2363
+ connector
2364
+ .getProvider()
2365
+ .then(function (provider) {
2366
+ settled = true;
2367
+ clearTimeout(staleTimer);
2368
+ // Only the NEWEST session's lookup may write. A previous session's
2369
+ // slow resolution landing late would otherwise overwrite the
2370
+ // current wallet's name with the old one.
2371
+ if (walletConnectPeerLatest.get(connector) !== connection) {
2372
+ return;
2373
+ }
2374
+ var peer = readWalletConnectPeer(provider);
2375
+ if (peer === null || peer === void 0 ? void 0 : peer.name) {
2376
+ walletConnectPeerNames.set(connector, peer.name);
2377
+ logger.debug("WagmiEventHandler: WalletConnect peer resolved", {
2378
+ peer: peer.name,
2379
+ });
2380
+ }
2381
+ else {
2382
+ // Resolved WITHOUT peer metadata: the previous wallet's name is
2383
+ // disproven for this session, not merely unproven. Drop it, and
2384
+ // let a later event retry the lookup - the session may simply
2385
+ // not have populated its peer yet.
2386
+ walletConnectPeerNames.delete(connector);
2387
+ walletConnectPeerLookups.delete(connection);
2388
+ }
2389
+ })
2390
+ .catch(function () {
2391
+ settled = true;
2392
+ clearTimeout(staleTimer);
2393
+ // The new session could not be inspected, so the PREVIOUS wallet's
2394
+ // name must not keep serving: drop it and fall back to the
2395
+ // connector's own name until a later session resolves. Guarded so
2396
+ // an old session's late failure cannot clear a newer resolution.
2397
+ if (walletConnectPeerLatest.get(connector) === connection) {
2398
+ walletConnectPeerNames.delete(connector);
2399
+ }
2400
+ // A failed lookup must not permanently disqualify the connection:
2401
+ // the connector may just have been initialising. A later event
2402
+ // retries.
2403
+ walletConnectPeerLookups.delete(connection);
2404
+ });
2133
2405
  };
2134
2406
  /**
2135
2407
  * Clean up all subscriptions