@formo/analytics 1.27.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 (74) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +69 -12
  2. package/dist/cjs/src/FormoAnalytics.js +273 -147
  3. package/dist/cjs/src/event/EventFactory.d.ts +10 -2
  4. package/dist/cjs/src/event/EventFactory.js +32 -21
  5. package/dist/cjs/src/index.d.ts +4 -0
  6. package/dist/cjs/src/index.js +6 -0
  7. package/dist/cjs/src/privy/index.d.ts +9 -0
  8. package/dist/cjs/src/privy/index.js +12 -0
  9. package/dist/cjs/src/privy/types.d.ts +175 -0
  10. package/dist/cjs/src/privy/types.js +12 -0
  11. package/dist/cjs/src/privy/utils.d.ts +32 -0
  12. package/dist/cjs/src/privy/utils.js +188 -0
  13. package/dist/cjs/src/session/index.js +2 -1
  14. package/dist/cjs/src/solana/SolanaAdapter.d.ts +211 -0
  15. package/dist/cjs/src/solana/SolanaAdapter.js +975 -0
  16. package/dist/cjs/src/solana/SolanaManager.d.ts +24 -0
  17. package/dist/cjs/src/solana/SolanaManager.js +80 -0
  18. package/dist/cjs/src/solana/address.d.ts +72 -0
  19. package/dist/cjs/src/solana/address.js +176 -0
  20. package/dist/cjs/src/solana/index.d.ts +13 -0
  21. package/dist/cjs/src/solana/index.js +32 -0
  22. package/dist/cjs/src/solana/types.d.ts +206 -0
  23. package/dist/cjs/src/solana/types.js +80 -0
  24. package/dist/cjs/src/types/base.d.ts +17 -0
  25. package/dist/cjs/src/types/events.d.ts +4 -3
  26. package/dist/cjs/src/utils/address.d.ts +21 -0
  27. package/dist/cjs/src/utils/address.js +48 -1
  28. package/dist/cjs/src/utils/builderCode.d.ts +30 -0
  29. package/dist/cjs/src/utils/builderCode.js +143 -0
  30. package/dist/cjs/src/utils/index.d.ts +1 -0
  31. package/dist/cjs/src/utils/index.js +1 -0
  32. package/dist/cjs/src/version.d.ts +1 -1
  33. package/dist/cjs/src/version.js +1 -1
  34. package/dist/cjs/src/wagmi/WagmiEventHandler.js +13 -15
  35. package/dist/cjs/src/wagmi/utils.d.ts +5 -0
  36. package/dist/cjs/src/wagmi/utils.js +20 -0
  37. package/dist/esm/src/FormoAnalytics.d.ts +69 -12
  38. package/dist/esm/src/FormoAnalytics.js +274 -148
  39. package/dist/esm/src/event/EventFactory.d.ts +10 -2
  40. package/dist/esm/src/event/EventFactory.js +34 -23
  41. package/dist/esm/src/index.d.ts +4 -0
  42. package/dist/esm/src/index.js +3 -0
  43. package/dist/esm/src/privy/index.d.ts +9 -0
  44. package/dist/esm/src/privy/index.js +8 -0
  45. package/dist/esm/src/privy/types.d.ts +175 -0
  46. package/dist/esm/src/privy/types.js +11 -0
  47. package/dist/esm/src/privy/utils.d.ts +32 -0
  48. package/dist/esm/src/privy/utils.js +185 -0
  49. package/dist/esm/src/session/index.js +2 -1
  50. package/dist/esm/src/solana/SolanaAdapter.d.ts +211 -0
  51. package/dist/esm/src/solana/SolanaAdapter.js +972 -0
  52. package/dist/esm/src/solana/SolanaManager.d.ts +24 -0
  53. package/dist/esm/src/solana/SolanaManager.js +77 -0
  54. package/dist/esm/src/solana/address.d.ts +72 -0
  55. package/dist/esm/src/solana/address.js +167 -0
  56. package/dist/esm/src/solana/index.d.ts +13 -0
  57. package/dist/esm/src/solana/index.js +13 -0
  58. package/dist/esm/src/solana/types.d.ts +206 -0
  59. package/dist/esm/src/solana/types.js +74 -0
  60. package/dist/esm/src/types/base.d.ts +17 -0
  61. package/dist/esm/src/types/events.d.ts +4 -3
  62. package/dist/esm/src/utils/address.d.ts +21 -0
  63. package/dist/esm/src/utils/address.js +45 -0
  64. package/dist/esm/src/utils/builderCode.d.ts +30 -0
  65. package/dist/esm/src/utils/builderCode.js +140 -0
  66. package/dist/esm/src/utils/index.d.ts +1 -0
  67. package/dist/esm/src/utils/index.js +1 -0
  68. package/dist/esm/src/version.d.ts +1 -1
  69. package/dist/esm/src/version.js +1 -1
  70. package/dist/esm/src/wagmi/WagmiEventHandler.js +14 -16
  71. package/dist/esm/src/wagmi/utils.d.ts +5 -0
  72. package/dist/esm/src/wagmi/utils.js +19 -0
  73. package/dist/index.umd.min.js +1 -1
  74. package/package.json +15 -3
