@formo/analytics 1.26.0 → 1.28.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 (87) hide show
  1. package/README.md +6 -4
  2. package/dist/cjs/src/FormoAnalytics.d.ts +71 -12
  3. package/dist/cjs/src/FormoAnalytics.js +274 -147
  4. package/dist/cjs/src/event/EventFactory.d.ts +10 -2
  5. package/dist/cjs/src/event/EventFactory.js +32 -21
  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/index.d.ts +4 -0
  9. package/dist/cjs/src/index.js +6 -0
  10. package/dist/cjs/src/privy/index.d.ts +9 -0
  11. package/dist/cjs/src/privy/index.js +12 -0
  12. package/dist/cjs/src/privy/types.d.ts +175 -0
  13. package/dist/cjs/src/privy/types.js +12 -0
  14. package/dist/cjs/src/privy/utils.d.ts +32 -0
  15. package/dist/cjs/src/privy/utils.js +188 -0
  16. package/dist/cjs/src/queue/EventQueue.d.ts +24 -2
  17. package/dist/cjs/src/queue/EventQueue.js +158 -49
  18. package/dist/cjs/src/session/index.js +2 -1
  19. package/dist/cjs/src/solana/SolanaAdapter.d.ts +211 -0
  20. package/dist/cjs/src/solana/SolanaAdapter.js +975 -0
  21. package/dist/cjs/src/solana/SolanaManager.d.ts +24 -0
  22. package/dist/cjs/src/solana/SolanaManager.js +80 -0
  23. package/dist/cjs/src/solana/address.d.ts +72 -0
  24. package/dist/cjs/src/solana/address.js +176 -0
  25. package/dist/cjs/src/solana/index.d.ts +13 -0
  26. package/dist/cjs/src/solana/index.js +32 -0
  27. package/dist/cjs/src/solana/types.d.ts +206 -0
  28. package/dist/cjs/src/solana/types.js +80 -0
  29. package/dist/cjs/src/types/base.d.ts +25 -0
  30. package/dist/cjs/src/types/events.d.ts +9 -6
  31. package/dist/cjs/src/utils/address.d.ts +21 -0
  32. package/dist/cjs/src/utils/address.js +48 -1
  33. package/dist/cjs/src/utils/builderCode.d.ts +30 -0
  34. package/dist/cjs/src/utils/builderCode.js +143 -0
  35. package/dist/cjs/src/utils/index.d.ts +1 -0
  36. package/dist/cjs/src/utils/index.js +1 -0
  37. package/dist/cjs/src/version.d.ts +1 -1
  38. package/dist/cjs/src/version.js +1 -1
  39. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +24 -0
  40. package/dist/cjs/src/wagmi/WagmiEventHandler.js +242 -24
  41. package/dist/cjs/src/wagmi/types.d.ts +31 -0
  42. package/dist/cjs/src/wagmi/utils.d.ts +79 -0
  43. package/dist/cjs/src/wagmi/utils.js +218 -0
  44. package/dist/esm/src/FormoAnalytics.d.ts +71 -12
  45. package/dist/esm/src/FormoAnalytics.js +275 -148
  46. package/dist/esm/src/event/EventFactory.d.ts +10 -2
  47. package/dist/esm/src/event/EventFactory.js +34 -23
  48. package/dist/esm/src/fetch/index.d.ts +10 -2
  49. package/dist/esm/src/fetch/index.js +123 -2
  50. package/dist/esm/src/index.d.ts +4 -0
  51. package/dist/esm/src/index.js +3 -0
  52. package/dist/esm/src/privy/index.d.ts +9 -0
  53. package/dist/esm/src/privy/index.js +8 -0
  54. package/dist/esm/src/privy/types.d.ts +175 -0
  55. package/dist/esm/src/privy/types.js +11 -0
  56. package/dist/esm/src/privy/utils.d.ts +32 -0
  57. package/dist/esm/src/privy/utils.js +185 -0
  58. package/dist/esm/src/queue/EventQueue.d.ts +24 -2
  59. package/dist/esm/src/queue/EventQueue.js +158 -49
  60. package/dist/esm/src/session/index.js +2 -1
  61. package/dist/esm/src/solana/SolanaAdapter.d.ts +211 -0
  62. package/dist/esm/src/solana/SolanaAdapter.js +972 -0
  63. package/dist/esm/src/solana/SolanaManager.d.ts +24 -0
  64. package/dist/esm/src/solana/SolanaManager.js +77 -0
  65. package/dist/esm/src/solana/address.d.ts +72 -0
  66. package/dist/esm/src/solana/address.js +167 -0
  67. package/dist/esm/src/solana/index.d.ts +13 -0
  68. package/dist/esm/src/solana/index.js +13 -0
  69. package/dist/esm/src/solana/types.d.ts +206 -0
  70. package/dist/esm/src/solana/types.js +74 -0
  71. package/dist/esm/src/types/base.d.ts +25 -0
  72. package/dist/esm/src/types/events.d.ts +9 -6
  73. package/dist/esm/src/utils/address.d.ts +21 -0
  74. package/dist/esm/src/utils/address.js +45 -0
  75. package/dist/esm/src/utils/builderCode.d.ts +30 -0
  76. package/dist/esm/src/utils/builderCode.js +140 -0
  77. package/dist/esm/src/utils/index.d.ts +1 -0
  78. package/dist/esm/src/utils/index.js +1 -0
  79. package/dist/esm/src/version.d.ts +1 -1
  80. package/dist/esm/src/version.js +1 -1
  81. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +24 -0
  82. package/dist/esm/src/wagmi/WagmiEventHandler.js +242 -24
  83. package/dist/esm/src/wagmi/types.d.ts +31 -0
  84. package/dist/esm/src/wagmi/utils.d.ts +79 -0
  85. package/dist/esm/src/wagmi/utils.js +211 -0
  86. package/dist/index.umd.min.js +1 -1
  87. package/package.json +20 -4
