@formo/analytics 1.38.0 → 1.38.2

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 (34) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +15 -4
  2. package/dist/cjs/src/FormoAnalytics.js +37 -6
  3. package/dist/cjs/src/FormoAnalyticsProvider.d.ts +8 -0
  4. package/dist/cjs/src/FormoAnalyticsProvider.js +41 -32
  5. package/dist/cjs/src/evm/EvmEventTracker.d.ts +100 -9
  6. package/dist/cjs/src/evm/EvmEventTracker.js +357 -19
  7. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +34 -2
  8. package/dist/cjs/src/evm/EvmProviderRegistry.js +61 -4
  9. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +21 -4
  10. package/dist/cjs/src/evm/EvmRequestTracker.js +54 -28
  11. package/dist/cjs/src/version.d.ts +1 -1
  12. package/dist/cjs/src/version.js +1 -1
  13. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +67 -1
  14. package/dist/cjs/src/wagmi/WagmiEventHandler.js +286 -39
  15. package/dist/cjs/src/wagmi/types.d.ts +6 -0
  16. package/dist/cjs/src/wagmi/utils.js +9 -1
  17. package/dist/esm/src/FormoAnalytics.d.ts +15 -4
  18. package/dist/esm/src/FormoAnalytics.js +37 -6
  19. package/dist/esm/src/FormoAnalyticsProvider.d.ts +8 -0
  20. package/dist/esm/src/FormoAnalyticsProvider.js +39 -31
  21. package/dist/esm/src/evm/EvmEventTracker.d.ts +100 -9
  22. package/dist/esm/src/evm/EvmEventTracker.js +357 -19
  23. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +34 -2
  24. package/dist/esm/src/evm/EvmProviderRegistry.js +61 -4
  25. package/dist/esm/src/evm/EvmRequestTracker.d.ts +21 -4
  26. package/dist/esm/src/evm/EvmRequestTracker.js +54 -28
  27. package/dist/esm/src/version.d.ts +1 -1
  28. package/dist/esm/src/version.js +1 -1
  29. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +67 -1
  30. package/dist/esm/src/wagmi/WagmiEventHandler.js +286 -39
  31. package/dist/esm/src/wagmi/types.d.ts +6 -0
  32. package/dist/esm/src/wagmi/utils.js +9 -1
  33. package/dist/index.umd.min.js +1 -1
  34. package/package.json +2 -2
@@ -71,6 +71,16 @@ var RESERVED_FIELDS = new Set([
71
71
  "transactionHash",
72
72
  "function_name",
73
73
  "function_args",
74
+ // Wallet attribution. Function args are spread AFTER the attribution
75
+ // properties, so an ABI input named `providerName` would silently
76
+ // relabel the wallet on every broadcast and receipt event. `rdns` is
77
+ // reserved with it even though THIS path (wagmi mutation attribution)
78
+ // emits only the name: elsewhere in the SDK - the EIP-1193 request path
79
+ // and connect events - `rdns` is the wallet-identity key, so an ABI
80
+ // input by that name would land in the same warehouse column as real
81
+ // wallet identities.
82
+ "providerName",
83
+ "rdns",
74
84
  ]);
75
85
  /**
76
86
  * Connections already announced to a destination during this page load.
@@ -238,8 +248,6 @@ var walletConnectPeerLookups = new WeakSet();
238
248
  * newest kicked one, so a slow resolution from a PREVIOUS session cannot
239
249
  * land after the current session's and overwrite it. */
240
250
  var walletConnectPeerLatest = new WeakMap();