@@ -2,9 +2,18 @@ import { LogLevel } from "../logger";
2
2
  import { IFormoEventContext, IFormoEventProperties, SignatureStatus, TransactionStatus } from "./events";
3
3
  import { EIP1193Provider } from "./provider";
4
4
  import { ReactNode } from "react";
5
+ import { SolanaOptions } from "../solana/types";
5
6
  export type Nullable<T> = T | null;
6
7
  export type ChainID = number;
7
8
  export type Address = string;
9
+ export type ChainNamespace = 'evm' | 'solana';
10
+ export interface ChainState {
11
+ address?: Address;
12
+ chainId?: ChainID;
13
+ }
14
+ export interface EvmChainState extends ChainState {
15
+ provider?: EIP1193Provider;
16
+ }
8
17
  export type ValidInputTypes = Uint8Array | bigint | string | number | boolean;
9
18
  export interface IFormoAnalytics {
10
19
  page(category?: string, name?: string, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
@@ -151,6 +160,14 @@ export interface Options {
151
160
  * @requires @tanstack/react-query@>=5.0.0 (for mutation tracking)
152
161
  */
153
162
  wagmi?: WagmiOptions;
163
+ /**
164
+ * Solana Wallet Adapter integration configuration
165
+ * When provided, the SDK will track Solana wallet events in addition to EVM wallet events
166
+ * This works alongside EIP-1193/Wagmi tracking - you can track both EVM and Solana wallets
167
+ * @requires @solana/wallet-adapter-base (optional peer dependency)
168
+ * @requires @solana/wallet-adapter-react (optional peer dependency, for React apps)
169
+ */
170
+ solana?: SolanaOptions;
154
171
  /**
155
172
  * Custom API host for sending events through your own domain to bypass ad blockers
156
173
  * - If not provided, events are sent directly to events.formo.so
@@ -44,9 +44,9 @@ export interface DetectAPIEvent {
44
44
  }
45
45
  export interface IdentifyAPIEvent {
46
46
  type: "identify";
47
- address: string;
48
- providerName: string;
49
- rdns: string;
47
+ address: Address;
48
+ providerName?: string;
49
+ rdns?: string;
50
50
  userId?: Nullable<string>;
51
51
  }
52
52
  export interface ChainAPIEvent {
@@ -65,6 +65,7 @@ export interface TransactionAPIEvent {
65
65
  transactionHash?: string;
66
66
  function_name?: string;
67
67
  function_args?: Record<string, unknown>;
68
+ builder_codes?: string;
68
69
  }
69
70
  export interface SignatureAPIEvent {
70
71
  type: "signature";
@@ -26,4 +26,25 @@ export declare const getValidAddress: (address: Address | null | undefined) => s
26
26
  */
27
27
  export declare const isBlockedAddress: (address: Address | null | undefined) => boolean;
28
28
  export declare const toChecksumAddress: (address: Address) => string;
29
+ /**
30
+ * Validates an EVM address and returns it in checksummed format.
31
+ * @param address The address to validate
32
+ * @returns The checksummed address or undefined if invalid
33
+ */
34
+ export declare const validateAndChecksumAddress: (address: string) => Address | undefined;
35
+ /**
36
+ * Validates an address for both EVM and Solana chains.
37
+ * For EVM addresses, returns checksummed format.
38
+ * For Solana addresses, returns the Base58 address as-is.
39
+ *
40
+ * When chainId is explicitly provided, validation is strict:
41
+ * - Solana chainId → only Solana validation
42
+ * - Non-Solana chainId → only EVM validation
43
+ * When chainId is omitted, EVM is tried first with Solana fallback.
44
+ *
45
+ * @param address The address to validate
46
+ * @param chainId Optional chain ID to determine address type
47
+ * @returns The validated address or undefined if invalid
48
+ */
49
+ export declare const validateAddress: (address: string, chainId?: number) => Address | undefined;
29
50
  //# sourceMappingURL=address.d.ts.map
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toChecksumAddress = exports.isBlockedAddress = exports.getValidAddress = exports.isValidAddress = void 0;
3
+ exports.validateAddress = exports.validateAndChecksumAddress = exports.toChecksumAddress = exports.isBlockedAddress = exports.getValidAddress = exports.isValidAddress = void 0;
4
4
  var keccak_js_1 = require("ethereum-cryptography/keccak.js");
5
5
  var utils_js_1 = require("ethereum-cryptography/utils.js");
6
6
  var validators_1 = require("../validators");
7
7
  var object_1 = require("../validators/object");
8
8
  var constants_1 = require("../constants");
9
+ var address_1 = require("../solana/address");
10
+ var types_1 = require("../solana/types");
9
11
  /**
10
12
  * Private helper function to validate and trim an address
11
13
  * @param address The address to validate and trim
@@ -85,4 +87,49 @@ var toChecksumAddress = function (address) {
85
87
  return checksumAddress;
86
88
  };
87
89
  exports.toChecksumAddress = toChecksumAddress;
90
+ /**
91
+ * Validates an EVM address and returns it in checksummed format.
92
+ * @param address The address to validate
93
+ * @returns The checksummed address or undefined if invalid
94
+ */
95
+ var validateAndChecksumAddress = function (address) {
96
+ var validAddress = (0, exports.getValidAddress)(address);
97
+ return validAddress ? (0, exports.toChecksumAddress)(validAddress) : undefined;
98
+ };
99
+ exports.validateAndChecksumAddress = validateAndChecksumAddress;
100
+ /**
101
+ * Validates an address for both EVM and Solana chains.
102
+ * For EVM addresses, returns checksummed format.
103
+ * For Solana addresses, returns the Base58 address as-is.
104
+ *
105
+ * When chainId is explicitly provided, validation is strict:
106
+ * - Solana chainId → only Solana validation
107
+ * - Non-Solana chainId → only EVM validation
108
+ * When chainId is omitted, EVM is tried first with Solana fallback.
109
+ *
110
+ * @param address The address to validate
111
+ * @param chainId Optional chain ID to determine address type
112
+ * @returns The validated address or undefined if invalid
113
+ */
114
+ var validateAddress = function (address, chainId) {
115
+ var solanaChainIds = Object.values(types_1.SOLANA_CHAIN_IDS);
116
+ // Explicit Solana chainId → validate ONLY as Solana
117
+ if (chainId !== undefined && chainId !== null && solanaChainIds.includes(chainId)) {
118
+ return (0, address_1.getValidSolanaAddress)(address) || undefined;
119
+ }
120
+ // Explicit non-Solana chainId → validate ONLY as EVM
121
+ if (chainId !== undefined && chainId !== null) {
122
+ return (0, exports.validateAndChecksumAddress)(address);
123
+ }
124
+ // No chainId → try EVM first, then Solana fallback
125
+ var validEvmAddress = (0, exports.validateAndChecksumAddress)(address);
126
+ if (validEvmAddress) {
127
+ return validEvmAddress;
128
+ }
129
+ if ((0, address_1.isSolanaAddress)(address)) {
130
+ return (0, address_1.getValidSolanaAddress)(address) || undefined;
131
+ }
132
+ return undefined;
133
+ };
134
+ exports.validateAddress = validateAddress;
88
135
  //# sourceMappingURL=address.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ERC-8021 Builder Code Extraction
3
+ *
4
+ * Extracts builder codes from transaction calldata by parsing the ERC-8021
5
+ * data suffix. The suffix is appended to the end of calldata and parsed
6
+ * backwards:
7
+ *
8
+ * [original calldata] [schemaData] [schemaId (1 byte)] [ercMarker (16 bytes)]
9
+ *
10
+ * Schema 0 (canonical registry):
11
+ * [codes (variable ASCII)] [codesLength (1 byte)] [schemaId 0x00] [ercMarker]
12
+ *
13
+ * Schema 1 (custom registry):
14
+ * [registryAddress (20 bytes)] [chainId (variable)] [chainIdLength (1 byte)]
15
+ * [codes (variable ASCII)] [codesLength (1 byte)] [schemaId 0x01] [ercMarker]
16
+ *
17
+ * - ercMarker: 0x80218021802180218021802180218021 (16 bytes)
18
+ * - codes: ASCII-encoded entity codes delimited by 0x2C (comma)
19
+ *
20
+ * @see https://docs.base.org/base-chain/builder-codes/builder-codes
21
+ * @see https://www.erc8021.com/
22
+ */
23
+ /**
24
+ * Extract builder codes from transaction calldata by parsing the ERC-8021 suffix.
25
+ *
26
+ * @param data - The transaction calldata hex string (with or without 0x prefix)
27
+ * @returns A comma-separated string of builder codes (e.g. "uniswap,base"), or undefined if no valid ERC-8021 suffix is found
28
+ */
29
+ export declare function extractBuilderCodes(data: string | undefined | null): string | undefined;
30
+ //# sourceMappingURL=builderCode.d.ts.map
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * ERC-8021 Builder Code Extraction
4
+ *
5
+ * Extracts builder codes from transaction calldata by parsing the ERC-8021
6
+ * data suffix. The suffix is appended to the end of calldata and parsed
7
+ * backwards:
8
+ *
9
+ * [original calldata] [schemaData] [schemaId (1 byte)] [ercMarker (16 bytes)]
10
+ *
11
+ * Schema 0 (canonical registry):
12
+ * [codes (variable ASCII)] [codesLength (1 byte)] [schemaId 0x00] [ercMarker]
13
+ *
14
+ * Schema 1 (custom registry):
15
+ * [registryAddress (20 bytes)] [chainId (variable)] [chainIdLength (1 byte)]
16
+ * [codes (variable ASCII)] [codesLength (1 byte)] [schemaId 0x01] [ercMarker]
17
+ *
18
+ * - ercMarker: 0x80218021802180218021802180218021 (16 bytes)
19
+ * - codes: ASCII-encoded entity codes delimited by 0x2C (comma)
20
+ *
21
+ * @see https://docs.base.org/base-chain/builder-codes/builder-codes
22
+ * @see https://www.erc8021.com/
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.extractBuilderCodes = extractBuilderCodes;
26
+ /** The 16-byte ERC-8021 marker appended at the very end of calldata */
27
+ var ERC_MARKER = "80218021802180218021802180218021";
28
+ /** Length of the ERC marker in hex characters (16 bytes = 32 hex chars) */
29
+ var ERC_MARKER_HEX_LENGTH = 32;
30
+ /** Supported schema IDs */
31
+ var SCHEMA_ID_CANONICAL = "00";
32
+ var SCHEMA_ID_CUSTOM_REGISTRY = "01";
33
+ /** Comma delimiter (0x2C) used to separate multiple codes */
34
+ var COMMA_BYTE = 0x2c;
35
+ /**
36
+ * Decode the codes field from a hex string into a comma-separated string.
37
+ * Validates that all bytes are printable ASCII (0x20–0x7E).
38
+ *
39
+ * @param codesHex - Hex string of the codes field
40
+ * @returns Comma-separated builder codes string, or undefined if invalid
41
+ */
42
+ function decodeCodes(codesHex) {
43
+ var bytes = [];
44
+ for (var i = 0; i < codesHex.length; i += 2) {
45
+ var byte = parseInt(codesHex.slice(i, i + 2), 16);
46
+ // Reject NaN (invalid hex), non-printable or non-ASCII bytes
47
+ if (isNaN(byte) || byte < 0x20 || byte > 0x7e) {
48
+ return undefined;
49
+ }
50
+ bytes.push(byte);
51
+ }
52
+ // Split on comma delimiter and decode each code as ASCII
53
+ var codes = [];
54
+ var current = [];
55
+ for (var _i = 0, bytes_1 = bytes; _i < bytes_1.length; _i++) {
56
+ var byte = bytes_1[_i];
57
+ if (byte === COMMA_BYTE) {
58
+ if (current.length > 0) {
59
+ codes.push(String.fromCharCode.apply(String, current));
60
+ current = [];
61
+ }
62
+ }
63
+ else {
64
+ current.push(byte);
65
+ }
66
+ }
67
+ // Push the last code segment
68
+ if (current.length > 0) {
69
+ codes.push(String.fromCharCode.apply(String, current));
70
+ }
71
+ return codes.length > 0 ? codes.join(",") : undefined;
72
+ }
73
+ /**
74
+ * Read codesLength and codes from the hex string ending at the given position.
75
+ *
76
+ * @param hex - Full hex string (lowercase, no 0x prefix)
77
+ * @param endPos - Hex char position where codesLength byte ends (i.e. start of schemaId)
78
+ * @returns Object with decoded codes string and the hex char position where codes start, or undefined
79
+ */
80
+ function readCodes(hex, endPos) {
81
+ // Read codesLength (1 byte before endPos)
82
+ var codesLengthStart = endPos - 2;
83
+ if (codesLengthStart < 0) {
84
+ return undefined;
85
+ }
86
+ var codesLength = parseInt(hex.slice(codesLengthStart, endPos), 16);
87
+ if (codesLength === 0 || isNaN(codesLength)) {
88
+ return undefined;
89
+ }
90
+ // Read the codes field
91
+ var codesHexLength = codesLength * 2;
92
+ var codesStart = codesLengthStart - codesHexLength;
93
+ if (codesStart < 0) {
94
+ return undefined;
95
+ }
96
+ var codesHex = hex.slice(codesStart, codesLengthStart);
97
+ var codes = decodeCodes(codesHex);
98
+ if (!codes) {
99
+ return undefined;
100
+ }
101
+ return { codes: codes, codesStart: codesStart };
102
+ }
103
+ /**
104
+ * Extract builder codes from transaction calldata by parsing the ERC-8021 suffix.
105
+ *
106
+ * @param data - The transaction calldata hex string (with or without 0x prefix)
107
+ * @returns A comma-separated string of builder codes (e.g. "uniswap,base"), or undefined if no valid ERC-8021 suffix is found
108
+ */
109
+ function extractBuilderCodes(data) {
110
+ if (!data || typeof data !== "string") {
111
+ return undefined;
112
+ }
113
+ // Normalize: remove 0x prefix and work with lowercase hex
114
+ var hex = data.startsWith("0x") || data.startsWith("0X")
115
+ ? data.slice(2).toLowerCase()
116
+ : data.toLowerCase();
117
+ // Minimum suffix: 1+ byte codes + 1 byte codesLength + 1 byte schemaId + 16 bytes marker = 19 bytes = 38 hex chars
118
+ if (hex.length < 38) {
119
+ return undefined;
120
+ }
121
+ // Step 1: Check last 16 bytes for ERC marker
122
+ var markerStart = hex.length - ERC_MARKER_HEX_LENGTH;
123
+ var marker = hex.slice(markerStart);
124
+ if (marker !== ERC_MARKER) {
125
+ return undefined;
126
+ }
127
+ // Step 2: Read schemaId (1 byte before the marker)
128
+ var schemaIdStart = markerStart - 2;
129
+ if (schemaIdStart < 0) {
130
+ return undefined;
131
+ }
132
+ var schemaId = hex.slice(schemaIdStart, markerStart);
133
+ // Step 3: Parse based on schemaId
134
+ if (schemaId === SCHEMA_ID_CANONICAL || schemaId === SCHEMA_ID_CUSTOM_REGISTRY) {
135
+ // Both Schema 0 and Schema 1 have codes in the same position
136
+ // (immediately before schemaId when parsing backwards)
137
+ var result = readCodes(hex, schemaIdStart);
138
+ return result === null || result === void 0 ? void 0 : result.codes;
139
+ }
140
+ // Unknown schema - cannot parse
141
+ return undefined;
142
+ }
143
+ //# sourceMappingURL=builderCode.js.map
@@ -1,5 +1,6 @@
1
1
  export * from "./address";
2
2
  export * from "./base";
3
+ export * from "./builderCode";
3
4
  export * from "./converter";
4
5
  export * from "./generate";
5
6
  export * from "./hash";
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./address"), exports);
18
18
  __exportStar(require("./base"), exports);
19
+ __exportStar(require("./builderCode"), exports);
19
20
  __exportStar(require("./converter"), exports);
20
21
  __exportStar(require("./generate"), exports);
21
22
  __exportStar(require("./hash"), exports);
@@ -1,2 +1,2 @@
1
- export declare const version = "1.27.0";
1
+ export declare const version = "1.28.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
4
  // This file is auto-generated by scripts/update-version.js during npm version
5
5
  // Do not edit manually - it will be overwritten
6
- exports.version = '1.27.0';
6
+ exports.version = '1.28.0';
7
7
  //# sourceMappingURL=version.js.map
@@ -58,6 +58,7 @@ exports.WagmiEventHandler = void 0;
58
58
  var events_1 = require("../types/events");
59
59
  var logger_1 = require("../logger");
60
60
  var utils_1 = require("./utils");
61
+ var builderCode_1 = require("../utils/builderCode");
61
62
  /**
62
63
  * Built-in transaction fields that could collide with function args.
63
64
  * Defined at module level to avoid recreating on every method call.
@@ -72,6 +73,7 @@ var RESERVED_FIELDS = new Set([
72
73
  "transactionHash",
73
74
  "function_name",
74
75
  "function_args",
76
+ "builder_codes",
75
77
  ]);
76
78
  /**
77
79
  * Clean up old entries from a Set to prevent memory leaks.
@@ -386,7 +388,7 @@ var WagmiEventHandler = /** @class */ (function () {
386
388
  chainId: chainId,
387
389
  blockNumber: (_a = receipt === null || receipt === void 0 ? void 0 : receipt.blockNumber) === null || _a === void 0 ? void 0 : _a.toString(),
388
390
  });
389
- this.formo.transaction(__assign(__assign(__assign(__assign(__assign({ status: txStatus, chainId: chainId || 0, address: address, transactionHash: transactionHash }, ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.data) && { data: pendingTx.data })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.to) && { to: pendingTx.to })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.value) && { value: pendingTx.value })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_name) && { function_name: pendingTx.function_name })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_args) && { function_args: pendingTx.function_args })),
391
+ this.formo.transaction(__assign(__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 })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.builder_codes) && { builder_codes: pendingTx.builder_codes })),
390
392
  // Spread function args as additional properties (only colliding keys are prefixed)