@@ -64,11 +64,13 @@ import { setConsentFlag, getConsentFlag, removeConsentFlag, } from "./consent";
64
64
  import { detectInjectedProviderInfo, isValidProvider } from "./provider";
65
65
  import { FormoAnalyticsSession, SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY, } from "./session";
66
66
  import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "./types";
67
- import { toChecksumAddress } from "./utils";
68
- import { getValidAddress } from "./utils/address";
67
+ import { validateAddress, validateAndChecksumAddress } from "./utils/address";
68
+ import { extractBuilderCodes } from "./utils/builderCode";
69
69
  import { isLocalhost } from "./validators";
70
70
  import { parseChainId } from "./utils/chain";
71
71
  import { WagmiEventHandler } from "./wagmi";
72
+ import { isSolanaChainId } from "./solana";
73
+ import { SolanaManager } from "./solana/SolanaManager";
72
74
  /**
73
75
  * Constants for provider switching reasons
74
76
  */
@@ -83,6 +85,11 @@ var FormoAnalytics = /** @class */ (function () {
83
85
  var _a, _b;
84
86
  this.writeKey = writeKey;
85
87
  this.options = options;
88
+ // Per-chain namespace state — isolates EVM and Solana connection state
89
+ this._chainState = {
90
+ evm: {},
91
+ solana: {},
92
+ };
86
93
  this._providerListenersMap = new Map();
87
94
  /**
88
95
  * EIP-6963 provider details discovered through the browser
@@ -137,6 +144,7 @@ var FormoAnalytics = /** @class */ (function () {
137
144
  retryCount: options.retryCount,
138
145
  maxQueueSize: options.maxQueueSize,
139
146
  flushInterval: options.flushInterval,
147
+ errorHandler: options.errorHandler,
140
148
  }), options);
141
149
  // Check consent status on initialization
142
150
  if (this.hasOptedOutTracking()) {
@@ -161,9 +169,39 @@ var FormoAnalytics = /** @class */ (function () {
161
169
  this.trackEIP1193Provider(provider);
162
170
  }
163
171
  }
172
+ // Initialize Solana manager if Solana options are provided
173
+ if (options.solana) {
174
+ this.solanaManager = new SolanaManager(this, options.solana);
175
+ }
164
176
  this.trackPageHit();
165
177
  this.trackPageHits();
166
178
  }
179
+ Object.defineProperty(FormoAnalytics.prototype, "_provider", {
180
+ // EVM state accessors — EVM listener paths must use these instead of
181
+ // currentAddress/currentChainId to avoid cross-namespace reads.
182
+ get: function () {
183
+ return this._chainState.evm.provider;
184
+ },
185
+ set: function (value) {
186
+ this._chainState.evm.provider = value;
187
+ },
188
+ enumerable: false,
189
+ configurable: true
190
+ });
191
+ Object.defineProperty(FormoAnalytics.prototype, "_evmAddress", {
192
+ get: function () {
193
+ return this._chainState.evm.address;
194
+ },
195
+ enumerable: false,
196
+ configurable: true
197
+ });
198
+ Object.defineProperty(FormoAnalytics.prototype, "_evmChainId", {
199
+ get: function () {
200
+ return this._chainState.evm.chainId;
201
+ },
202
+ enumerable: false,
203
+ configurable: true
204
+ });
167
205
  /**
168
206
  * Helper method to check if a provider is different from the currently active one
169
207
  * @param provider The provider to check
@@ -243,12 +281,17 @@ var FormoAnalytics = /** @class */ (function () {
243
281
  * @returns {void}
244
282
  */
245
283
  FormoAnalytics.prototype.cleanup = function () {
246
- logger.info("FormoAnalytics: Cleaning up resources");
284
+ logger.debug("FormoAnalytics: Cleaning up resources");
247
285
  // Clean up Wagmi handler if present
248
286
  if (this.wagmiHandler) {
249
287
  this.wagmiHandler.cleanup();
250
288
  this.wagmiHandler = undefined;
251
289
  }
290
+ // Clean up Solana manager if present
291
+ if (this.solanaManager) {
292
+ this.solanaManager.cleanup();
293
+ this.solanaManager = undefined;
294
+ }
252
295
  // Clean up EIP-1193 providers if not in Wagmi mode
253
296
  if (!this.isWagmiMode) {
254
297
  for (var _i = 0, _a = Array.from(this._trackedProviders); _i < _a.length; _i++) {
@@ -256,7 +299,7 @@ var FormoAnalytics = /** @class */ (function () {
256
299
  this.untrackProvider(provider);
257
300
  }
258
301
  }
259
- logger.info("FormoAnalytics: Cleanup complete");
302
+ logger.debug("FormoAnalytics: Cleanup complete");
260
303
  };
261
304
  /**
262
305
  * Emits a connect wallet event.
@@ -269,7 +312,7 @@ var FormoAnalytics = /** @class */ (function () {
269
312
  */
270
313
  FormoAnalytics.prototype.connect = function (_a, properties_1, context_1, callback_1) {
271
314
  return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
272
- var checksummedAddress;
315
+ var validAddress;
273
316
  var chainId = _b.chainId, address = _b.address;
274
317
  return __generator(this, function (_c) {
275
318
  switch (_c.label) {
@@ -282,16 +325,15 @@ var FormoAnalytics = /** @class */ (function () {
282
325
  logger.warn("Connect: Address cannot be empty");
283
326
  return [2 /*return*/];
284
327
  }
285
- this.currentChainId = chainId;
286
- checksummedAddress = this.validateAndChecksumAddress(address);
287
- if (!checksummedAddress) {
288
- logger.warn("Connect: Invalid address provided (\"".concat(address, "\"). Please provide a valid Ethereum address in checksum format."));
328
+ validAddress = validateAddress(address, chainId);
329
+ if (!validAddress) {
330
+ logger.warn("Connect: Invalid address provided (\"".concat(address, "\"). Please provide a valid EVM or Solana address."));
289
331
  return [2 /*return*/];
290
332
  }
291
- this.currentAddress = checksummedAddress;
333
+ this.setChainState(chainId, { address: validAddress });
292
334
  return [4 /*yield*/, this.trackEvent(EventType.CONNECT, {
293
335
  chainId: chainId,
294
- address: this.currentAddress,
336
+ address: validAddress,
295
337
  }, properties, context, callback)];
296
338
  case 1:
297
339
  _c.sent();
@@ -311,13 +353,14 @@ var FormoAnalytics = /** @class */ (function () {
311
353
  */
312
354
  FormoAnalytics.prototype.disconnect = function (params, properties, context, callback) {
313
355
  return __awaiter(this, void 0, void 0, function () {
314
- var chainId, address, providerInfo, disconnectProperties;
356
+ var chainId, address, isSolana, providerInfo, disconnectProperties;
315
357
  return __generator(this, function (_a) {
316
358
  switch (_a.label) {
317
359
  case 0:
318
360
  chainId = (params === null || params === void 0 ? void 0 : params.chainId) || this.currentChainId;
319
361
  address = (params === null || params === void 0 ? void 0 : params.address) || this.currentAddress;
320
- providerInfo = this._provider
362
+ isSolana = isSolanaChainId(chainId);
363
+ providerInfo = !isSolana && this._provider
321
364
  ? this.getProviderInfo(this._provider)
322
365
  : null;
323
366
  logger.info("Disconnect: Emitting disconnect event with:", {
@@ -333,9 +376,9 @@ var FormoAnalytics = /** @class */ (function () {
333
376
  return [4 /*yield*/, this.trackEvent(EventType.DISCONNECT, __assign(__assign({}, (chainId && { chainId: chainId })), (address && { address: address })), disconnectProperties, context, callback)];
334
377
  case 1:
335
378
  _a.sent();
336
- this.currentAddress = undefined;
337
- this.currentChainId = undefined;
338
- this.clearActiveProvider();
379
+ // Clear the disconnecting chain's namespace state.
380
+ // Per-chain isolation ensures a Solana disconnect never wipes EVM state (and vice versa).
381
+ this.clearChainState(chainId);
339
382
  logger.info("Wallet disconnected: Cleared currentAddress, currentChainId, and provider");
340
383
  return [2 /*return*/];
341
384
  }
@@ -369,7 +412,7 @@ var FormoAnalytics = /** @class */ (function () {
369
412
  logger.warn("FormoAnalytics::chain: address was empty and no previous address has been recorded");
370
413
  return [2 /*return*/];
371
414
  }
372
- this.currentChainId = chainId;
415
+ this.setChainState(chainId, {});
373
416
  return [4 /*yield*/, this.trackEvent(EventType.CHAIN, {
374
417
  chainId: chainId,
375
418
  address: address || this.currentAddress,
@@ -422,10 +465,10 @@ var FormoAnalytics = /** @class */ (function () {
422
465
  */
423
466
  FormoAnalytics.prototype.transaction = function (_a, properties_1, context_1, callback_1) {
424
467
  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;
468
+ 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, builder_codes = _b.builder_codes;
426
469
  return __generator(this, function (_c) {
427
470
  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)];
471
+ case 0: return [4 /*yield*/, this.trackEvent(EventType.TRANSACTION, __assign(__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 })), (builder_codes && { builder_codes: builder_codes })), properties, context, callback)];
429
472
  case 1:
430
473
  _c.sent();
431
474
  return [2 /*return*/];
@@ -435,42 +478,59 @@ var FormoAnalytics = /** @class */ (function () {
435
478
  };
436
479
  /**
437
480
  * Emits an identify event with current wallet address and provider info.
438
- * @param {string} params.address
439
- * @param {string} params.userId
440
- * @param {string} params.rdns
441
- * @param {string} params.providerName
442
- * @param {IFormoEventProperties} properties
481
+ *
482
+ * @param {string} params.address - Wallet address
483
+ * @param {string} params.userId - External user ID
484
+ * @param {string} params.rdns - Provider reverse domain name
485
+ * @param {string} params.providerName - Provider display name
486
+ * @param {IFormoEventProperties} properties - Additional properties to include with the identify event
443
487
  * @param {IFormoEventContext} context
444
488
  * @param {(...args: unknown[]) => void} callback
445
489
  * @returns {Promise<void>}
490
+ *
491
+ * @example
492
+ * ```ts
493
+ * // Basic identify
494
+ * formo.identify({ address: '0x...', userId: 'user123' });
495
+ *
496
+ * // With Privy user
497
+ * import { parsePrivyProperties } from '@formo/analytics';
498
+ * const { user } = usePrivy();
499
+ * if (user) {
500
+ * const { properties, wallets } = parsePrivyProperties(user);
501
+ * for (const wallet of wallets) {
502
+ * formo.identify({ address: wallet.address, userId: user.id }, properties);
503
+ * }
504
+ * }
505
+ * ```
446
506
  */
447
507
  FormoAnalytics.prototype.identify = function (params, properties, context, callback) {
448
508
  return __awaiter(this, void 0, void 0, function () {
449
- var _i, _a, providerDetail, provider, address_1, validAddress_1, err_1, userId, address, providerName, rdns, validAddress, isAlreadyIdentified, e_1;
450
- var _b;
451
- return __generator(this, function (_c) {
452
- switch (_c.label) {
509
+ var _i, _a, providerDetail, provider, address_1, validAddress_1, err_1, address, providerName, userId, rdns, validAddress, isAlreadyIdentified, e_1;
510
+ var _b, _c;
511
+ return __generator(this, function (_d) {
512
+ switch (_d.label) {
453
513
  case 0:
454
- _c.trys.push([0, 12, , 13]);
514
+ _d.trys.push([0, 12, , 13]);
455
515
  if (!!params) return [3 /*break*/, 10];
456
516
  // If no params provided, auto-identify
457
517
  logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
458
518
  _i = 0, _a = this._providers;
459
- _c.label = 1;
519
+ _d.label = 1;
460
520
  case 1:
461
521
  if (!(_i < _a.length)) return [3 /*break*/, 9];
462
522
  providerDetail = _a[_i];
463
523
  provider = providerDetail.provider;
464
524
  if (!provider)
465
525
  return [3 /*break*/, 8];
466
- _c.label = 2;
526
+ _d.label = 2;
467
527
  case 2:
468
- _c.trys.push([2, 7, , 8]);
528
+ _d.trys.push([2, 7, , 8]);
469
529
  return [4 /*yield*/, this.getAddress(provider)];
470
530
  case 3:
471
- address_1 = _c.sent();
531
+ address_1 = _d.sent();
472
532
  if (!address_1) return [3 /*break*/, 6];
473
- validAddress_1 = this.validateAndChecksumAddress(address_1);
533
+ validAddress_1 = validateAndChecksumAddress(address_1);
474
534
  logger.info("Auto-identify: Checking deduplication", {
475
535
  validAddress: validAddress_1,
476
536
  rdns: providerDetail.info.rdns,
@@ -490,16 +550,16 @@ var FormoAnalytics = /** @class */ (function () {
490
550
  }, properties, context, callback)];
491
551
  case 4:
492
552
  // NOTE: do not set this.currentAddress without explicit connect or identify
493
- _c.sent();
553
+ _d.sent();
494
554
  return [3 /*break*/, 6];
495
555
  case 5:
496
556
  if (validAddress_1) {
497
557
  logger.info("Auto-identify: Skipping already identified wallet", validAddress_1, providerDetail.info.name, providerDetail.info.rdns);
498
558
  }
499
- _c.label = 6;
559
+ _d.label = 6;
500
560
  case 6: return [3 /*break*/, 8];
501
561
  case 7:
502
- err_1 = _c.sent();
562
+ err_1 = _d.sent();
503
563
  logger.error("Failed to identify provider ".concat(providerDetail.info.name, ":"), err_1);
504
564
  return [3 /*break*/, 8];
505
565
  case 8:
@@ -507,32 +567,32 @@ var FormoAnalytics = /** @class */ (function () {
507
567
  return [3 /*break*/, 1];
508
568
  case 9: return [2 /*return*/];
509
569
  case 10:
510
- userId = params.userId, address = params.address, providerName = params.providerName, rdns = params.rdns;
570
+ address = params.address, providerName = params.providerName, userId = params.userId, rdns = params.rdns;
571
+ // Runtime validation: address is required
572
+ if (!address) {
573
+ (_b = logger.warn) === null || _b === void 0 ? void 0 : _b.call(logger, "identify() called without address - address is required");
574
+ return [2 /*return*/];
575
+ }
576
+ // Explicit identify
511
577
  logger.info("Identify", address, userId, providerName, rdns);
512
- validAddress = undefined;
513
- if (address) {
514
- validAddress = this.validateAndChecksumAddress(address);
515
- this.currentAddress = validAddress || undefined;
516
- if (!validAddress) {
517
- (_b = logger.warn) === null || _b === void 0 ? void 0 : _b.call(logger, "Invalid address provided to identify:", address);
518
- }
578
+ validAddress = validateAddress(address);
579
+ if (validAddress) {
580
+ this.currentAddress = validAddress;
519
581
  }
520
582
  else {
521
- this.currentAddress = undefined;
583
+ (_c = logger.warn) === null || _c === void 0 ? void 0 : _c.call(logger, "Invalid address provided to identify:", address);
584
+ return [2 /*return*/];
522
585
  }
523
586
  if (userId) {
524
587
  this.currentUserId = userId;
525
588
  cookie().set(SESSION_USER_ID_KEY, userId);
526
589
  }
527
- isAlreadyIdentified = validAddress
528
- ? this.session.isWalletIdentified(validAddress, rdns || "")
529
- : false;
590
+ isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "");
530
591
  logger.debug("Identify: Checking deduplication", {
531
592
  validAddress: validAddress,
532
593
  rdns: rdns,
533
594
  providerName: providerName,
534
- hasValidAddress: !!validAddress,
535
- hasRdns: !!rdns,
595
+ userId: userId,
536
596
  isAlreadyIdentified: isAlreadyIdentified,
537
597
  });
538
598
  if (isAlreadyIdentified) {
@@ -540,10 +600,7 @@ var FormoAnalytics = /** @class */ (function () {
540
600
  return [2 /*return*/];
541
601
  }
542
602
  // Mark as identified before emitting the event
543
- // Mark even if rdns is empty to prevent duplicate empty identifies
544
- if (validAddress) {
545
- this.session.markWalletIdentified(validAddress, rdns || "");
546
- }
603
+ this.session.markWalletIdentified(validAddress, rdns || "");
547
604
  return [4 /*yield*/, this.trackEvent(EventType.IDENTIFY, {
548
605
  address: validAddress,
549
606
  providerName: providerName,
@@ -551,10 +608,10 @@ var FormoAnalytics = /** @class */ (function () {
551
608
  rdns: rdns,
552
609
  }, properties, context, callback)];
553
610
  case 11:
554
- _c.sent();
611
+ _d.sent();
555
612
  return [3 /*break*/, 13];
556
613
  case 12:
557
- e_1 = _c.sent();
614
+ e_1 = _d.sent();
558
615
  logger.log("identify error", e_1);
559
616
  return [3 /*break*/, 13];
560
617
  case 13: return [2 /*return*/];
@@ -779,21 +836,21 @@ var FormoAnalytics = /** @class */ (function () {
779
836
  if (!(accounts.length === 0)) return [3 /*break*/, 9];
780
837
  if (!(this._provider === provider)) return [3 /*break*/, 7];
781
838
  logger.info("OnAccountsChanged: Detecting disconnect, current state:", {
782
- currentAddress: this.currentAddress,
783
- currentChainId: this.currentChainId,
839
+ evmAddress: this._evmAddress,
840
+ evmChainId: this._evmChainId,
784
841
  providerMatch: this._provider === provider,
785
842
  });
786
843
  if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
787
844
  _a.label = 1;
788
845
  case 1:
789
846
  _a.trys.push([1, 3, , 4]);
790
- // Pass current state explicitly to ensure we have the data for the disconnect event
847
+ // Pass EVM state explicitly to ensure we have the data for the disconnect event
791
848
  return [4 /*yield*/, this.disconnect({
792
- chainId: this.currentChainId,
793
- address: this.currentAddress,
849
+ chainId: this._evmChainId,
850
+ address: this._evmAddress,
794
851
  })];
795
852
  case 2:
796
- // Pass current state explicitly to ensure we have the data for the disconnect event
853
+ // Pass EVM state explicitly to ensure we have the data for the disconnect event
797
854
  _a.sent();
798
855
  return [3 /*break*/, 4];
799
856
  case 3:
@@ -804,9 +861,7 @@ var FormoAnalytics = /** @class */ (function () {
804
861
  case 5:
805
862
  logger.debug("OnAccountsChanged: Disconnect event skipped (autocapture.disconnect: false)");
806
863
  // Still clear state even if not tracking the event
807
- this.currentAddress = undefined;
808
- this.currentChainId = undefined;
809
- this.clearActiveProvider();
864
+ this.clearChainState('evm');
810
865
  _a.label = 6;
811
866
  case 6: return [3 /*break*/, 8];
812
867
  case 7:
@@ -814,14 +869,14 @@ var FormoAnalytics = /** @class */ (function () {
814
869
  _a.label = 8;
815
870
  case 8: return [2 /*return*/];
816
871
  case 9:
817
- address = this.validateAndChecksumAddress(accounts[0]);
872
+ address = validateAndChecksumAddress(accounts[0]);
818
873
  if (!address) {
819
874
  logger.warn("onAccountsChanged: Invalid address received", accounts[0]);
820
875
  return [2 /*return*/];
821
876
  }
822
- if (!(this._provider && this._provider !== provider)) return [3 /*break*/, 26];
823
- currentStoredAddress = this.currentAddress;
824
- newProviderAddress = this.validateAndChecksumAddress(address);
877
+ if (!(this._provider && this._provider !== provider)) return [3 /*break*/, 25];
878
+ currentStoredAddress = this._evmAddress;
879
+ newProviderAddress = validateAndChecksumAddress(address);
825
880
  logger.info("OnAccountsChanged: Different provider attempting to connect", {
826
881
  activeProvider: this.getProviderInfo(this._provider).name,
827
882
  eventProvider: this.getProviderInfo(provider).name,
@@ -830,7 +885,7 @@ var FormoAnalytics = /** @class */ (function () {
830
885
  });
831
886
  _a.label = 10;
832
887
  case 10:
833
- _a.trys.push([10, 22, , 26]);
888
+ _a.trys.push([10, 21, , 25]);
834
889
  return [4 /*yield*/, this.getAccounts(this._provider)];
835
890
  case 11:
836
891
  activeProviderAccounts = _a.sent();
@@ -854,8 +909,8 @@ var FormoAnalytics = /** @class */ (function () {
854
909
  });
855
910
  if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 13];
856
911
  return [4 /*yield*/, this.disconnect({
857
- chainId: this.currentChainId,
858
- address: this.currentAddress,
912
+ chainId: this._evmChainId,
913
+ address: this._evmAddress,
859
914
  })];
860
915
  case 12:
861
916
  _a.sent();
@@ -863,8 +918,7 @@ var FormoAnalytics = /** @class */ (function () {
863
918
  case 13:
864
919
  logger.debug("OnAccountsChanged: Disconnect event skipped during provider switch (autocapture.disconnect: false)");
865
920
  // Still clear state even if not tracking the event
866
- this.currentAddress = undefined;
867
- this.currentChainId = undefined;
921
+ this.clearChainState('evm');
868
922
  _a.label = 14;
869
923
  case 14:
870
924
  // Clear state and let the new provider become active
@@ -879,7 +933,7 @@ var FormoAnalytics = /** @class */ (function () {
879
933
  newAddress: newProviderAddress,
880
934
  });
881
935
  return [2 /*return*/];
882
- case 16: return [3 /*break*/, 21];
936
+ case 16: return [3 /*break*/, 20];
883
937
  case 17:
884
938
  logger.info("OnAccountsChanged: Current provider has no accounts, switching to new provider", {
885
939
  oldProvider: this.getProviderInfo(this._provider).name,
@@ -888,8 +942,8 @@ var FormoAnalytics = /** @class */ (function () {
888
942
  });
889
943
  if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 19];
890
944
  return [4 /*yield*/, this.disconnect({
891
- chainId: this.currentChainId,
892
- address: this.currentAddress,
945
+ chainId: this._evmChainId,
946
+ address: this._evmAddress,
893
947
  })];
894
948
  case 18:
895
949
  _a.sent();
@@ -897,15 +951,10 @@ var FormoAnalytics = /** @class */ (function () {
897
951
  case 19:
898
952
  logger.debug("OnAccountsChanged: Disconnect event skipped for old provider (autocapture.disconnect: false)");
899
953
  // Still clear state even if not tracking the event
900
- this.currentAddress = undefined;
901
- this.currentChainId = undefined;
954
+ this.clearChainState('evm');
902
955
  _a.label = 20;
903
- case 20:
904
- // Clear state and let the new provider become active
905
- this.clearActiveProvider();
906
- _a.label = 21;
907
- case 21: return [3 /*break*/, 26];
908
- case 22:
956
+ case 20: return [3 /*break*/, 25];
957
+ case 21:
909
958
  error_2 = _a.sent();
910
959
  logger.warn("OnAccountsChanged: Could not check current provider accounts, switching to new provider", {
911
960
  error: error_2 instanceof Error ? error_2.message : String(error_2),
@@ -916,40 +965,36 @@ var FormoAnalytics = /** @class */ (function () {
916
965
  newProvider: this.getProviderInfo(provider).name,
917
966
  reason: PROVIDER_SWITCH_REASONS.CHECK_FAILED,
918
967
  });
919
- if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 24];
968
+ if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 23];
920
969
  return [4 /*yield*/, this.disconnect({
921
- chainId: this.currentChainId,
922
- address: this.currentAddress,
970
+ chainId: this._evmChainId,
971
+ address: this._evmAddress,
923
972
  })];
924
- case 23:
973
+ case 22:
925
974
  _a.sent();
926
- return [3 /*break*/, 25];
927
- case 24:
975
+ return [3 /*break*/, 24];
976
+ case 23:
928
977
  logger.debug("OnAccountsChanged: Disconnect event skipped for failed provider check (autocapture.disconnect: false)");
929
978
  // Still clear state even if not tracking the event
930
- this.currentAddress = undefined;
931
- this.currentChainId = undefined;
932
- _a.label = 25;
979
+ this.clearChainState('evm');
980
+ _a.label = 24;
981
+ case 24: return [3 /*break*/, 25];
933
982
  case 25:
934
- this.clearActiveProvider();
935
- return [3 /*break*/, 26];
936
- case 26:
937
983
  // Set provider if none exists (first connection)
938
984
  if (!this._provider) {
939
985
  this._provider = provider;
940
986
  }
941
987
  // If both the provider and address are the same, no-op
942
- if (this._provider === provider && address === this.currentAddress) {
988
+ if (this._provider === provider && address === this._evmAddress) {
943
989
  return [2 /*return*/];
944
990
  }
945
991
  return [4 /*yield*/, this.getCurrentChainId(provider)];
946
- case 27:
992
+ case 26:
947
993
  nextChainId = _a.sent();
948
- wasDisconnected = !this.currentAddress;
994
+ wasDisconnected = !this._evmAddress;
949
995
  // CRITICAL: Always update state regardless of whether connect tracking is enabled
950
996
  // This ensures disconnect events will have valid address/chainId values
951
- this.currentAddress = address;
952
- this.currentChainId = nextChainId;
997
+ this.setChainState('evm', { address: address, chainId: nextChainId });
953
998
  providerInfo = this.getProviderInfo(provider);
954
999
  effectiveChainId = nextChainId || 0;
955
1000
  if (this.isAutocaptureEnabled("connect")) {
@@ -1010,7 +1055,7 @@ var FormoAnalytics = /** @class */ (function () {
1010
1055
  this.handleProviderMismatch(provider);
1011
1056
  }
1012
1057
  // Chain changes only matter for connected users
1013
- if (!this.currentAddress) {
1058
+ if (!this._evmAddress) {
1014
1059
  logger.info("OnChainChanged: No current address, user appears disconnected");
1015
1060
  return [2 /*return*/, Promise.resolve()];
1016
1061
  }
@@ -1018,19 +1063,19 @@ var FormoAnalytics = /** @class */ (function () {
1018
1063
  if (!this._provider) {
1019
1064
  this._provider = provider;
1020
1065
  }
1021
- this.currentChainId = nextChainId;
1066
+ this.setChainState('evm', { chainId: nextChainId });
1022
1067
  try {
1023
- // This is just a chain change since we already confirmed currentAddress exists
1068
+ // This is just a chain change since we already confirmed _evmAddress exists
1024
1069
  if (this.isAutocaptureEnabled("chain")) {
1025
1070
  return [2 /*return*/, this.chain({
1026
- chainId: this.currentChainId,
1027
- address: this.currentAddress,
1071
+ chainId: nextChainId,
1072
+ address: this._evmAddress,
1028
1073
  })];
1029
1074
  }
1030
1075
  else {
1031
1076
  logger.debug("OnChainChanged: Chain event skipped (autocapture.chain: false)", {
1032
- chainId: this.currentChainId,
1033
- address: this.currentAddress,
1077
+ chainId: this._evmChainId,
1078
+ address: this._evmAddress,
1034
1079
  });
1035
1080
  }
1036
1081
  }
@@ -1066,8 +1111,8 @@ var FormoAnalytics = /** @class */ (function () {
1066
1111
  if (this._provider !== provider)
1067
1112
  return [2 /*return*/];
1068
1113
  logger.info("OnDisconnect: Wallet disconnect event received, current state:", {
1069
- currentAddress: this.currentAddress,
1070
- currentChainId: this.currentChainId,
1114
+ currentAddress: this._evmAddress,
1115
+ currentChainId: this._evmChainId,
1071
1116
  });
1072
1117
  if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
1073
1118
  _a.label = 1;
@@ -1075,8 +1120,8 @@ var FormoAnalytics = /** @class */ (function () {
1075
1120
  _a.trys.push([1, 3, , 4]);
1076
1121
  // Pass current state explicitly to ensure we have the data for the disconnect event
1077
1122
  return [4 /*yield*/, this.disconnect({
1078
- chainId: this.currentChainId,
1079
- address: this.currentAddress,
1123
+ chainId: this._evmChainId,
1124
+ address: this._evmAddress,
1080
1125
  })];
1081
1126
  case 2:
1082
1127
  // Pass current state explicitly to ensure we have the data for the disconnect event
@@ -1090,9 +1135,7 @@ var FormoAnalytics = /** @class */ (function () {
1090
1135
  case 5:
1091
1136
  logger.debug("OnDisconnect: Disconnect event skipped (autocapture.disconnect: false)");
1092
1137
  // Still clear state even if not tracking the event
1093
- this.currentAddress = undefined;
1094
- this.currentChainId = undefined;
1095
- this.clearActiveProvider();
1138
+ this.clearChainState('evm');
1096
1139
  _a.label = 6;
1097
1140
  case 6: return [2 /*return*/];
1098
1141
  }
@@ -1118,7 +1161,7 @@ var FormoAnalytics = /** @class */ (function () {
1118
1161
  case 2:
1119
1162
  address = _a.sent();
1120
1163
  if (chainId && address) {
1121
- wasDisconnected = !this.currentAddress;
1164
+ wasDisconnected = !this._evmAddress;
1122
1165
  // Set provider if none exists
1123
1166
  if (!this._provider) {
1124
1167
  this._provider = provider;
@@ -1127,12 +1170,13 @@ var FormoAnalytics = /** @class */ (function () {
1127
1170
  // CRITICAL: Always update state from active provider regardless of tracking config
1128
1171
  // This ensures disconnect events will have valid address/chainId values
1129
1172
  if (isActiveProvider) {
1130
- this.currentChainId = chainId;
1131
- this.currentAddress =
1132
- this.validateAndChecksumAddress(address) || undefined;
1173
+ this.setChainState('evm', {
1174
+ chainId: chainId,
1175
+ address: validateAndChecksumAddress(address) || undefined,
1176
+ });
1133
1177
  }
1134
1178
  // Conditionally emit connect event based on tracking configuration
1135
- if (isActiveProvider && this.currentAddress) {
1179
+ if (isActiveProvider && this._evmAddress) {
1136
1180
  providerInfo = this.getProviderInfo(provider);
1137
1181
  effectiveChainId = chainId || 0;
1138
1182
  if (this.isAutocaptureEnabled("connect")) {
@@ -1215,7 +1259,7 @@ var FormoAnalytics = /** @class */ (function () {
1215
1259
  logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
1216
1260
  return [2 /*return*/, request({ method: method, params: params })];
1217
1261
  }
1218
- _c = this.currentChainId;
1262
+ _c = this._evmChainId;
1219
1263
  if (_c) return [3 /*break*/, 2];
1220
1264
  return [4 /*yield*/, this.getCurrentChainId(provider)];
1221
1265
  case 1:
@@ -1712,14 +1756,37 @@ var FormoAnalytics = /** @class */ (function () {
1712
1756
  enumerable: false,
1713
1757
  configurable: true
1714
1758
  });
1759
+ Object.defineProperty(FormoAnalytics.prototype, "solana", {
1760
+ /**
1761
+ * Access the Solana integration manager.
1762
+ * Lazily creates one if not already initialized.
1763
+ *
1764
+ * @example
1765
+ * ```tsx
1766
+ * formo.solana.setWallet(wallet);
1767
+ * formo.solana.setConnection(connection);
1768
+ * formo.solana.setCluster("devnet");
1769
+ * formo.solana.syncWalletState();
1770
+ * ```
1771
+ */
1772
+ get: function () {
1773
+ if (!this.solanaManager) {
1774
+ this.solanaManager = new SolanaManager(this);
1775
+ }
1776
+ return this.solanaManager;
1777
+ },
1778
+ enumerable: false,
1779
+ configurable: true
1780
+ });
1715
1781
  FormoAnalytics.prototype.getAddress = function (provider) {
1716
1782
  return __awaiter(this, void 0, void 0, function () {
1717
1783
  var p, accounts, err_3, code;
1718
1784
  return __generator(this, function (_a) {
1719
1785
  switch (_a.label) {
1720
1786
  case 0:
1721
- if (this.currentAddress)
1722
- return [2 /*return*/, this.currentAddress];
1787
+ // Use EVM-specific state to avoid returning a Solana address in an EVM context
1788
+ if (this._chainState.evm.address)
1789
+ return [2 /*return*/, this._chainState.evm.address];
1723
1790
  p = provider || this.provider;
1724
1791
  if (!p) {
1725
1792
  logger.info("The provider is not set");
@@ -1732,7 +1799,7 @@ var FormoAnalytics = /** @class */ (function () {
1732
1799
  case 2:
1733
1800
  accounts = _a.sent();
1734
1801
  if (accounts && accounts.length > 0) {
1735
- return [2 /*return*/, this.validateAndChecksumAddress(accounts[0]) || null];
1802
+ return [2 /*return*/, validateAndChecksumAddress(accounts[0]) || null];
1736
1803
  }
1737
1804
  return [3 /*break*/, 4];
1738
1805
  case 3:
@@ -1750,7 +1817,6 @@ var FormoAnalytics = /** @class */ (function () {
1750
1817
  FormoAnalytics.prototype.getAccounts = function (provider) {
1751
1818
  return __awaiter(this, void 0, void 0, function () {
1752
1819
  var p, res, err_4, code;
1753
- var _this = this;
1754
1820
  return __generator(this, function (_a) {
1755
1821
  switch (_a.label) {
1756
1822
  case 0:
@@ -1766,7 +1832,7 @@ var FormoAnalytics = /** @class */ (function () {
1766
1832
  if (!res || res.length === 0)
1767
1833
  return [2 /*return*/, null];
1768
1834
  return [2 /*return*/, res
1769
- .map(function (e) { return _this.validateAndChecksumAddress(e); })
1835
+ .map(function (e) { return validateAndChecksumAddress(e); })
1770
1836
  .filter(function (e) { return e !== undefined; })];
1771
1837
  case 3:
1772
1838
  err_4 = _a.sent();
@@ -1818,12 +1884,12 @@ var FormoAnalytics = /** @class */ (function () {
1818
1884
  var rawAddress = method === "personal_sign"
1819
1885
  ? params[1]
1820
1886
  : params[0];
1821
- var validAddress = this.validateAndChecksumAddress(rawAddress);
1887
+ var validAddress = validateAndChecksumAddress(rawAddress);
1822
1888
  if (!validAddress) {
1823
1889
  throw new Error("Invalid address in signature payload: ".concat(rawAddress));
1824
1890
  }
1825
1891
  var basePayload = {
1826
- chainId: (_a = chainId !== null && chainId !== void 0 ? chainId : this.currentChainId) !== null && _a !== void 0 ? _a : undefined,
1892
+ chainId: (_a = chainId !== null && chainId !== void 0 ? chainId : this._evmChainId) !== null && _a !== void 0 ? _a : undefined,
1827
1893
  address: validAddress,
1828
1894
  };
1829
1895
  if (method === "personal_sign") {
@@ -1834,29 +1900,25 @@ var FormoAnalytics = /** @class */ (function () {
1834
1900
  };
1835
1901
  FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
1836
1902
  return __awaiter(this, void 0, void 0, function () {
1837
- var _a, data, from, to, value, validAddress, _b;
1903
+ var _a, data, from, to, value, validAddress, builder_codes, _b;
1838
1904
  var _c;
1839
1905
  return __generator(this, function (_d) {
1840
1906
  switch (_d.label) {
1841
1907
  case 0:
1842
1908
  _a = params[0], data = _a.data, from = _a.from, to = _a.to, value = _a.value;
1843
- validAddress = this.validateAndChecksumAddress(from);
1909
+ validAddress = validateAndChecksumAddress(from);
1844
1910
  if (!validAddress) {
1845
1911
  throw new Error("Invalid address in transaction payload: ".concat(from));
1846
1912
  }
1913
+ builder_codes = extractBuilderCodes(data);
1847
1914
  _c = {};
1848
- _b = this.currentChainId;
1915
+ _b = this._evmChainId;
1849
1916
  if (_b) return [3 /*break*/, 2];
1850
1917
  return [4 /*yield*/, this.getCurrentChainId(provider)];
1851
1918
  case 1:
1852
1919
  _b = (_d.sent());
1853
1920
  _d.label = 2;
1854
- case 2: return [2 /*return*/, (_c.chainId = _b,
1855
- _c.data = data,
1856
- _c.address = validAddress,
1857
- _c.to = to,
1858
- _c.value = value,
1859
- _c)];
1921
+ case 2: return [2 /*return*/, __assign.apply(void 0, [(_c.chainId = _b, _c.data = data, _c.address = validAddress, _c.to = to, _c.value = value, _c), (builder_codes && { builder_codes: builder_codes })])];
1860
1922
  }
1861
1923
  });
1862
1924
  });
@@ -1996,19 +2058,84 @@ var FormoAnalytics = /** @class */ (function () {
1996
2058
  // If this is a different provider, allow the switch
1997
2059
  if (this._provider) {
1998
2060
  // Clear any provider-specific state when switching
1999
- this.currentChainId = undefined;
2000
- this.currentAddress = undefined;
2061
+ this.setChainState('evm', { address: undefined, chainId: undefined, provider: provider });
2001
2062
  }
2002
- this._provider = provider;
2063
+ else {
2064
+ this._provider = provider;
2065
+ }
2066
+ };
2067
+ /**
2068
+ * Determine which namespace a chainId belongs to.
2069
+ */
2070
+ FormoAnalytics.prototype.getNamespace = function (chainId) {
2071
+ return isSolanaChainId(chainId) ? 'solana' : 'evm';
2072
+ };
2073
+ /**
2074
+ * Update per-chain state and sync the derived currentAddress/currentChainId.
2075
+ * Accepts either a namespace string ('evm'/'solana') or a chainId number
2076
+ * to resolve the namespace automatically. When a chainId number is passed,
2077
+ * it is also stored as the namespace's chainId (unless explicitly overridden
2078
+ * in the update object).
2079
+ */
2080
+ FormoAnalytics.prototype.setChainState = function (namespaceOrChainId, update) {
2081
+ var namespace = typeof namespaceOrChainId === 'string'
2082
+ ? namespaceOrChainId
2083
+ : this.getNamespace(namespaceOrChainId);
2084
+ var ns = this._chainState[namespace];
2085
+ if ('address' in update)
2086
+ ns.address = update.address;
2087
+ if ('chainId' in update) {
2088
+ ns.chainId = update.chainId;
2089
+ }
2090
+ else if (typeof namespaceOrChainId === 'number') {
2091
+ ns.chainId = namespaceOrChainId;
2092
+ }
2093
+ if (namespace === 'evm' && 'provider' in update) {
2094
+ ns.provider = update.provider;
2095
+ }
2096
+ this._activeNamespace = namespace;
2097
+ this.syncDerivedState();
2003
2098
  };
2004
2099
  /**
2005
- * Helper method to validate and checksum an address
2006
- * @param address The address to validate and checksum
2007
- * @returns The checksummed address or undefined if invalid
2100
+ * Clear per-chain state for a given namespace (or chainId) and sync derived state.
2008
2101
  */
2009
- FormoAnalytics.prototype.validateAndChecksumAddress = function (address) {
2010
- var validAddress = getValidAddress(address);
2011
- return validAddress ? toChecksumAddress(validAddress) : undefined;
2102
+ FormoAnalytics.prototype.clearChainState = function (namespaceOrChainId) {
2103
+ var namespace = typeof namespaceOrChainId === 'string'
2104
+ ? namespaceOrChainId
2105
+ : this.getNamespace(namespaceOrChainId);
2106
+ if (namespace === 'evm') {
2107
+ this._chainState.evm = {};
2108
+ }
2109
+ else {
2110
+ this._chainState.solana = {};
2111
+ }
2112
+ this.syncDerivedState();
2113
+ };
2114
+ /**
2115
+ * Synchronize currentAddress/currentChainId from the active namespace.
2116
+ * Last-connected-chain-wins: _activeNamespace takes precedence.
2117
+ */
2118
+ FormoAnalytics.prototype.syncDerivedState = function () {
2119
+ var active = this._activeNamespace;
2120
+ if (active) {
2121
+ var state = this._chainState[active];
2122
+ if (state.address || state.chainId) {
2123
+ this.currentAddress = state.address;
2124
+ this.currentChainId = state.chainId;
2125
+ return;
2126
+ }
2127
+ }
2128
+ // Fall through to the other namespace
2129
+ var other = active === 'evm' ? 'solana' : 'evm';
2130
+ var otherState = this._chainState[other];
2131
+ if (otherState.address || otherState.chainId) {
2132
+ this.currentAddress = otherState.address;
2133
+ this.currentChainId = otherState.chainId;
2134
+ this._activeNamespace = other;
2135
+ return;
2136
+ }
2137
+ this.currentAddress = undefined;
2138
+ this.currentChainId = undefined;
2012
2139
  };
2013
2140
  /**
2014
2141
  * Helper method to clear the active provider state