@formo/analytics 1.35.2 → 1.37.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.
- package/dist/cjs/src/FormoAnalytics.d.ts +79 -363
- package/dist/cjs/src/FormoAnalytics.js +368 -2234
- package/dist/cjs/src/event/EventManager.d.ts +3 -1
- package/dist/cjs/src/event/EventManager.js +5 -1
- package/dist/cjs/src/event/type.d.ts +1 -0
- package/dist/cjs/src/evm/EvmEventTracker.d.ts +175 -0
- package/dist/cjs/src/evm/EvmEventTracker.js +1030 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +132 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.js +348 -0
- package/dist/cjs/src/evm/EvmRequestTracker.d.ts +98 -0
- package/dist/cjs/src/evm/EvmRequestTracker.js +713 -0
- package/dist/cjs/src/evm/batch.d.ts +94 -0
- package/dist/cjs/src/evm/batch.js +130 -0
- package/dist/cjs/src/queue/EventQueue.d.ts +28 -0
- package/dist/cjs/src/queue/EventQueue.js +97 -19
- package/dist/cjs/src/queue/type.d.ts +1 -0
- package/dist/cjs/src/tracking/TrackingPolicy.d.ts +146 -0
- package/dist/cjs/src/tracking/TrackingPolicy.js +200 -0
- package/dist/cjs/src/validators/address.d.ts +1 -1
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +43 -0
- package/dist/cjs/src/wagmi/WagmiEventHandler.js +253 -2
- package/dist/cjs/src/wallet/WalletStateStore.d.ts +223 -0
- package/dist/cjs/src/wallet/WalletStateStore.js +515 -0
- package/dist/esm/src/FormoAnalytics.d.ts +79 -363
- package/dist/esm/src/FormoAnalytics.js +369 -2235
- package/dist/esm/src/event/EventManager.d.ts +3 -1
- package/dist/esm/src/event/EventManager.js +5 -1
- package/dist/esm/src/event/type.d.ts +1 -0
- package/dist/esm/src/evm/EvmEventTracker.d.ts +175 -0
- package/dist/esm/src/evm/EvmEventTracker.js +1027 -0
- package/dist/esm/src/evm/EvmProviderRegistry.d.ts +132 -0
- package/dist/esm/src/evm/EvmProviderRegistry.js +345 -0
- package/dist/esm/src/evm/EvmRequestTracker.d.ts +98 -0
- package/dist/esm/src/evm/EvmRequestTracker.js +710 -0
- package/dist/esm/src/evm/batch.d.ts +94 -0
- package/dist/esm/src/evm/batch.js +123 -0
- package/dist/esm/src/queue/EventQueue.d.ts +28 -0
- package/dist/esm/src/queue/EventQueue.js +97 -19
- package/dist/esm/src/queue/type.d.ts +1 -0
- package/dist/esm/src/tracking/TrackingPolicy.d.ts +146 -0
- package/dist/esm/src/tracking/TrackingPolicy.js +197 -0
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +43 -0
- package/dist/esm/src/wagmi/WagmiEventHandler.js +253 -2
- package/dist/esm/src/wallet/WalletStateStore.d.ts +223 -0
- package/dist/esm/src/wallet/WalletStateStore.js +512 -0
- package/dist/index.umd.min.js +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { getTimezone } from "../utils/timezone";
|
|
2
|
+
import { isLocalhost } from "../validators";
|
|
3
|
+
/**
|
|
4
|
+
* Every "may we track this?" decision, in one place.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `FormoAnalytics` so the rules can be read and tested without
|
|
7
|
+
* an SDK instance, and so option parsing happens once rather than being
|
|
8
|
+
* re-derived at each gate.
|
|
9
|
+
*/
|
|
10
|
+
var TrackingPolicy = /** @class */ (function () {
|
|
11
|
+
function TrackingPolicy(deps) {
|
|
12
|
+
this.deps = deps;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `tracking` as an options object, or null when it is absent, a boolean, or
|
|
16
|
+
* anything else. Every exclusion rule lives on the object form, so a null
|
|
17
|
+
* here means "no exclusions configured", not "excluded".
|
|
18
|
+
*/
|
|
19
|
+
TrackingPolicy.prototype.trackingOptions = function () {
|
|
20
|
+
var tracking = this.deps.options().tracking;
|
|
21
|
+
if (tracking === null ||
|
|
22
|
+
typeof tracking !== "object" ||
|
|
23
|
+
Array.isArray(tracking)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return tracking;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Visitor-level tracking suppression.
|
|
30
|
+
*
|
|
31
|
+
* True when the SDK must not persist any identity/session/chain state or
|
|
32
|
+
* send any events for this visitor: an explicit opt-out, or a
|
|
33
|
+
* jurisdiction/timezone/host/path exclusion. Public entry points that write
|
|
34
|
+
* state before reaching `shouldTrack()` (identify / connect / detect) check
|
|
35
|
+
* this first, so a suppressed visitor leaves no cookies or session state.
|
|
36
|
+
*/
|
|
37
|
+
TrackingPolicy.prototype.isTrackingSuppressed = function () {
|
|
38
|
+
return this.deps.hasOptedOut() || this.isEnvironmentExcluded();
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Whether the environment is excluded: the visitor's timezone, the current
|
|
42
|
+
* hostname, or the current pathname.
|
|
43
|
+
*
|
|
44
|
+
* Timezone is visitor-level and stable for the session. Host and path are
|
|
45
|
+
* current-page-level and transient, so a SPA navigating back to an allowed
|
|
46
|
+
* path resumes tracking for later actions.
|
|
47
|
+
*/
|
|
48
|
+
TrackingPolicy.prototype.isEnvironmentExcluded = function () {
|
|
49
|
+
return (this.isTimezoneExcluded() ||
|
|
50
|
+
this.isHostExcluded() ||
|
|
51
|
+
this.isPathExcluded());
|
|
52
|
+
};
|
|
53
|
+
/** Exact match against `tracking.excludeHosts`. */
|
|
54
|
+
TrackingPolicy.prototype.isHostExcluded = function () {
|
|
55
|
+
var tracking = this.trackingOptions();
|
|
56
|
+
if (!tracking)
|
|
57
|
+
return false;
|
|
58
|
+
if (typeof window === "undefined")
|
|
59
|
+
return false;
|
|
60
|
+
var _a = tracking.excludeHosts, excludeHosts = _a === void 0 ? [] : _a;
|
|
61
|
+
return excludeHosts.includes(window.location.hostname);
|
|
62
|
+
};
|
|
63
|
+
/** Exact match against `tracking.excludePaths`. */
|
|
64
|
+
TrackingPolicy.prototype.isPathExcluded = function () {
|
|
65
|
+
var tracking = this.trackingOptions();
|
|
66
|
+
if (!tracking)
|
|
67
|
+
return false;
|
|
68
|
+
if (typeof window === "undefined")
|
|
69
|
+
return false;
|
|
70
|
+
var _a = tracking.excludePaths, excludePaths = _a === void 0 ? [] : _a;
|
|
71
|
+
return excludePaths.includes(window.location.pathname);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Case-insensitive match of the browser-resolved timezone against
|
|
75
|
+
* `tracking.excludeTimezones`. Client-side and best-effort; see the option's
|
|
76
|
+
* own documentation.
|
|
77
|
+
*/
|
|
78
|
+
TrackingPolicy.prototype.isTimezoneExcluded = function () {
|
|
79
|
+
var tracking = this.trackingOptions();
|
|
80
|
+
if (!tracking)
|
|
81
|
+
return false;
|
|
82
|
+
var _a = tracking.excludeTimezones, excludeTimezones = _a === void 0 ? [] : _a;
|
|
83
|
+
if (excludeTimezones.length === 0)
|
|
84
|
+
return false;
|
|
85
|
+
var timezone = getTimezone();
|
|
86
|
+
if (!timezone)
|
|
87
|
+
return false;
|
|
88
|
+
var lowerTimezone = timezone.toLowerCase();
|
|
89
|
+
return excludeTimezones.some(function (tz) { return typeof tz === "string" && tz.toLowerCase() === lowerTimezone; });
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Whether the CURRENT PAGE is excluded, as opposed to the visitor.
|
|
93
|
+
*
|
|
94
|
+
* Host and path exclusions are transient: identity written on an allowed
|
|
95
|
+
* page must survive a visit to an excluded route, so callers that persist
|
|
96
|
+
* or restore identity skip the work here rather than purging.
|
|
97
|
+
*/
|
|
98
|
+
TrackingPolicy.prototype.isPageExcluded = function () {
|
|
99
|
+
return this.isHostExcluded() || this.isPathExcluded();
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Whether a persisted identity cookie should be actively purged, not merely
|
|
103
|
+
* left unwritten.
|
|
104
|
+
*
|
|
105
|
+
* Host and path exclusions are deliberately absent: they are transient
|
|
106
|
+
* current-page states, so a cookie legitimately written on an allowed page
|
|
107
|
+
* must survive a visit to an excluded route.
|
|
108
|
+
*/
|
|
109
|
+
TrackingPolicy.prototype.isPersistedIdentityPurgeRequired = function () {
|
|
110
|
+
return this.deps.hasOptedOut() || this.isTimezoneExcluded();
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Whether the chain in play is in `tracking.excludeChains`.
|
|
114
|
+
*
|
|
115
|
+
* Separate from `shouldTrack()` so `identify()` can ask *before* mutating
|
|
116
|
+
* identity state. `trackEvent()` drops an excluded event silently, but
|
|
117
|
+
* `identify()` marks the wallet as identified first, so without this an
|
|
118
|
+
* identify on an excluded chain is dedup-marked and then discarded, and the
|
|
119
|
+
* wallet never re-emits for the rest of the session even after switching to
|
|
120
|
+
* an allowed chain. On the Privy path that loses a whole cluster at once.
|
|
121
|
+
*/
|
|
122
|
+
TrackingPolicy.prototype.isChainExcluded = function (context) {
|
|
123
|
+
var tracking = this.trackingOptions();
|
|
124
|
+
if (!tracking)
|
|
125
|
+
return false;
|
|
126
|
+
var _a = tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
127
|
+
if (excludeChains.length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
return this.isChainRefused(excludeChains, context);
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* The shared chain rule, so `shouldTrack()` and `isChainExcluded()` cannot
|
|
133
|
+
* drift apart. Callers have already established that `excludeChains` is
|
|
134
|
+
* non-empty.
|
|
135
|
+
*
|
|
136
|
+
* Fails CLOSED on an unknown chain. `resolveChainIdForProvider` reports 0
|
|
137
|
+
* when it has never heard a chain from the signing wallet, and 0 is in no
|
|
138
|
+
* exclusion list, so treating it as "not excluded" would let through exactly
|
|
139
|
+
* the events an operator excluded: the wallet on the excluded chain is often
|
|
140
|
+
* the one we know least about. An explicit exclusion is a directive, so an
|
|
141
|
+
* unresolvable chain is refused.
|
|
142
|
+
*
|
|
143
|
+
* Keyed on 0 and deliberately NOT on `undefined`. 0 is the explicit "we
|
|
144
|
+
* asked and could not tell" marker. `undefined` means no chain state yet,
|
|
145
|
+
* which is a legitimate transient (the Privy path reconciles a Solana wallet
|
|
146
|
+
* through exactly that state), and refusing it would drop real events.
|
|
147
|
+
* `backfillActiveWallet()` never persists 0, so an unresolvable chain cannot
|
|
148
|
+
* leak into the central value and reach the unscoped events (page / track /
|
|
149
|
+
* identify) that fall back to it.
|
|
150
|
+
*/
|
|
151
|
+
TrackingPolicy.prototype.isChainRefused = function (excludeChains, context) {
|
|
152
|
+
var _a;
|
|
153
|
+
var chainToCheck = (_a = context === null || context === void 0 ? void 0 : context.chainId) !== null && _a !== void 0 ? _a : this.deps.currentChainId();
|
|
154
|
+
if (chainToCheck === 0)
|
|
155
|
+
return true;
|
|
156
|
+
if (chainToCheck === undefined)
|
|
157
|
+
return false;
|
|
158
|
+
return excludeChains.includes(chainToCheck);
|
|
159
|
+
};
|
|
160
|
+
/** Whether an event may be tracked at all, given consent and configuration. */
|
|
161
|
+
TrackingPolicy.prototype.shouldTrack = function (context) {
|
|
162
|
+
if (this.deps.hasOptedOut())
|
|
163
|
+
return false;
|
|
164
|
+
// An explicit boolean is the whole answer; no exclusions apply to it.
|
|
165
|
+
var tracking = this.deps.options().tracking;
|
|
166
|
+
if (typeof tracking === "boolean")
|
|
167
|
+
return tracking;
|
|
168
|
+
var configured = this.trackingOptions();
|
|
169
|
+
if (configured) {
|
|
170
|
+
if (this.isEnvironmentExcluded())
|
|
171
|
+
return false;
|
|
172
|
+
var _a = configured.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
173
|
+
if (excludeChains.length > 0 && this.isChainRefused(excludeChains, context)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
// Nothing configured: track everywhere except localhost.
|
|
179
|
+
return !isLocalhost();
|
|
180
|
+
};
|
|
181
|
+
/** Whether a wallet event kind is enabled for autocapture. Defaults to on. */
|
|
182
|
+
TrackingPolicy.prototype.isAutocaptureEnabled = function (eventType) {
|
|
183
|
+
var autocapture = this.deps.options().autocapture;
|
|
184
|
+
if (autocapture === undefined)
|
|
185
|
+
return true;
|
|
186
|
+
if (typeof autocapture === "boolean")
|
|
187
|
+
return autocapture;
|
|
188
|
+
if (autocapture !== null && typeof autocapture === "object") {
|
|
189
|
+
// Only an explicit false disables a kind.
|
|
190
|
+
return autocapture[eventType] !== false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
};
|
|
194
|
+
return TrackingPolicy;
|
|
195
|
+
}());
|
|
196
|
+
export { TrackingPolicy };
|
|
197
|
+
//# sourceMappingURL=TrackingPolicy.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "1.
|
|
1
|
+
export declare const version = "1.37.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/esm/src/version.js
CHANGED
|
@@ -96,6 +96,8 @@ export declare class WagmiEventHandler {
|
|
|
96
96
|
* broadcast to match the receipt against.
|
|
97
97
|
*/
|
|
98
98
|
private get pendingTransactions();
|
|
99
|
+
/** Broadcast batches awaiting `callsStatus`, shared like the map above. */
|
|
100
|
+
private get pendingBatches();
|
|
99
101
|
constructor(formoAnalytics: FormoAnalytics, wagmiConfig: WagmiConfig, queryClient?: QueryClient);
|
|
100
102
|
/**
|
|
101
103
|
* Set up listeners for wallet connection, disconnection, and chain changes
|
|
@@ -228,6 +230,30 @@ export declare class WagmiEventHandler {
|
|
|
228
230
|
* Handle query cache events (transaction confirmations)
|
|
229
231
|
*/
|
|
230
232
|
private handleQueryEvent;
|
|
233
|
+
/**
|
|
234
|
+
* Settle a just-registered batch from a status query that already ran.
|
|
235
|
+
*
|
|
236
|
+
* Best-effort by design: the minimal QueryClient interface the SDK
|
|
237
|
+
* accepts is not guaranteed to expose cache lookup, and a missing
|
|
238
|
+
* `getAll` just means settlement waits for the next query event, which
|
|
239
|
+
* is where it normally comes from anyway.
|
|
240
|
+
*/
|
|
241
|
+
private settleFromCachedCallsStatus;
|
|
242
|
+
/**
|
|
243
|
+
* Settle an EIP-5792 batch from a `callsStatus` query.
|
|
244
|
+
*
|
|
245
|
+
* Only batches whose broadcast this SDK observed are settled: the batch id
|
|
246
|
+
* must be in `pendingBatches`, for the same reason receipt queries are
|
|
247
|
+
* gated on an observed hash - queries are visible to any code sharing the
|
|
248
|
+
* QueryClient, and emitting for an id we never saw broadcast would let a
|
|
249
|
+
* forged query invent transactions.
|
|
250
|
+
*
|
|
251
|
+
* Outcome semantics are shared with the EIP-1193 path (`src/evm/batch.ts`):
|
|
252
|
+
* per-call receipts outrank the batch verdict, an atomic batch's single
|
|
253
|
+
* receipt reaches every call, and a 600 leaves receipt-less calls
|
|
254
|
+
* unsettled rather than guessed.
|
|
255
|
+
*/
|
|
256
|
+
private handleCallsStatusQuery;
|
|
231
257
|
/**
|
|
232
258
|
* Handle waitForTransactionReceipt query completion
|
|
233
259
|
* Emits CONFIRMED or REVERTED transaction status
|
|
@@ -245,6 +271,23 @@ export declare class WagmiEventHandler {
|
|
|
245
271
|
* Handle transaction mutations (sendTransaction, writeContract)
|
|
246
272
|
*/
|
|
247
273
|
private handleTransactionMutation;
|
|
274
|
+
/**
|
|
275
|
+
* One `transaction` event per call in an EIP-5792 batch, wagmi path.
|
|
276
|
+
*
|
|
277
|
+
* Mirrors `EvmRequestTracker.trackBatchedCalls` exactly: the CALL is the
|
|
278
|
+
* unit of attribution, so each call gets its own STARTED at pending and
|
|
279
|
+
* BROADCASTED (with `batch_id`) when the wallet returns an id. The batch's
|
|
280
|
+
* on-chain outcome arrives through the `callsStatus` query, handled in
|
|
281
|
+
* `handleCallsStatusQuery`.
|
|
282
|
+
*
|
|
283
|
+
* Rejection matches the 1193 path's rule: only a user rejection (4001
|
|
284
|
+
* anywhere in the error chain) marks the calls rejected - one dismissal
|
|
285
|
+
* dismisses the whole prompt, so every call in it is rejected, and
|
|
286
|
+
* reporting only the first would undercount. Any other error (a wallet
|
|
287
|
+
* without EIP-5792 support, a transport failure) emits nothing further:
|
|
288
|
+
* inventing a rejection the user never made would be worse.
|
|
289
|
+
*/
|
|
290
|
+
private handleSendCallsMutation;
|
|
248
291
|
/**
|
|
249
292
|
* Get the current Wagmi state
|
|
250
293
|
* Supports both getState() method and direct state property access
|
|
@@ -54,6 +54,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
54
54
|
};
|
|
55
55
|
import { SignatureStatus, TransactionStatus } from "../types/events";
|
|
56
56
|
import { logger } from "../logger";
|
|
57
|
+
import { readBatchId, readBatchStatusCode, readBatchChainId, batchCallOutcome, batchReceiptForCall, } from "../evm/batch";
|
|
57
58
|
import { encodeWriteContractData, concatCalldataWithSuffix, extractFunctionArgs, buildSafeFunctionArgs, } from "./utils";
|
|
58
59
|
/**
|
|
59
60
|
* Built-in transaction fields that could collide with function args.
|
|
@@ -196,6 +197,25 @@ var ownerKey = function (writeKey, config) {
|
|
|
196
197
|
}
|
|
197
198
|
return "".concat(writeKey, ":").concat(id);
|
|
198
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Was this mutation error the user dismissing the wallet prompt?
|
|
202
|
+
*
|
|
203
|
+
* Matches the EIP-1193 path's rule (code 4001), but a wagmi mutation error
|
|
204
|
+
* arrives wrapped: viem nests the RPC error under `cause`, sometimes twice.
|
|
205
|
+
* Walk the chain rather than trusting the top level, and accept viem's
|
|
206
|
+
* `UserRejectedRequestError` by name for wallets that map the rejection to a
|
|
207
|
+
* typed error without preserving the numeric code.
|
|
208
|
+
*/
|
|
209
|
+
function isUserRejection(error) {
|
|
210
|
+
var cursor = error;
|
|
211
|
+
for (var depth = 0; cursor && depth < 5; depth++) {
|
|
212
|
+
if (cursor.code === 4001 || cursor.name === "UserRejectedRequestError") {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
cursor = cursor.cause;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
199
219
|
/**
|
|
200
220
|
* Pending transactions, shared per destination rather than per handler.
|
|
201
221
|
*
|
|
@@ -205,6 +225,7 @@ var ownerKey = function (writeKey, config) {
|
|
|
205
225
|
* broadcast to match the receipt against.
|
|
206
226
|
*/
|
|
207
227
|
var pendingTransactionsByDestination = new Map();
|
|
228
|
+
var pendingBatchesByDestination = new Map();
|
|
208
229
|
/**
|
|
209
230
|
* Pending records outlive a rebuild by the same grace period as the markers.
|
|
210
231
|
* The ordinary rebuild is cleanup THEN remount, so dropping them the instant
|
|
@@ -219,6 +240,7 @@ function schedulePendingTransactionExpiry(key) {
|
|
|
219
240
|
var timer = setTimeout(function () {
|
|
220
241
|
pendingTransactionExpiry.delete(key);
|
|
221
242
|
pendingTransactionsByDestination.delete(key);
|
|
243
|
+
pendingBatchesByDestination.delete(key);
|
|
222
244
|
}, MARKER_GRACE_MS);
|
|
223
245
|
(_b = (_a = timer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
224
246
|
pendingTransactionExpiry.set(key, timer);
|
|
@@ -285,6 +307,7 @@ export function __resetSeededWallet() {
|
|
|
285
307
|
pendingTransactionExpiry.forEach(function (t) { return clearTimeout(t); });
|
|
286
308
|
pendingTransactionExpiry.clear();
|
|
287
309
|
pendingTransactionsByDestination.clear();
|
|
310
|
+
pendingBatchesByDestination.clear();
|
|
288
311
|
announcedConnections.clear();
|
|
289
312
|
liveHandlers.clear();
|
|
290
313
|
markerExpiry.forEach(function (timer) { return clearTimeout(timer); });
|
|
@@ -497,6 +520,21 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
497
520
|
enumerable: false,
|
|
498
521
|
configurable: true
|
|
499
522
|
});
|
|
523
|
+
Object.defineProperty(WagmiEventHandler.prototype, "pendingBatches", {
|
|
524
|
+
/** Broadcast batches awaiting `callsStatus`, shared like the map above. */
|
|
525
|
+
get: function () {
|
|
526
|
+
var _a;
|
|
527
|
+
var key = (_a = this.ownerKey) !== null && _a !== void 0 ? _a : "";
|
|
528
|
+
var map = pendingBatchesByDestination.get(key);
|
|
529
|
+
if (!map) {
|
|
530
|
+
map = new Map();
|
|
531
|
+
pendingBatchesByDestination.set(key, map);
|
|
532
|
+
}
|
|
533
|
+
return map;
|
|
534
|
+
},
|
|
535
|
+
enumerable: false,
|
|
536
|
+
configurable: true
|
|
537
|
+
});
|
|
500
538
|
/**
|
|
501
539
|
* Set up listeners for wallet connection, disconnection, and chain changes
|
|
502
540
|
*/
|
|
@@ -1449,8 +1487,23 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
1449
1487
|
return;
|
|
1450
1488
|
}
|
|
1451
1489
|
var queryType = queryKey[0];
|
|
1452
|
-
// Only
|
|
1453
|
-
|
|
1490
|
+
// Only the two query families that settle something we broadcast:
|
|
1491
|
+
// waitForTransactionReceipt for single transactions, callsStatus for
|
|
1492
|
+
// EIP-5792 batches (both useCallsStatus and useWaitForCallsStatus share
|
|
1493
|
+
// the 'callsStatus' key, in wagmi 2 and 3 alike).
|
|
1494
|
+
if (queryType !== "waitForTransactionReceipt" && queryType !== "callsStatus") {
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
// Batch settlement dedupes on the pending-batch registry, NOT on
|
|
1498
|
+
// processedQueries: settling deletes the registration, so a duplicate
|
|
1499
|
+
// delivery finds nothing to do. A processed-key here would be worse
|
|
1500
|
+
// than redundant - the status query can complete BEFORE the sendCalls
|
|
1501
|
+
// mutation registers the batch (TanStack dispatches a mutation's
|
|
1502
|
+
// success state after its onSuccess callbacks, and apps await the
|
|
1503
|
+
// status inside onSuccess), and a key recorded on that early skip
|
|
1504
|
+
// would block every refetch from ever settling the batch.
|
|
1505
|
+
if (queryType === "callsStatus") {
|
|
1506
|
+
this.handleCallsStatusQuery(query);
|
|
1454
1507
|
return;
|
|
1455
1508
|
}
|
|
1456
1509
|
var state = query.state;
|
|
@@ -1483,6 +1536,111 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
1483
1536
|
// Clean up old processed queries to prevent memory leaks
|
|
1484
1537
|
cleanupOldEntries(this.processedQueries);
|
|
1485
1538
|
};
|
|
1539
|
+
/**
|
|
1540
|
+
* Settle a just-registered batch from a status query that already ran.
|
|
1541
|
+
*
|
|
1542
|
+
* Best-effort by design: the minimal QueryClient interface the SDK
|
|
1543
|
+
* accepts is not guaranteed to expose cache lookup, and a missing
|
|
1544
|
+
* `getAll` just means settlement waits for the next query event, which
|
|
1545
|
+
* is where it normally comes from anyway.
|
|
1546
|
+
*/
|
|
1547
|
+
WagmiEventHandler.prototype.settleFromCachedCallsStatus = function (batchId) {
|
|
1548
|
+
var _a, _b, _c;
|
|
1549
|
+
try {
|
|
1550
|
+
var cache = (_a = this.queryClient) === null || _a === void 0 ? void 0 : _a.getQueryCache();
|
|
1551
|
+
var queries = (_b = cache === null || cache === void 0 ? void 0 : cache.getAll) === null || _b === void 0 ? void 0 : _b.call(cache);
|
|
1552
|
+
if (!Array.isArray(queries))
|
|
1553
|
+
return;
|
|
1554
|
+
for (var _i = 0, queries_1 = queries; _i < queries_1.length; _i++) {
|
|
1555
|
+
var query = queries_1[_i];
|
|
1556
|
+
var key = query === null || query === void 0 ? void 0 : query.queryKey;
|
|
1557
|
+
if (Array.isArray(key) &&
|
|
1558
|
+
key[0] === "callsStatus" &&
|
|
1559
|
+
((_c = key[1]) === null || _c === void 0 ? void 0 : _c.id) === batchId) {
|
|
1560
|
+
this.handleCallsStatusQuery(query);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
catch (error) {
|
|
1565
|
+
logger.debug("WagmiEventHandler: cached callsStatus scan failed", error);
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
/**
|
|
1569
|
+
* Settle an EIP-5792 batch from a `callsStatus` query.
|
|
1570
|
+
*
|
|
1571
|
+
* Only batches whose broadcast this SDK observed are settled: the batch id
|
|
1572
|
+
* must be in `pendingBatches`, for the same reason receipt queries are
|
|
1573
|
+
* gated on an observed hash - queries are visible to any code sharing the
|
|
1574
|
+
* QueryClient, and emitting for an id we never saw broadcast would let a
|
|
1575
|
+
* forged query invent transactions.
|
|
1576
|
+
*
|
|
1577
|
+
* Outcome semantics are shared with the EIP-1193 path (`src/evm/batch.ts`):
|
|
1578
|
+
* per-call receipts outrank the batch verdict, an atomic batch's single
|
|
1579
|
+
* receipt reaches every call, and a 600 leaves receipt-less calls
|
|
1580
|
+
* unsettled rather than guessed.
|
|
1581
|
+
*/
|
|
1582
|
+
WagmiEventHandler.prototype.handleCallsStatusQuery = function (query) {
|
|
1583
|
+
var _this = this;
|
|
1584
|
+
var _a;
|
|
1585
|
+
if (!this.formo.isAutocaptureEnabled("transaction")) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
var queryKey = query.queryKey;
|
|
1589
|
+
// Query key format: ['callsStatus', { id, ... }]
|
|
1590
|
+
var params = queryKey[1];
|
|
1591
|
+
var batchId = params === null || params === void 0 ? void 0 : params.id;
|
|
1592
|
+
if (!batchId) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
var pending = this.pendingBatches.get(batchId);
|
|
1596
|
+
if (!pending) {
|
|
1597
|
+
logger.debug("WagmiEventHandler: unobserved batch", { batchId: batchId });
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
var state = query.state;
|
|
1601
|
+
if (state.status !== "success" || !state.data) {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
try {
|
|
1605
|
+
var res_1 = state.data;
|
|
1606
|
+
var code_1 = readBatchStatusCode(res_1);
|
|
1607
|
+
// Below 200 the batch is still pending; the query will update again.
|
|
1608
|
+
if (code_1 === undefined || code_1 < 200) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
logger.debug("WagmiEventHandler: batch settled", { batchId: batchId, code: code_1 });
|
|
1612
|
+
// An explicit mutation chain is authoritative. Otherwise prefer the
|
|
1613
|
+
// chain the settlement result names - EIP-5792 v2 reports where the
|
|
1614
|
+
// batch actually landed - over one inferred from the connection at
|
|
1615
|
+
// broadcast, which goes stale if the wallet moves chains while the
|
|
1616
|
+
// prompt is up. Same precedence as the single-transaction receipt
|
|
1617
|
+
// path.
|
|
1618
|
+
var settledChainId_1 = pending.chainIdWasExplicit
|
|
1619
|
+
? pending.chainId
|
|
1620
|
+
: (_a = readBatchChainId(res_1)) !== null && _a !== void 0 ? _a : pending.chainId;
|
|
1621
|
+
pending.calls.forEach(function (call, index) {
|
|
1622
|
+
var receipt = batchReceiptForCall(res_1, index, pending.calls.length);
|
|
1623
|
+
var outcome = batchCallOutcome(code_1, receipt);
|
|
1624
|
+
// 600 means SOME calls reverted, so a call with no receipt of its
|
|
1625
|
+
// own has not been decided. Reporting it either way would invent a
|
|
1626
|
+
// result; leaving it unsettled is the honest answer.
|
|
1627
|
+
if (outcome === undefined)
|
|
1628
|
+
return;
|
|
1629
|
+
_this.formo.transaction(__assign(__assign(__assign(__assign({ status: outcome, chainId: settledChainId_1 || 0, address: pending.address }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), ((receipt === null || receipt === void 0 ? void 0 : receipt.transactionHash)
|
|
1630
|
+
? { transactionHash: receipt.transactionHash }
|
|
1631
|
+
: {})), {
|
|
1632
|
+
batch_size: pending.calls.length,
|
|
1633
|
+
batch_index: index,
|
|
1634
|
+
batch_id: batchId,
|
|
1635
|
+
});
|
|
1636
|
+
});
|
|
1637
|
+
// Settled; a later refetch of the same query must not re-emit.
|
|
1638
|
+
this.pendingBatches.delete(batchId);
|
|
1639
|
+
}
|
|
1640
|
+
catch (error) {
|
|
1641
|
+
logger.error("WagmiEventHandler: callsStatus error:", error);
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1486
1644
|
/**
|
|
1487
1645
|
* Handle waitForTransactionReceipt query completion
|
|
1488
1646
|
* Emits CONFIRMED or REVERTED transaction status
|
|
@@ -1612,6 +1770,13 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
1612
1770
|
if (mutationType === "sendTransaction" || mutationType === "writeContract") {
|
|
1613
1771
|
this.handleTransactionMutation(mutationType, mutation);
|
|
1614
1772
|
}
|
|
1773
|
+
// Handle EIP-5792 batch mutations (useSendCalls). Absent this branch,
|
|
1774
|
+
// wagmi-mode apps captured nothing for a batch: the EIP-1193 request
|
|
1775
|
+
// wrapper that handles `wallet_sendCalls` is never installed in wagmi
|
|
1776
|
+
// mode, so the mutation was the only place the batch was visible at all.
|
|
1777
|
+
if (mutationType === "sendCalls") {
|
|
1778
|
+
this.handleSendCallsMutation(mutation);
|
|
1779
|
+
}
|
|
1615
1780
|
// Clean up old processed mutations to prevent memory leaks
|
|
1616
1781
|
cleanupOldEntries(this.processedMutations);
|
|
1617
1782
|
};
|
|
@@ -1801,6 +1966,92 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
1801
1966
|
logger.error("WagmiEventHandler: Error handling transaction mutation:", error);
|
|
1802
1967
|
}
|
|
1803
1968
|
};
|
|
1969
|
+
/**
|
|
1970
|
+
* One `transaction` event per call in an EIP-5792 batch, wagmi path.
|
|
1971
|
+
*
|
|
1972
|
+
* Mirrors `EvmRequestTracker.trackBatchedCalls` exactly: the CALL is the
|
|
1973
|
+
* unit of attribution, so each call gets its own STARTED at pending and
|
|
1974
|
+
* BROADCASTED (with `batch_id`) when the wallet returns an id. The batch's
|
|
1975
|
+
* on-chain outcome arrives through the `callsStatus` query, handled in
|
|
1976
|
+
* `handleCallsStatusQuery`.
|
|
1977
|
+
*
|
|
1978
|
+
* Rejection matches the 1193 path's rule: only a user rejection (4001
|
|
1979
|
+
* anywhere in the error chain) marks the calls rejected - one dismissal
|
|
1980
|
+
* dismisses the whole prompt, so every call in it is rejected, and
|
|
1981
|
+
* reporting only the first would undercount. Any other error (a wallet
|
|
1982
|
+
* without EIP-5792 support, a transport failure) emits nothing further:
|
|
1983
|
+
* inventing a rejection the user never made would be worse.
|
|
1984
|
+
*/
|
|
1985
|
+
WagmiEventHandler.prototype.handleSendCallsMutation = function (mutation) {
|
|
1986
|
+
var _this = this;
|
|
1987
|
+
if (!this.formo.isAutocaptureEnabled("transaction")) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
var state = mutation.state;
|
|
1991
|
+
var variables = state.variables || {};
|
|
1992
|
+
var rawCalls = Array.isArray(variables.calls) ? variables.calls : [];
|
|
1993
|
+
if (rawCalls.length === 0) {
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
// Same resolution order as single transactions: an explicit per-call
|
|
1997
|
+
// value beats the tracked connection.
|
|
1998
|
+
var explicitChainId = normalizeChainId(variables.chainId);
|
|
1999
|
+
var chainId = explicitChainId !== null && explicitChainId !== void 0 ? explicitChainId : this.trackingState.lastChainId;
|
|
2000
|
+
var accountAddress = resolveAccountAddress(variables.account);
|
|
2001
|
+
var userAddress = accountAddress || this.trackingState.lastAddress;
|
|
2002
|
+
if (!userAddress) {
|
|
2003
|
+
logger.warn("WagmiEventHandler: sendCalls without address");
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
try {
|
|
2007
|
+
var calls_1 = rawCalls.map(function (call) { return ({
|
|
2008
|
+
to: call === null || call === void 0 ? void 0 : call.to,
|
|
2009
|
+
value: (call === null || call === void 0 ? void 0 : call.value) !== undefined ? String(call.value) : undefined,
|
|
2010
|
+
data: call === null || call === void 0 ? void 0 : call.data,
|
|
2011
|
+
}); });
|
|
2012
|
+
var emitAll = function (status, extra) {
|
|
2013
|
+
calls_1.forEach(function (call, index) {
|
|
2014
|
+
_this.formo.transaction(__assign(__assign(__assign({ status: status, chainId: chainId || 0, address: userAddress }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), __assign({ batch_size: calls_1.length, batch_index: index }, extra));
|
|
2015
|
+
});
|
|
2016
|
+
};
|
|
2017
|
+
if (state.status === "pending") {
|
|
2018
|
+
// STARTED carries no batch id, exactly as a single transaction has
|
|
2019
|
+
// no hash yet: the wallet has not issued one.
|
|
2020
|
+
logger.debug("WagmiEventHandler: sendCalls start", { calls: calls_1.length });
|
|
2021
|
+
emitAll(TransactionStatus.STARTED);
|
|
2022
|
+
}
|
|
2023
|
+
else if (state.status === "success") {
|
|
2024
|
+
var batchId = readBatchId(state.data);
|
|
2025
|
+
logger.debug("WagmiEventHandler: sendCalls broadcast", { batchId: batchId });
|
|
2026
|
+
emitAll(TransactionStatus.BROADCASTED, batchId ? { batch_id: batchId } : undefined);
|
|
2027
|
+
if (batchId) {
|
|
2028
|
+
this.pendingBatches.set(batchId, __assign(__assign({ address: userAddress }, (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined, calls: calls_1 }));
|
|
2029
|
+
// Same bound as pendingTransactions, same reason.
|
|
2030
|
+
if (this.pendingBatches.size > 100) {
|
|
2031
|
+
var keys = Array.from(this.pendingBatches.keys());
|
|
2032
|
+
for (var i = 0; i < 50 && i < keys.length; i++) {
|
|
2033
|
+
this.pendingBatches.delete(keys[i]);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
// The status query can have completed BEFORE this registration:
|
|
2037
|
+
// TanStack dispatches a mutation's success state after its
|
|
2038
|
+
// onSuccess callbacks, and apps await waitForCallsStatus inside
|
|
2039
|
+
// onSuccess. That early query event found no registration and did
|
|
2040
|
+
// nothing, so look for its settled result in the cache now.
|
|
2041
|
+
this.settleFromCachedCallsStatus(batchId);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
else if (state.status === "error") {
|
|
2045
|
+
if (isUserRejection(state.error)) {
|
|
2046
|
+
logger.debug("WagmiEventHandler: sendCalls rejected");
|
|
2047
|
+
emitAll(TransactionStatus.REJECTED);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
catch (error) {
|
|
2052
|
+
logger.error("WagmiEventHandler: sendCalls error:", error);
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
1804
2055
|
/**
|
|
1805
2056
|
* Get the current Wagmi state
|
|
1806
2057
|
* Supports both getState() method and direct state property access
|