391
393
  pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.safeFunctionArgs);
392
394
  // Clean up the pending transaction after confirmation
@@ -540,7 +542,7 @@ var WagmiEventHandler = /** @class */ (function () {
540
542
  var value = (_a = variables.value) === null || _a === void 0 ? void 0 : _a.toString();
541
543
  if (mutationType === "writeContract") {
542
544
  // For writeContract, extract function info and encode data
543
- var abi = variables.abi, fnName = variables.functionName, args = variables.args, contractAddress = variables.address;
545
+ var abi = variables.abi, fnName = variables.functionName, args = variables.args, contractAddress = variables.address, dataSuffix = variables.dataSuffix;
544
546
  to = contractAddress;
545
547
  function_name = fnName;
546
548
  if (abi && fnName) {
@@ -549,7 +551,8 @@ var WagmiEventHandler = /** @class */ (function () {
549
551
  // Encode the function data synchronously if viem is available
550
552
  var encodedData = (0, utils_1.encodeWriteContractData)(abi, fnName, args);
551
553
  if (encodedData) {
552
- data = encodedData;
554
+ // Include dataSuffix (ERC-8021 builder code) so extractBuilderCodes sees full calldata
555
+ data = (0, utils_1.concatCalldataWithSuffix)(encodedData, dataSuffix);
553
556
  logger_1.logger.debug("WagmiEventHandler: Encoded writeContract data", data.substring(0, 10));
554
557
  }
555
558
  }
@@ -560,14 +563,9 @@ var WagmiEventHandler = /** @class */ (function () {
560
563
  data = variables.data;
561
564
  to = variables.to;
562
565
  }
563
- logger_1.logger.info("WagmiEventHandler: Tracking transaction event", {
564
- status: status_2,
565
- mutationType: mutationType,
566
- address: userAddress,
567
- chainId: chainId,
568
- transactionHash: transactionHash,
569
- function_name: function_name,
570
- });
566
+ // Extract builder codes from transaction data (ERC-8021)
567
+ var builder_codes = (0, builderCode_1.extractBuilderCodes)(data);
568
+ logger_1.logger.info("WagmiEventHandler: Tracking transaction event", __assign({ status: status_2, mutationType: mutationType, address: userAddress, chainId: chainId, transactionHash: transactionHash, function_name: function_name }, (builder_codes && { builder_codes: builder_codes })));
571
569
  // Build safeFunctionArgs with collision handling and struct flattening
572
570
  var safeFunctionArgs = (0, utils_1.buildSafeFunctionArgs)(function_args, RESERVED_FIELDS);
573
571
  // Store transaction details for BROADCASTED status to use in CONFIRMED/REVERTED
@@ -575,7 +573,7 @@ var WagmiEventHandler = /** @class */ (function () {
575
573
  // Include the sender address to handle wallet switches between broadcast and confirmation
576
574
  if (status_2 === events_1.TransactionStatus.BROADCASTED && transactionHash) {
577
575
  var normalizedHash = transactionHash.toLowerCase();
578
- var txDetails = __assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
576
+ var txDetails = __assign(__assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (builder_codes && { builder_codes: builder_codes })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
579
577
  this.pendingTransactions.set(normalizedHash, txDetails);
580
578
  logger_1.logger.debug("WagmiEventHandler: Stored pending transaction for confirmation", {
581
579
  transactionHash: normalizedHash,
@@ -589,7 +587,7 @@ var WagmiEventHandler = /** @class */ (function () {
589
587
  }
590
588
  }
591
589
  }
592
- this.formo.transaction(__assign(__assign(__assign(__assign(__assign(__assign({ status: status_2, chainId: chainId || 0, address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })),
590
+ this.formo.transaction(__assign(__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 })), (builder_codes && { builder_codes: builder_codes })),
593
591
  // Spread function args as additional properties (only colliding keys are prefixed)
594
592
  safeFunctionArgs);
595
593
  }
@@ -647,7 +645,7 @@ var WagmiEventHandler = /** @class */ (function () {
647
645
  * Clean up all subscriptions
648
646
  */
649
647
  WagmiEventHandler.prototype.cleanup = function () {
650
- logger_1.logger.info("WagmiEventHandler: Cleaning up subscriptions");
648
+ logger_1.logger.debug("WagmiEventHandler: Cleaning up subscriptions");
651
649
  for (var _i = 0, _a = this.unsubscribers; _i < _a.length; _i++) {
652
650
  var unsubscribe = _a[_i];
653
651
  try {
@@ -661,7 +659,7 @@ var WagmiEventHandler = /** @class */ (function () {
661
659
  this.processedMutations.clear();
662
660
  this.processedQueries.clear();
663
661
  this.pendingTransactions.clear();
664
- logger_1.logger.info("WagmiEventHandler: Cleanup complete");
662
+ logger_1.logger.debug("WagmiEventHandler: Cleanup complete");
665
663
  };
666
664
  return WagmiEventHandler;
667
665
  }());
@@ -40,6 +40,11 @@ export interface AbiOutput {
40
40
  components?: AbiOutput[];
41
41
  internalType?: string;
42
42
  }
43
+ /**
44
+ * Concatenate encoded calldata with an optional ERC-8021 dataSuffix (e.g. builder codes).
45
+ * Uses viem's concatHex when available for correct hex handling.
46
+ */
47
+ export declare function concatCalldataWithSuffix(encodedData: string, dataSuffix: string | undefined): string;
43
48
  /**
44
49
  * Encode writeContract data using viem's encodeFunctionData
45
50
  *
@@ -7,6 +7,7 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.flattenObject = flattenObject;
10
+ exports.concatCalldataWithSuffix = concatCalldataWithSuffix;
10
11
  exports.encodeWriteContractData = encodeWriteContractData;
11
12
  exports.extractFunctionArgs = extractFunctionArgs;
12
13
  exports.buildSafeFunctionArgs = buildSafeFunctionArgs;
@@ -82,6 +83,7 @@ function tryLoadViem() {
82
83
  if (viem === null || viem === void 0 ? void 0 : viem.encodeFunctionData) {
83
84
  viemModule = {
84
85
  encodeFunctionData: viem.encodeFunctionData,
86
+ concatHex: viem.concatHex,
85
87
  };
86
88
  return viemModule;
87
89
  }
@@ -92,6 +94,24 @@ function tryLoadViem() {
92
94
  viemModule = null;
93
95
  return null;
94
96
  }
97
+ /**
98
+ * Concatenate encoded calldata with an optional ERC-8021 dataSuffix (e.g. builder codes).
99
+ * Uses viem's concatHex when available for correct hex handling.
100
+ */
101
+ function concatCalldataWithSuffix(encodedData, dataSuffix) {
102
+ if (!dataSuffix || dataSuffix === "0x") {
103
+ return encodedData;
104
+ }
105
+ var viem = tryLoadViem();
106
+ if (viem === null || viem === void 0 ? void 0 : viem.concatHex) {
107
+ return viem.concatHex([
108
+ encodedData,
109
+ dataSuffix,
110
+ ]);
111
+ }
112
+ var suffixHex = dataSuffix.replace(/^0x/i, "");
113
+ return "".concat(encodedData).concat(suffixHex);
114
+ }
95
115
  /**
96
116
  * Encode writeContract data using viem's encodeFunctionData
97
117
  *
@@ -1,9 +1,15 @@
1
1
  import { EIP6963ProviderDetail } from "mipd";
2
2
  import { Address, ChainID, Config, EIP1193Provider, IFormoAnalytics, IFormoEventContext, IFormoEventProperties, Options, SignatureStatus, TransactionStatus } from "./types";
3
+ import { SolanaManager } from "./solana/SolanaManager";
3
4
  export declare class FormoAnalytics implements IFormoAnalytics {
4
5
  readonly writeKey: string;
5
6
  options: Options;
6
- private _provider?;
7
+ private _chainState;
8
+ private _activeNamespace?;
9
+ private get _provider();
10
+ private set _provider(value);
11
+ private get _evmAddress();
12
+ private get _evmChainId();
7
13
  private _providerListenersMap;
8
14
  private session;
9
15
  private eventManager;
@@ -29,6 +35,11 @@ export declare class FormoAnalytics implements IFormoAnalytics {
29
35
  * Only initialized when options.wagmi is provided
30
36
  */
31
37
  private wagmiHandler?;
38
+ /**
39
+ * Solana integration manager for tracking Solana wallet events.
40
+ * Only initialized when options.solana is provided or via formo.solana.
41
+ */
42
+ private solanaManager?;
32
43
  /**
33
44
  * Flag indicating if Wagmi mode is enabled
34
45
  * When true, EIP-1193 provider wrapping is skipped
@@ -139,7 +150,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
139
150
  * @param {(...args: unknown[]) => void} callback
140
151
  * @returns {Promise<void>}
141
152
  */
142
- transaction({ status, chainId, address, data, to, value, transactionHash, function_name, function_args, }: {
153
+ transaction({ status, chainId, address, data, to, value, transactionHash, function_name, function_args, builder_codes, }: {
143
154
  status: TransactionStatus;
144
155
  chainId: ChainID;
145
156
  address: Address;
@@ -149,20 +160,38 @@ export declare class FormoAnalytics implements IFormoAnalytics {
149
160
  transactionHash?: string;
150
161
  function_name?: string;
151
162
  function_args?: Record<string, unknown>;
163
+ builder_codes?: string;
152
164
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
153
165
  /**
154
166
  * Emits an identify event with current wallet address and provider info.
155
- * @param {string} params.address
156
- * @param {string} params.userId
157
- * @param {string} params.rdns
158
- * @param {string} params.providerName
159
- * @param {IFormoEventProperties} properties
167
+ *
168
+ * @param {string} params.address - Wallet address
169
+ * @param {string} params.userId - External user ID
170
+ * @param {string} params.rdns - Provider reverse domain name
171
+ * @param {string} params.providerName - Provider display name
172
+ * @param {IFormoEventProperties} properties - Additional properties to include with the identify event
160
173
  * @param {IFormoEventContext} context
161
174
  * @param {(...args: unknown[]) => void} callback
162
175
  * @returns {Promise<void>}
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * // Basic identify
180
+ * formo.identify({ address: '0x...', userId: 'user123' });
181
+ *
182
+ * // With Privy user
183
+ * import { parsePrivyProperties } from '@formo/analytics';
184
+ * const { user } = usePrivy();
185
+ * if (user) {
186
+ * const { properties, wallets } = parsePrivyProperties(user);
187
+ * for (const wallet of wallets) {
188
+ * formo.identify({ address: wallet.address, userId: user.id }, properties);
189
+ * }
190
+ * }
191
+ * ```
163
192
  */
164
193
  identify(params?: {
165
- address?: Address;
194
+ address: Address;
166
195
  providerName?: string;
167
196
  userId?: string;
168
197
  rdns?: string;
@@ -258,6 +287,19 @@ export declare class FormoAnalytics implements IFormoAnalytics {
258
287
  get providers(): readonly EIP6963ProviderDetail[];
259
288
  private detectWallets;
260
289
  get provider(): EIP1193Provider | undefined;
290
+ /**
291
+ * Access the Solana integration manager.
292
+ * Lazily creates one if not already initialized.
293
+ *
294
+ * @example
295
+ * ```tsx
296
+ * formo.solana.setWallet(wallet);
297
+ * formo.solana.setConnection(connection);
298
+ * formo.solana.setCluster("devnet");
299
+ * formo.solana.syncWalletState();
300
+ * ```
301
+ */
302
+ get solana(): SolanaManager;
261
303
  private getAddress;
262
304
  private getAccounts;
263
305
  private getCurrentChainId;
@@ -298,11 +340,26 @@ export declare class FormoAnalytics implements IFormoAnalytics {
298
340
  */
299
341
  private handleProviderMismatch;
300
342
  /**
301
- * Helper method to validate and checksum an address
302
- * @param address The address to validate and checksum
303
- * @returns The checksummed address or undefined if invalid
343
+ * Determine which namespace a chainId belongs to.
344
+ */
345
+ private getNamespace;
346
+ /**
347
+ * Update per-chain state and sync the derived currentAddress/currentChainId.
348
+ * Accepts either a namespace string ('evm'/'solana') or a chainId number
349
+ * to resolve the namespace automatically. When a chainId number is passed,
350
+ * it is also stored as the namespace's chainId (unless explicitly overridden
351
+ * in the update object).
352
+ */
353
+ private setChainState;
354
+ /**
355
+ * Clear per-chain state for a given namespace (or chainId) and sync derived state.
356
+ */
357
+ private clearChainState;
358
+ /**
359
+ * Synchronize currentAddress/currentChainId from the active namespace.
360
+ * Last-connected-chain-wins: _activeNamespace takes precedence.
304
361
  */
305
- private validateAndChecksumAddress;
362
+ private syncDerivedState;
306
363
  /**
307
364
  * Helper method to clear the active provider state
308
365
  * Centralizes provider clearing logic for consistency