241
- /** Connections whose provider already has the hybrid-capture wrapper. */
242
- var wagmiWrappedConnections = new WeakSet();
243
251
  /**
244
252
  * Pending transactions, shared per destination rather than per handler.
245
253
  *
@@ -424,6 +432,57 @@ var WagmiEventHandler = /** @class */ (function () {
424
432
  * Key format: `${queryHash}:${status}`
425
433
  */
426
434
  this.processedQueries = new Set();
435
+ /**
436
+ * Start resolving the wallet behind a WalletConnect connection.
437
+ *
438
+ * Fire-and-forget on purpose: emission paths are synchronous by design
439
+ * and must never wait (see the connect marker comment). Called at the
440
+ * START of the status/address flows rather than only at read time - the
441
+ * lookup is one microtask for an initialised connector, and the emission
442
+ * sits behind several awaits, so kicking early usually means even the
443
+ * FIRST connect names the real wallet. When the race is lost the event
444
+ * honestly says "WalletConnect" and every later event names the peer.
445
+ */
446
+ /**
447
+ * Install the request wrapper on the active connector's provider.
448
+ *
449
+ * This is what lets wagmi mode capture IMPERATIVE viem calls
450
+ * (walletClient.sendTransaction / .signMessage / .writeContract / raw
451
+ * request), which create no mutation and were silently lost. Hook-driven
452
+ * calls stay owned by the mutation handlers via the pending-mutation
453
+ * dedup. Fire-and-forget per connection; a provider that cannot be
454
+ * produced simply keeps mutation-only capture.
455
+ */
456
+ /**
457
+ * Connections THIS instance has wrapped. Deliberately per-instance, not
458
+ * module-level: a module-level guard let a rebuilt SDK instance skip
459
+ * re-wrapping, so ownership stayed with the torn-down instance and every
460
+ * capture died in its closed queue - and it also stopped a second
461
+ * write-key instance from ever registering. Re-wrapping is safe and
462
+ * cheap: the request tracker rebinds ownership of an intact wrapper.
463
+ */
464
+ /**
465
+ * Latest wrap attempt per connector. Every kick re-resolves
466
+ * getProvider() - a connector can hand out a REPLACEMENT provider after
467
+ * a reconnect, so a once-only guard would leave the new session's
468
+ * provider unwrapped forever. Re-wrapping is idempotent: the request
469
+ * tracker rebinds ownership of an intact wrapper. The epoch lets a slow
470
+ * resolution from a superseded kick (an earlier session, an earlier
471
+ * chain) be discarded instead of overwriting fresher state.
472
+ */
473
+ this.wrapEpochs = new WeakMap();
474
+ /** Consecutive automatic wrap retries per connector; see below. */
475
+ this.wrapRetryCounts = new WeakMap();
476
+ /** Pending retry timers, cancelled by cleanup(). */
477
+ this.wrapRetryTimers = new Set();
478
+ /**
479
+ * Bumped on every observed disconnect. Every wrap attempt captures it
480
+ * at kick time and its resolution stands down on a mismatch, so a
481
+ * disconnect invalidates ALL pending wraps at once - including ones
482
+ * whose getProvider() had not resolved yet, which no per-connector
483
+ * bookkeeping can reach because nothing has been recorded for them.
484
+ */
485
+ this.wrapSessionGeneration = 0;
427
486
  this.formo = formoAnalytics;
428
487
  this.wagmiConfig = wagmiConfig;
429
488
  this.queryClient = queryClient;
@@ -598,6 +657,10 @@ var WagmiEventHandler = /** @class */ (function () {
598
657
  var connectionUnsubscribe = this.wagmiConfig.subscribe(function (state) { var _a, _b; return "".concat((_a = state.current) !== null && _a !== void 0 ? _a : "", "|").concat((_b = _this.getConnectedAddress(state)) !== null && _b !== void 0 ? _b : ""); }, function (key, prevKey) {
599
658
  var _a = key.split("|"), address = _a[1];
600
659
  var _b = (prevKey !== null && prevKey !== void 0 ? prevKey : "|").split("|"), prevAddress = _b[1];
660
+ // A connector switch keeps status "connected", so the status
661
+ // subscription never re-wraps; the NEW active connection's provider
662
+ // must be instrumented from here or its imperative calls are lost.
663
+ _this.wrapActiveConnectorProvider(_this.getState());
601
664
  _this.handleActiveAddressChange(address || undefined, prevAddress || undefined);
602
665
  });
603
666
  this.unsubscribers.push(connectionUnsubscribe);
@@ -903,6 +966,19 @@ var WagmiEventHandler = /** @class */ (function () {
903
966
  return __generator(this, function (_e) {
904
967
  switch (_e.label) {
905
968
  case 0:
969
+ if (status === "disconnected" || status === "reconnecting") {
970
+ // The wrapped session is over - or, for "reconnecting", about to be
971
+ // replaced without ever passing through "disconnected". This must
972
+ // run for EVERY such transition, before the processing lock and
973
+ // regardless of whether a wallet was being tracked, or a rapid
974
+ // same-connector reconnect retains the old provider and the chain
975
+ // callback writes the new session's chain onto it. The generation
976
+ // bump invalidates every wrap still in flight from the ended
977
+ // session, resolved or not. The "connected" that follows re-wraps.
978
+ this.wrapSessionGeneration += 1;
979
+ this.fallbackConnector = undefined;
980
+ this.fallbackProvider = undefined;
981
+ }
906
982
  // Prevent concurrent processing
907
983
  if (this.trackingState.isProcessing) {
908
984
  // Dropped, not queued - but remember that a disconnect went past.
@@ -1439,10 +1515,31 @@ var WagmiEventHandler = /** @class */ (function () {
1439
1515
  */
1440
1516
  WagmiEventHandler.prototype.handleChainChange = function (chainId, prevChainId) {
1441
1517
  return __awaiter(this, void 0, void 0, function () {
1442
- var state, address, hadAddress, wasAnnounced, announcedNow;
1443
- return __generator(this, function (_a) {
1444
- switch (_a.label) {
1518
+ var live, activeConnector, state, address, hadAddress, wasAnnounced, announcedNow;
1519
+ var _a, _b, _c;
1520
+ return __generator(this, function (_d) {
1521
+ switch (_d.label) {
1445
1522
  case 0:
1523
+ // Keep the fallback-wrapped provider's registry chain in step with the
1524
+ // store, so request-derived events are labelled correctly even when the
1525
+ // provider exposes no synchronous chainId. Only when the report is
1526
+ // about the connection that provider belongs to: after a connector
1527
+ // switch the store's chain describes the NEW connection, and writing it
1528
+ // to the old provider would mislabel that wallet's events.
1529
+ if (this.fallbackProvider !== undefined) {
1530
+ live = this.getState();
1531
+ activeConnector = live.current
1532
+ ? (_a = live.connections.get(live.current)) === null || _a === void 0 ? void 0 : _a.connector
1533
+ : undefined;
1534
+ if (activeConnector === this.fallbackConnector) {
1535
+ try {
1536
+ (_c = (_b = this.formo)._rememberWagmiProviderChain) === null || _c === void 0 ? void 0 : _c.call(_b, this.fallbackProvider, chainId);
1537
+ }
1538
+ catch (_e) {
1539
+ /* never let bookkeeping break the chain flow */
1540
+ }
1541
+ }
1542
+ }
1446
1543
  if (chainId === prevChainId || chainId === undefined) {
1447
1544
  return [2 /*return*/];
1448
1545
  }
@@ -1488,7 +1585,7 @@ var WagmiEventHandler = /** @class */ (function () {
1488
1585
  });
1489
1586
  return [4 /*yield*/, this.applyChainForTrackedWallet(address, chainId)];
1490
1587
  case 1:
1491
- _a.sent();
1588
+ _d.sent();
1492
1589
  return [2 /*return*/];
1493
1590
  }
1494
1591
  });
@@ -2246,6 +2343,75 @@ var WagmiEventHandler = /** @class */ (function () {
2246
2343
  });
2247
2344
  return found;
2248
2345
  };
2346
+ /**
2347
+ * A specific connector's name and rdns, for the fallback-wrapped
2348
+ * provider's request-derived events.
2349
+ *
2350
+ * The NAME is exactly what the hook path emits for that connector
2351
+ * (`getConnectorName`: the live WalletConnect peer for a WalletConnect
2352
+ * connector, the connector's own name otherwise), so a branded connector
2353
+ * keeps its name on both paths and a session that changes wallets
2354
+ * renames on both. The rdns is the connector's when wagmi knows it as
2355
+ * ONE value (EIP-6963 discovered connectors carry theirs, some as a
2356
+ * one-element list); a connector that matches several rdns values does
2357
+ * not say which one this provider is, so nothing is passed and the
2358
+ * registry keeps the sniffed one.
2359
+ */
2360
+ WagmiEventHandler.prototype.attributionForConnector = function (connector, state) {
2361
+ var name = this.nameForConnector(connector, state);
2362
+ if (!name) {
2363
+ return undefined;
2364
+ }
2365
+ var raw = connector.rdns;
2366
+ var rdns = typeof raw === "string" && raw.length > 0
2367
+ ? raw
2368
+ : Array.isArray(raw) && raw.length === 1 && typeof raw[0] === "string" && raw[0].length > 0
2369
+ ? raw[0]
2370
+ : undefined;
2371
+ return __assign({ name: name }, (rdns && { rdns: rdns }));
2372
+ };
2373
+ /**
2374
+ * Resolver handed to the registry at wrap time, read at each request
2375
+ * start. BOUND to the connector whose `getProvider` resolved to the
2376
+ * wrapped provider, not to whichever connector is current: an app can
2377
+ * keep a wallet client over a previous connector's provider and issue a
2378
+ * request through it after switching, and that request belongs to the
2379
+ * wallet that handles it. The peer name is still read live. A provider
2380
+ * SHARED by two connectors is re-bound when the new connector's
2381
+ * resolution lands (one microtask for an injected connector); a request
2382
+ * issued inside that gap keeps the previous label.
2383
+ */
2384
+ WagmiEventHandler.prototype.boundConnectorAttribution = function (connector) {
2385
+ var _this = this;
2386
+ return function () {
2387
+ if (_this.disposed)
2388
+ return undefined;
2389
+ try {
2390
+ return _this.attributionForConnector(connector, _this.getState());
2391
+ }
2392
+ catch (_a) {
2393
+ return undefined;
2394
+ }
2395
+ };
2396
+ };
2397
+ /** `getConnectorName` for a SPECIFIC connector, current or not. */
2398
+ WagmiEventHandler.prototype.nameForConnector = function (connector, state) {
2399
+ var _a;
2400
+ var cached = walletConnectPeerNames.get(connector);
2401
+ if (cached) {
2402
+ return cached;
2403
+ }
2404
+ var current = state.current
2405
+ ? (_a = state.connections.get(state.current)) === null || _a === void 0 ? void 0 : _a.connector
2406
+ : undefined;
2407
+ if (current === connector) {
2408
+ // Backstop kick, as in getConnectorName; only the current connection
2409
+ // has a session to look up.
2410
+ this.kickWalletConnectPeerLookup(state);
2411
+ }
2412
+ var name = connector.name;
2413
+ return typeof name === "string" && name.length > 0 ? name : undefined;
2414
+ };
2249
2415
  /**
2250
2416
  * Get the connector name from Wagmi state
2251
2417
  */
@@ -2267,30 +2433,10 @@ var WagmiEventHandler = /** @class */ (function () {
2267
2433
  this.kickWalletConnectPeerLookup(state);
2268
2434
  return connector.name;
2269
2435
  };
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) {
2436
+ WagmiEventHandler.prototype.wrapActiveConnectorProvider = function (state, isRetry) {
2292
2437
  var _this = this;
2293
- var _a, _b;
2438
+ var _a, _b, _c;
2439
+ if (isRetry === void 0) { isRetry = false; }
2294
2440
  // OPT-IN only. Wagmi mode's baseline never touches the signing
2295
2441
  // transport; instrumenting the provider is an explicit integrator
2296
2442
  // decision (options.wagmi.eip1193Fallback), made auditable in
@@ -2299,25 +2445,124 @@ var WagmiEventHandler = /** @class */ (function () {
2299
2445
  if (!optedIn) {
2300
2446
  return;
2301
2447
  }
2448
+ if (state.status !== "connected") {
2449
+ // Status kicks run for every transition; only a connected snapshot
2450
+ // describes a session worth wrapping.
2451
+ return;
2452
+ }
2302
2453
  var connection = state.current
2303
2454
  ? state.connections.get(state.current)
2304
2455
  : undefined;
2305
2456
  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)) {
2457
+ if (!connection || typeof (connector === null || connector === void 0 ? void 0 : connector.getProvider) !== "function") {
2309
2458
  return;
2310
2459
  }
2311
- wagmiWrappedConnections.add(connection);
2312
- connector
2313
- .getProvider()
2460
+ var epoch = ((_c = this.wrapEpochs.get(connector)) !== null && _c !== void 0 ? _c : 0) + 1;
2461
+ this.wrapEpochs.set(connector, epoch);
2462
+ var session = this.wrapSessionGeneration;
2463
+ // A store-driven kick resets the retry budget; automatic retries
2464
+ // (below) spend it.
2465
+ if (!isRetry) {
2466
+ this.wrapRetryCounts.delete(connector);
2467
+ }
2468
+ // When the NEWEST attempt fails, older in-flight resolutions have
2469
+ // been epoch-discarded (they may describe an earlier session) and
2470
+ // nothing else would wrap the provider until the next store update.
2471
+ // Recover by re-kicking fresh with live state, bounded so a
2472
+ // persistently failing connector cannot loop: after the budget is
2473
+ // spent, recovery waits for a real store update, which resets it.
2474
+ var retryAfterFailure = function () {
2475
+ var _a;
2476
+ if (_this.disposed)
2477
+ return;
2478
+ if (_this.wrapEpochs.get(connector) !== epoch) {
2479
+ // Superseded: the newer attempt owns recovery.
2480
+ return;
2481
+ }
2482
+ var failures = ((_a = _this.wrapRetryCounts.get(connector)) !== null && _a !== void 0 ? _a : 0) + 1;
2483
+ _this.wrapRetryCounts.set(connector, failures);
2484
+ if (failures > 3)
2485
+ return;
2486
+ var timer = setTimeout(function () {
2487
+ var _a;
2488
+ _this.wrapRetryTimers.delete(timer);
2489
+ if (_this.disposed)
2490
+ return;
2491
+ // Re-kick only while THIS connector is still the active one and
2492
+ // no newer attempt superseded this one. A retry for a connector
2493
+ // the user has left must not fire at the current connector: with
2494
+ // the retry flag it would skip the budget reset and, worse, its
2495
+ // fresh epoch would discard the current connector's own in-flight
2496
+ // resolution.
2497
+ var live = _this.getState();
2498
+ var stillCurrent = live.current !== undefined &&
2499
+ ((_a = live.connections.get(live.current)) === null || _a === void 0 ? void 0 : _a.connector) === connector;
2500
+ if (stillCurrent && _this.wrapEpochs.get(connector) === epoch) {
2501
+ _this.wrapActiveConnectorProvider(live, true);
2502
+ }
2503
+ }, 25 * failures);
2504
+ _this.wrapRetryTimers.add(timer);
2505
+ };
2506
+ var resolution;
2507
+ try {
2508
+ resolution = connector.getProvider();
2509
+ }
2510
+ catch (_d) {
2511
+ // A synchronous throw must not escape into the subscription
2512
+ // callback: address-change handling runs after this kick and a
2513
+ // propagated throw would silently skip it.
2514
+ retryAfterFailure();
2515
+ return;
2516
+ }
2517
+ resolution
2314
2518
  .then(function (provider) {
2315
- var _a, _b;
2316
- (_b = (_a = _this.formo)._wrapWagmiProvider) === null || _b === void 0 ? void 0 : _b.call(_a, provider);
2519
+ var _a, _b, _c, _d;
2520
+ if (_this.wrapEpochs.get(connector) !== epoch) {
2521
+ // A newer kick is in flight or already resolved; this answer may
2522
+ // describe an earlier session. Let the newest one win.
2523
+ return;
2524
+ }
2525
+ // The fallback installs only the request wrapper, so a provider
2526
+ // without a synchronous chainId property would never teach the
2527
+ // registry its chain and its events would carry chain 0. The wagmi
2528
+ // store knows; feed it here and on every later chain change. Read
2529
+ // the chain from LIVE state, not from a snapshot taken before this
2530
+ // asynchronous resolution: a switch that lands while getProvider is
2531
+ // in flight fires handleChainChange before fallbackProvider exists,
2532
+ // so a pre-resolution snapshot would stick as the recorded chain.
2533
+ // Activity is judged by CONNECTOR identity: wagmi replaces the
2534
+ // connection record itself on every account or chain update.
2535
+ var live = _this.getState();
2536
+ // A wrap only describes a CONNECTED session. Status changes kick
2537
+ // this path for every transition, and a resolution landing while
2538
+ // disconnected would re-point the fallback pair at a provider
2539
+ // whose session is over.
2540
+ var stillActive = !_this.disposed &&
2541
+ _this.wrapSessionGeneration === session &&
2542
+ live.status === "connected" &&
2543
+ live.current !== undefined &&
2544
+ ((_a = live.connections.get(live.current)) === null || _a === void 0 ? void 0 : _a.connector) === connector;
2545
+ if (!stillActive) {
2546
+ // The user moved on while getProvider was in flight. Wrapping
2547
+ // now would instrument a provider whose chain nobody feeds -
2548
+ // its requests would report chain 0. Switching back re-kicks.
2549
+ return;
2550
+ }
2551
+ var chainId = (_b = _this.getActiveConnectionChainId(live)) !== null && _b !== void 0 ? _b : live.chainId;
2552
+ var wrapped = (_d = (_c = _this.formo)._wrapWagmiProvider) === null || _d === void 0 ? void 0 : _d.call(_c, provider, typeof chainId === "number" ? chainId : undefined, _this.boundConnectorAttribution(connector));
2553
+ if (wrapped !== true) {
2554
+ // The provider was refused (invalid shape, frozen provider,
2555
+ // torn-down SDK).
2556
+ retryAfterFailure();
2557
+ return;
2558
+ }
2559
+ _this.wrapRetryCounts.delete(connector);
2560
+ _this.fallbackConnector = connector;
2561
+ _this.fallbackProvider = provider;
2317
2562
  })
2318
2563
  .catch(function () {
2319
- // Mutation-only capture remains; retry on the next connection.
2320
- wagmiWrappedConnections.delete(connection);
2564
+ // Mutation-only capture remains.
2565
+ retryAfterFailure();
2321
2566
  });
2322
2567
  };
2323
2568
  WagmiEventHandler.prototype.kickWalletConnectPeerLookup = function (state) {
@@ -2407,6 +2652,8 @@ var WagmiEventHandler = /** @class */ (function () {
2407
2652
  * Clean up all subscriptions
2408
2653
  */
2409
2654
  WagmiEventHandler.prototype.cleanup = function () {
2655
+ this.wrapRetryTimers.forEach(function (t) { return clearTimeout(t); });
2656
+ this.wrapRetryTimers.clear();
2410
2657
  logger.debug("WagmiEventHandler: Cleaning up subscriptions");
2411
2658
  if (!this.disposed) {
2412
2659
  this.disposed = true;
@@ -31,6 +31,12 @@ export interface WagmiConnector {
31
31
  name: string;
32
32
  type: string;
33
33
  uid: string;
34
+ /**
35
+ * Any connector implementation may expose an rdns; EIP-6963 discovered
36
+ * connectors are one source. A connector matching several rdns values
37
+ * lists them all.
38
+ */
39
+ rdns?: string | readonly string[];
34
40
  }
35
41
  /**
36
42
  * Wagmi config interface
@@ -185,7 +185,15 @@ export function buildSafeFunctionArgs(functionArgs, reservedFields) {
185
185
  var result = {};
186
186
  for (var _i = 0, _a = Object.entries(functionArgs); _i < _a.length; _i++) {
187
187
  var _b = _a[_i], key = _b[0], val = _b[1];
188
- var safeKey = reservedFields.has(key) ? "arg_".concat(key) : key;
188
+ // A prefixed key must not land on another argument's own name: an ABI
189
+ // with both `to` and `arg_to` would otherwise keep only one of them,
190
+ // whichever came later. Keep prefixing until the name is free.
191
+ var safeKey = key;
192
+ if (reservedFields.has(key)) {
193
+ do {
194
+ safeKey = "arg_".concat(safeKey);
195
+ } while (safeKey in functionArgs);
196
+ }
189
197
  result[safeKey] = val;
190
198
  // If the value is a nested object (struct), flatten it
191
199
  // Skip flattened keys that would overwrite existing top-level args