@formo/analytics 1.37.0 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/FormoAnalytics.d.ts +64 -0
- package/dist/cjs/src/FormoAnalytics.js +155 -5
- package/dist/cjs/src/FormoAnalyticsProvider.js +1 -0
- package/dist/cjs/src/evm/EvmEventTracker.d.ts +38 -10
- package/dist/cjs/src/evm/EvmEventTracker.js +182 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.js +26 -4
- package/dist/cjs/src/evm/EvmRequestTracker.d.ts +28 -10
- package/dist/cjs/src/evm/EvmRequestTracker.js +199 -55
- package/dist/cjs/src/provider/detection.d.ts +30 -0
- package/dist/cjs/src/provider/detection.js +62 -0
- package/dist/cjs/src/provider/index.d.ts +1 -1
- package/dist/cjs/src/provider/index.js +3 -1
- package/dist/cjs/src/types/base.d.ts +23 -0
- package/dist/cjs/src/types/provider.d.ts +16 -0
- package/dist/cjs/src/types/provider.js +17 -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 +44 -0
- package/dist/cjs/src/wagmi/WagmiEventHandler.js +306 -34
- package/dist/esm/src/FormoAnalytics.d.ts +64 -0
- package/dist/esm/src/FormoAnalytics.js +155 -5
- package/dist/esm/src/FormoAnalyticsProvider.js +1 -0
- package/dist/esm/src/evm/EvmEventTracker.d.ts +38 -10
- package/dist/esm/src/evm/EvmEventTracker.js +183 -1
- package/dist/esm/src/evm/EvmProviderRegistry.js +27 -5
- package/dist/esm/src/evm/EvmRequestTracker.d.ts +28 -10
- package/dist/esm/src/evm/EvmRequestTracker.js +198 -54
- package/dist/esm/src/provider/detection.d.ts +30 -0
- package/dist/esm/src/provider/detection.js +60 -0
- package/dist/esm/src/provider/index.d.ts +1 -1
- package/dist/esm/src/provider/index.js +1 -1
- package/dist/esm/src/types/base.d.ts +23 -0
- package/dist/esm/src/types/provider.d.ts +16 -0
- package/dist/esm/src/types/provider.js +16 -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 +44 -0
- package/dist/esm/src/wagmi/WagmiEventHandler.js +306 -34
- package/dist/index.umd.min.js +1 -1
- package/package.json +2 -2
|
@@ -5,6 +5,13 @@ import { AutocaptureEventType } from "../tracking/TrackingPolicy";
|
|
|
5
5
|
/** What the request tracker needs from the SDK that owns it. */
|
|
6
6
|
export interface EvmRequestTrackerDeps {
|
|
7
7
|
isAutocaptureEnabled(eventType: AutocaptureEventType): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Hybrid-capture dedup: true when a PENDING wagmi mutation already covers
|
|
10
|
+
* this request, so the mutation handler owns the capture. TanStack sets a
|
|
11
|
+
* mutation pending BEFORE its mutationFn issues the wallet call, so a
|
|
12
|
+
* hook-driven request always matches; an imperative one never does.
|
|
13
|
+
*/
|
|
14
|
+
shouldSkipRequestCapture?(method: string, params: unknown[]): boolean;
|
|
8
15
|
signature(params: {
|
|
9
16
|
status: SignatureStatus;
|
|
10
17
|
chainId?: ChainID;
|
|
@@ -23,16 +30,6 @@ export interface EvmRequestTrackerDeps {
|
|
|
23
30
|
function_args?: Record<string, unknown>;
|
|
24
31
|
}, properties?: IFormoEventProperties): Promise<void>;
|
|
25
32
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Autocapture for signatures and transactions, by wrapping a provider's
|
|
28
|
-
* `request`.
|
|
29
|
-
*
|
|
30
|
-
* The wrapper is deliberately thin: it observes the call the dapp was already
|
|
31
|
-
* making and never issues one of its own. That rule is why the chain a
|
|
32
|
-
* request ran on is read from `eth_chainId` calls the app makes, rather than
|
|
33
|
-
* probed - an SDK-issued lookup on a serialising transport can wedge the
|
|
34
|
-
* user's wallet, which is never an acceptable price for a label.
|
|
35
|
-
*/
|
|
36
33
|
export declare class EvmRequestTracker {
|
|
37
34
|
private readonly wallet;
|
|
38
35
|
private readonly registry;
|
|
@@ -50,6 +47,8 @@ export declare class EvmRequestTracker {
|
|
|
50
47
|
constructor(wallet: WalletStateStore, registry: EvmProviderRegistry, deps: EvmRequestTrackerDeps);
|
|
51
48
|
/** Stop every poll in flight. Terminal, like the event queue's close(). */
|
|
52
49
|
cleanup(): void;
|
|
50
|
+
/** Providers whose owner list includes this instance; pruned on cleanup. */
|
|
51
|
+
private wrappedProviders;
|
|
53
52
|
/** Re-arm a poll, unless this tracker has been torn down. */
|
|
54
53
|
private schedulePoll;
|
|
55
54
|
/**
|
|
@@ -59,6 +58,25 @@ export declare class EvmRequestTracker {
|
|
|
59
58
|
* would be missed for the rest of the session.
|
|
60
59
|
*/
|
|
61
60
|
registerRequestListeners(provider: EIP1193Provider): boolean;
|
|
61
|
+
/** Install the wrapper function onto the provider; separated so the
|
|
62
|
+
* routing shim above stays small. */
|
|
63
|
+
private installWrappedRequest;
|
|
64
|
+
/**
|
|
65
|
+
* The wrapper body proper: everything a request observation does, run
|
|
66
|
+
* against THIS instance's registry, wallet state, and event queue. Kept
|
|
67
|
+
* as a method so a surviving wrapper installed by a previous SDK
|
|
68
|
+
* instance can hand calls to the current one.
|
|
69
|
+
*/
|
|
70
|
+
private dispatchWrappedRequest;
|
|
71
|
+
/**
|
|
72
|
+
* Wallet attribution for request-derived events.
|
|
73
|
+
*
|
|
74
|
+
* Live per read through the registry, so a WalletConnect session names
|
|
75
|
+
* its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
|
|
76
|
+
* rows had provider_name EMPTY on every signature and transaction, which
|
|
77
|
+
* made per-wallet activity unanswerable in the warehouse.
|
|
78
|
+
*/
|
|
79
|
+
private attributionFor;
|
|
62
80
|
private buildSignatureEventPayload;
|
|
63
81
|
private buildTransactionEventPayload;
|
|
64
82
|
/**
|
|
@@ -57,9 +57,19 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
57
57
|
}
|
|
58
58
|
return t;
|
|
59
59
|
};
|
|
60
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
61
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
62
|
+
if (ar || !(i in from)) {
|
|
63
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
64
|
+
ar[i] = from[i];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
68
|
+
};
|
|
60
69
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
61
70
|
exports.EvmRequestTracker = void 0;
|
|
62
71
|
var logger_1 = require("../logger");
|
|
72
|
+
var provider_1 = require("../provider");
|
|
63
73
|
var chain_1 = require("../utils/chain");
|
|
64
74
|
var address_1 = require("../utils/address");
|
|
65
75
|
var types_1 = require("../types");
|
|
@@ -100,6 +110,19 @@ function hexToUtf8(hex) {
|
|
|
100
110
|
* probed - an SDK-issued lookup on a serialising transport can wedge the
|
|
101
111
|
* user's wallet, which is never an acceptable price for a label.
|
|
102
112
|
*/
|
|
113
|
+
/**
|
|
114
|
+
* Providers with a dispatch already on the synchronous call stack.
|
|
115
|
+
*
|
|
116
|
+
* A page can end up with LAYERED Formo wrappers: another library wraps our
|
|
117
|
+
* wrapper, the SDK rebuilds, and the new instance wraps the outer function
|
|
118
|
+
* because the marker is not on it. Both layers route to the same newest
|
|
119
|
+
* live tracker, which would instrument one user request twice. Every
|
|
120
|
+
* dispatch issues its underlying request synchronously (deliberately -
|
|
121
|
+
* the wallet call always goes out first), so an inner shim reached during
|
|
122
|
+
* that window belongs to the SAME user request and passes straight
|
|
123
|
+
* through.
|
|
124
|
+
*/
|
|
125
|
+
var dispatchInFlight = new WeakSet();
|
|
103
126
|
var EvmRequestTracker = /** @class */ (function () {
|
|
104
127
|
function EvmRequestTracker(wallet, registry, deps) {
|
|
105
128
|
this.wallet = wallet;
|
|
@@ -115,12 +138,28 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
115
138
|
*/
|
|
116
139
|
this.polls = new Set();
|
|
117
140
|
this.disposed = false;
|
|
141
|
+
/** Providers whose owner list includes this instance; pruned on cleanup. */
|
|
142
|
+
this.wrappedProviders = new Set();
|
|
118
143
|
}
|
|
119
144
|
/** Stop every poll in flight. Terminal, like the event queue's close(). */
|
|
120
145
|
EvmRequestTracker.prototype.cleanup = function () {
|
|
146
|
+
var _this = this;
|
|
121
147
|
this.disposed = true;
|
|
122
148
|
this.polls.forEach(function (timer) { return clearTimeout(timer); });
|
|
123
149
|
this.polls.clear();
|
|
150
|
+
// Remove this instance from every provider's owner list. The lists
|
|
151
|
+
// live on LONG-LIVED provider objects; leaving disposed trackers in
|
|
152
|
+
// them retains each old instance's whole object graph across rebuilds,
|
|
153
|
+
// growing without bound under HMR.
|
|
154
|
+
this.wrappedProviders.forEach(function (provider) {
|
|
155
|
+
var owners = provider[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
156
|
+
if (Array.isArray(owners)) {
|
|
157
|
+
var idx = owners.indexOf(_this);
|
|
158
|
+
if (idx !== -1)
|
|
159
|
+
owners.splice(idx, 1);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.wrappedProviders.clear();
|
|
124
163
|
};
|
|
125
164
|
/** Re-arm a poll, unless this tracker has been torn down. */
|
|
126
165
|
EvmRequestTracker.prototype.schedulePoll = function (fn, delayMs) {
|
|
@@ -146,19 +185,118 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
146
185
|
logger_1.logger.error("Provider not found for request (signature, transaction) tracking");
|
|
147
186
|
return false;
|
|
148
187
|
}
|
|
149
|
-
//
|
|
188
|
+
// Already wrapped: take OWNERSHIP rather than skipping. The wrapper
|
|
189
|
+
// survives an SDK rebuild and closes over the instance that installed
|
|
190
|
+
// it, whose queue is closed after cleanup - "skip" made the rebuilt
|
|
191
|
+
// instance report success while every request-derived event died in
|
|
192
|
+
// the dead instance's queue. The wrapper reads the owner slot per call.
|
|
150
193
|
var currentRequest = provider.request;
|
|
151
194
|
if (this.registry.isWrapped(provider, currentRequest)) {
|
|
152
|
-
|
|
195
|
+
var owners = provider[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
196
|
+
if (!Array.isArray(owners)) {
|
|
197
|
+
// A wrapper without its owner list cannot be taken over, and
|
|
198
|
+
// claiming success would silence every request event.
|
|
199
|
+
logger_1.logger.warn("wrapped without owner list; cannot rebind");
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
var idx = owners.indexOf(this);
|
|
203
|
+
if (idx !== -1)
|
|
204
|
+
owners.splice(idx, 1);
|
|
205
|
+
owners.push(this);
|
|
206
|
+
this.wrappedProviders.add(provider);
|
|
207
|
+
logger_1.logger.info("Provider already wrapped; rebinding the wrapper to this instance.");
|
|
153
208
|
return true;
|
|
154
209
|
}
|
|
155
210
|
var request = provider.request.bind(provider);
|
|
156
211
|
var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
212
|
+
var owners, liveOwner;
|
|
213
|
+
var method = _b.method, params = _b.params;
|
|
214
|
+
return __generator(this, function (_c) {
|
|
215
|
+
owners = provider[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
216
|
+
if (dispatchInFlight.has(provider)) {
|
|
217
|
+
// An outer Formo wrapper is already instrumenting this very
|
|
218
|
+
// request; this layer only forwards.
|
|
219
|
+
return [2 /*return*/, request({ method: method, params: params })];
|
|
220
|
+
}
|
|
221
|
+
liveOwner = Array.isArray(owners)
|
|
222
|
+
? __spreadArray([], owners, true).reverse().find(function (o) { return !o.disposed; })
|
|
223
|
+
: undefined;
|
|
224
|
+
dispatchInFlight.add(provider);
|
|
225
|
+
try {
|
|
226
|
+
return [2 /*return*/, (liveOwner !== null && liveOwner !== void 0 ? liveOwner : this).dispatchWrappedRequest({ method: method, params: params }, provider, request)];
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
// Cleared as soon as the dispatch call RETURNS its promise: the
|
|
230
|
+
// underlying request has been issued synchronously by then, so
|
|
231
|
+
// the window covers exactly the nested layers of this one call
|
|
232
|
+
// and never a concurrent request.
|
|
233
|
+
dispatchInFlight.delete(provider);
|
|
234
|
+
}
|
|
235
|
+
return [2 /*return*/];
|
|
236
|
+
});
|
|
237
|
+
}); };
|
|
238
|
+
try {
|
|
239
|
+
// MERGE with any existing list rather than overwriting: a wallet that
|
|
240
|
+
// replaced `request` forces a re-wrap, and discarding the prior list
|
|
241
|
+
// would drop other live instances from ownership - the newest-live
|
|
242
|
+
// fallback then has nobody to fall back to.
|
|
243
|
+
var slot = provider;
|
|
244
|
+
var prior = slot[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
245
|
+
var owners = Array.isArray(prior) ? prior : [];
|
|
246
|
+
var idx = owners.indexOf(this);
|
|
247
|
+
if (idx !== -1)
|
|
248
|
+
owners.splice(idx, 1);
|
|
249
|
+
owners.push(this);
|
|
250
|
+
if (!Array.isArray(prior)) {
|
|
251
|
+
slot[types_1.WRAPPED_REQUEST_OWNER_SYMBOL] = owners;
|
|
252
|
+
}
|
|
253
|
+
this.wrappedProviders.add(provider);
|
|
254
|
+
}
|
|
255
|
+
catch (_a) {
|
|
256
|
+
/* frozen provider: the request write below fails too and aborts */
|
|
257
|
+
}
|
|
258
|
+
return this.installWrappedRequest(provider, wrappedRequest);
|
|
259
|
+
};
|
|
260
|
+
/** Install the wrapper function onto the provider; separated so the
|
|
261
|
+
* routing shim above stays small. */
|
|
262
|
+
EvmRequestTracker.prototype.installWrappedRequest = function (provider, wrappedRequest) {
|
|
263
|
+
// Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
|
|
264
|
+
wrappedRequest[types_1.WRAPPED_REQUEST_SYMBOL] = true;
|
|
265
|
+
// Both writes go inside the try. A frozen or non-extensible provider
|
|
266
|
+
// throws on the symbol assignment just as readily as on `request`, and
|
|
267
|
+
// that one used to sit outside the guard, so an unwrappable provider
|
|
268
|
+
// aborted registration instead of being skipped.
|
|
269
|
+
try {
|
|
270
|
+
provider[types_1.WRAPPED_REQUEST_REF_SYMBOL] =
|
|
271
|
+
wrappedRequest;
|
|
272
|
+
provider.request = wrappedRequest;
|
|
273
|
+
// Read back: an accessor or Proxy can ACCEPT the assignment without
|
|
274
|
+
// installing it, and success here is a promise that capture works.
|
|
275
|
+
if (provider.request !== wrappedRequest) {
|
|
276
|
+
logger_1.logger.warn("request assignment swallowed; not wrapped");
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
logger_1.logger.warn("Failed to wrap provider.request; skipping", e);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* The wrapper body proper: everything a request observation does, run
|
|
288
|
+
* against THIS instance's registry, wallet state, and event queue. Kept
|
|
289
|
+
* as a method so a surviving wrapper installed by a previous SDK
|
|
290
|
+
* instance can hand calls to the current one.
|
|
291
|
+
*/
|
|
292
|
+
EvmRequestTracker.prototype.dispatchWrappedRequest = function (_a, provider_2, request_1) {
|
|
293
|
+
return __awaiter(this, arguments, void 0, function (_b, provider, request) {
|
|
157
294
|
var generation_1, responsePromise, capturedChainId_1, response_1, error_1, rpcError, txPromise, txChainId_1, transactionHash_1, error_2, rpcError;
|
|
158
295
|
var _this = this;
|
|
296
|
+
var _c, _d, _e, _f;
|
|
159
297
|
var method = _b.method, params = _b.params;
|
|
160
|
-
return __generator(this, function (
|
|
161
|
-
switch (
|
|
298
|
+
return __generator(this, function (_g) {
|
|
299
|
+
switch (_g.label) {
|
|
162
300
|
case 0:
|
|
163
301
|
// Learn the chain from a call the APP was making anyway.
|
|
164
302
|
//
|
|
@@ -187,6 +325,10 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
187
325
|
logger_1.logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
|
|
188
326
|
return [2 /*return*/, request({ method: method, params: params })];
|
|
189
327
|
}
|
|
328
|
+
if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, method, params)) {
|
|
329
|
+
// A pending wagmi mutation owns this capture.
|
|
330
|
+
return [2 /*return*/, request({ method: method, params: params })];
|
|
331
|
+
}
|
|
190
332
|
responsePromise = request({ method: method, params: params });
|
|
191
333
|
// Attach a no-op handler now so a rejection arriving before the await
|
|
192
334
|
// below is never reported as unhandled. The real handling is there.
|
|
@@ -199,7 +341,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
199
341
|
switch (_a.label) {
|
|
200
342
|
case 0:
|
|
201
343
|
_a.trys.push([0, 2, , 3]);
|
|
202
|
-
return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
|
|
344
|
+
return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
|
|
203
345
|
case 1:
|
|
204
346
|
_a.sent();
|
|
205
347
|
return [3 /*break*/, 3];
|
|
@@ -211,12 +353,12 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
211
353
|
}
|
|
212
354
|
});
|
|
213
355
|
}); })();
|
|
214
|
-
|
|
356
|
+
_g.label = 1;
|
|
215
357
|
case 1:
|
|
216
|
-
|
|
358
|
+
_g.trys.push([1, 3, , 4]);
|
|
217
359
|
return [4 /*yield*/, responsePromise];
|
|
218
360
|
case 2:
|
|
219
|
-
response_1 =
|
|
361
|
+
response_1 = _g.sent();
|
|
220
362
|
// Track signature confirmation only for truthy responses
|
|
221
363
|
if (response_1) {
|
|
222
364
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
@@ -225,7 +367,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
225
367
|
switch (_a.label) {
|
|
226
368
|
case 0:
|
|
227
369
|
_a.trys.push([0, 2, , 3]);
|
|
228
|
-
return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)))];
|
|
370
|
+
return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)), this.attributionFor(provider))];
|
|
229
371
|
case 1:
|
|
230
372
|
_a.sent();
|
|
231
373
|
return [3 /*break*/, 3];
|
|
@@ -240,9 +382,9 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
240
382
|
}
|
|
241
383
|
return [2 /*return*/, response_1];
|
|
242
384
|
case 3:
|
|
243
|
-
error_1 =
|
|
385
|
+
error_1 = _g.sent();
|
|
244
386
|
rpcError = error_1;
|
|
245
|
-
if ((
|
|
387
|
+
if ((0, provider_1.isUserRejectionError)(rpcError)) {
|
|
246
388
|
// Use the already cast rpcError to avoid duplication
|
|
247
389
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
248
390
|
var e_3;
|
|
@@ -250,7 +392,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
250
392
|
switch (_a.label) {
|
|
251
393
|
case 0:
|
|
252
394
|
_a.trys.push([0, 2, , 3]);
|
|
253
|
-
return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
|
|
395
|
+
return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
|
|
254
396
|
case 1:
|
|
255
397
|
_a.sent();
|
|
256
398
|
return [3 /*break*/, 3];
|
|
@@ -282,6 +424,10 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
282
424
|
logger_1.logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
|
|
283
425
|
return [2 /*return*/, request({ method: method, params: params })];
|
|
284
426
|
}
|
|
427
|
+
if ((_f = (_e = this.deps).shouldSkipRequestCapture) === null || _f === void 0 ? void 0 : _f.call(_e, method, params)) {
|
|
428
|
+
// A pending wagmi mutation owns this capture.
|
|
429
|
+
return [2 /*return*/, request({ method: method, params: params })];
|
|
430
|
+
}
|
|
285
431
|
txPromise = request({ method: method, params: params });
|
|
286
432
|
txPromise.catch(function () { return undefined; });
|
|
287
433
|
txChainId_1 = this.registry.resolveChainId(provider);
|
|
@@ -294,7 +440,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
294
440
|
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
295
441
|
case 1:
|
|
296
442
|
payload = _a.sent();
|
|
297
|
-
return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload))];
|
|
443
|
+
return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload), this.attributionFor(provider))];
|
|
298
444
|
case 2:
|
|
299
445
|
_a.sent();
|
|
300
446
|
return [3 /*break*/, 4];
|
|
@@ -306,12 +452,12 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
306
452
|
}
|
|
307
453
|
});
|
|
308
454
|
}); })();
|
|
309
|
-
|
|
455
|
+
_g.label = 5;
|
|
310
456
|
case 5:
|
|
311
|
-
|
|
457
|
+
_g.trys.push([5, 7, , 8]);
|
|
312
458
|
return [4 /*yield*/, txPromise];
|
|
313
459
|
case 6:
|
|
314
|
-
transactionHash_1 =
|
|
460
|
+
transactionHash_1 = _g.sent();
|
|
315
461
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
316
462
|
var payload, e_5;
|
|
317
463
|
return __generator(this, function (_a) {
|
|
@@ -321,7 +467,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
321
467
|
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
322
468
|
case 1:
|
|
323
469
|
payload = _a.sent();
|
|
324
|
-
return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }))];
|
|
470
|
+
return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }), this.attributionFor(provider))];
|
|
325
471
|
case 2:
|
|
326
472
|
_a.sent();
|
|
327
473
|
// Start async polling for transaction receipt
|
|
@@ -337,9 +483,9 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
337
483
|
}); })();
|
|
338
484
|
return [2 /*return*/, transactionHash_1];
|
|
339
485
|
case 7:
|
|
340
|
-
error_2 =
|
|
486
|
+
error_2 = _g.sent();
|
|
341
487
|
rpcError = error_2;
|
|
342
|
-
if ((
|
|
488
|
+
if ((0, provider_1.isUserRejectionError)(rpcError)) {
|
|
343
489
|
// Use the already cast rpcError to avoid duplication
|
|
344
490
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
345
491
|
var payload, e_6;
|
|
@@ -350,7 +496,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
350
496
|
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
351
497
|
case 1:
|
|
352
498
|
payload = _a.sent();
|
|
353
|
-
return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload))];
|
|
499
|
+
return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload), this.attributionFor(provider))];
|
|
354
500
|
case 2:
|
|
355
501
|
_a.sent();
|
|
356
502
|
return [3 /*break*/, 4];
|
|
@@ -367,23 +513,19 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
367
513
|
case 8: return [2 /*return*/, request({ method: method, params: params })];
|
|
368
514
|
}
|
|
369
515
|
});
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
catch (e) {
|
|
384
|
-
logger_1.logger.warn("Failed to wrap provider.request; skipping", e);
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Wallet attribution for request-derived events.
|
|
520
|
+
*
|
|
521
|
+
* Live per read through the registry, so a WalletConnect session names
|
|
522
|
+
* its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
|
|
523
|
+
* rows had provider_name EMPTY on every signature and transaction, which
|
|
524
|
+
* made per-wallet activity unanswerable in the warehouse.
|
|
525
|
+
*/
|
|
526
|
+
EvmRequestTracker.prototype.attributionFor = function (provider) {
|
|
527
|
+
var info = this.registry.infoFor(provider);
|
|
528
|
+
return { providerName: info.name, rdns: info.rdns };
|
|
387
529
|
};
|
|
388
530
|
EvmRequestTracker.prototype.buildSignatureEventPayload = function (method, params,
|
|
389
531
|
// Intentionally not read. Kept for positional call-site arity.
|
|
@@ -449,7 +591,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
449
591
|
/**
|
|
450
592
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
451
593
|
*/
|
|
452
|
-
EvmRequestTracker.prototype.pollTransactionReceipt = function (
|
|
594
|
+
EvmRequestTracker.prototype.pollTransactionReceipt = function (provider_2, transactionHash_2, payload_1) {
|
|
453
595
|
return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
|
|
454
596
|
var attempts, poll;
|
|
455
597
|
var _this = this;
|
|
@@ -479,7 +621,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
479
621
|
// status: 1 = success, 0 = reverted
|
|
480
622
|
if (receipt.status === "0x1" || receipt.status === 1) {
|
|
481
623
|
this.deps
|
|
482
|
-
.transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }))
|
|
624
|
+
.transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
|
|
483
625
|
.catch(function (e) {
|
|
484
626
|
return logger_1.logger.error("Formo: Failed to track transaction confirmation", e);
|
|
485
627
|
});
|
|
@@ -487,7 +629,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
487
629
|
}
|
|
488
630
|
else if (receipt.status === "0x0" || receipt.status === 0) {
|
|
489
631
|
this.deps
|
|
490
|
-
.transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }))
|
|
632
|
+
.transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
|
|
491
633
|
.catch(function (e) {
|
|
492
634
|
return logger_1.logger.error("Formo: Failed to track transaction revert", e);
|
|
493
635
|
});
|
|
@@ -530,10 +672,10 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
530
672
|
*/
|
|
531
673
|
EvmRequestTracker.prototype.trackBatchedCalls = function (provider, request, params) {
|
|
532
674
|
return __awaiter(this, void 0, void 0, function () {
|
|
533
|
-
var sendPromise, batch, calls, declared, chainId, address, payloads, _i, payloads_1, p, properties, rest, result, batchId, _a, payloads_2, p, properties, rest, error_3, rpcError, _b, payloads_3, p, properties, rest;
|
|
534
|
-
var _c;
|
|
535
|
-
return __generator(this, function (
|
|
536
|
-
switch (
|
|
675
|
+
var sendPromise, batch, calls, declared, chainId, address, attribution, payloads, _i, payloads_1, p, properties, rest, result, batchId, _a, payloads_2, p, properties, rest, error_3, rpcError, _b, payloads_3, p, properties, rest;
|
|
676
|
+
var _c, _d, _e;
|
|
677
|
+
return __generator(this, function (_f) {
|
|
678
|
+
switch (_f.label) {
|
|
537
679
|
case 0:
|
|
538
680
|
if (!this.deps.isAutocaptureEnabled("transaction")) {
|
|
539
681
|
logger_1.logger.debug("Transaction event skipped (autocapture.transaction: false)", {
|
|
@@ -541,13 +683,17 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
541
683
|
});
|
|
542
684
|
return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
|
|
543
685
|
}
|
|
686
|
+
if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, "wallet_sendCalls", params)) {
|
|
687
|
+
// A pending wagmi sendCalls mutation owns this capture.
|
|
688
|
+
return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
|
|
689
|
+
}
|
|
544
690
|
sendPromise = request({ method: "wallet_sendCalls", params: params });
|
|
545
691
|
sendPromise.catch(function () { return undefined; });
|
|
546
692
|
batch = params[0];
|
|
547
693
|
calls = Array.isArray(batch === null || batch === void 0 ? void 0 : batch.calls) ? batch.calls : [];
|
|
548
694
|
declared = typeof (batch === null || batch === void 0 ? void 0 : batch.chainId) === "string" ? (0, chain_1.parseChainId)(batch.chainId) : undefined;
|
|
549
695
|
chainId = declared || this.registry.resolveChainId(provider);
|
|
550
|
-
address = (0, address_1.validateAndChecksumAddress)((
|
|
696
|
+
address = (0, address_1.validateAndChecksumAddress)((_e = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _e !== void 0 ? _e : "");
|
|
551
697
|
if (!address) {
|
|
552
698
|
// Nothing can be attributed without a sender, and inventing one would
|
|
553
699
|
// be worse than reporting nothing. The user's call still goes through.
|
|
@@ -566,16 +712,14 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
566
712
|
if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
|
|
567
713
|
this.wallet.backfill(address, chainId, provider);
|
|
568
714
|
}
|
|
715
|
+
attribution = this.attributionFor(provider);
|
|
569
716
|
payloads = calls.map(function (call, index) { return ({
|
|
570
717
|
chainId: chainId,
|
|
571
718
|
address: address,
|
|
572
719
|
to: call === null || call === void 0 ? void 0 : call.to,
|
|
573
720
|
value: call === null || call === void 0 ? void 0 : call.value,
|
|
574
721
|
data: call === null || call === void 0 ? void 0 : call.data,
|
|
575
|
-
properties: {
|
|
576
|
-
batch_size: calls.length,
|
|
577
|
-
batch_index: index,
|
|
578
|
-
},
|
|
722
|
+
properties: __assign({ batch_size: calls.length, batch_index: index }, attribution),
|
|
579
723
|
}); });
|
|
580
724
|
// STARTED carries no batch id: the wallet has not issued one yet, exactly
|
|
581
725
|
// as `eth_sendTransaction` has no hash at this point. Position within the
|
|
@@ -587,12 +731,12 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
587
731
|
.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, rest), properties)
|
|
588
732
|
.catch(function (e) { return logger_1.logger.error("Formo: Failed to track batch call start", e); });
|
|
589
733
|
}
|
|
590
|
-
|
|
734
|
+
_f.label = 1;
|
|
591
735
|
case 1:
|
|
592
|
-
|
|
736
|
+
_f.trys.push([1, 3, , 4]);
|
|
593
737
|
return [4 /*yield*/, sendPromise];
|
|
594
738
|
case 2:
|
|
595
|
-
result =
|
|
739
|
+
result = _f.sent();
|
|
596
740
|
batchId = (0, batch_1.readBatchId)(result);
|
|
597
741
|
for (_a = 0, payloads_2 = payloads; _a < payloads_2.length; _a++) {
|
|
598
742
|
p = payloads_2[_a];
|
|
@@ -607,9 +751,9 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
607
751
|
this.pollBatchStatus(provider, batchId, payloads);
|
|
608
752
|
return [2 /*return*/, result];
|
|
609
753
|
case 3:
|
|
610
|
-
error_3 =
|
|
754
|
+
error_3 = _f.sent();
|
|
611
755
|
rpcError = error_3;
|
|
612
|
-
if ((
|
|
756
|
+
if ((0, provider_1.isUserRejectionError)(rpcError)) {
|
|
613
757
|
// One rejection dismisses the whole prompt, so every call in it is
|
|
614
758
|
// rejected. Reporting only the first would undercount.
|
|
615
759
|
for (_b = 0, payloads_3 = payloads; _b < payloads_3.length; _b++) {
|
|
@@ -639,7 +783,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
639
783
|
* what makes a partially reverted non-atomic batch report honestly instead
|
|
640
784
|
* of marking every call with the batch's worst outcome.
|
|
641
785
|
*/
|
|
642
|
-
EvmRequestTracker.prototype.pollBatchStatus = function (
|
|
786
|
+
EvmRequestTracker.prototype.pollBatchStatus = function (provider_2, batchId_1, payloads_4) {
|
|
643
787
|
return __awaiter(this, arguments, void 0, function (provider, batchId, payloads, maxAttempts, intervalMs) {
|
|
644
788
|
var attempts, poll;
|
|
645
789
|
var _this = this;
|
|
@@ -41,6 +41,36 @@ export interface ProviderInfo {
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
export declare function detectInjectedProviderInfo(provider: EIP1193Provider): ProviderInfo;
|
|
44
|
+
/**
|
|
45
|
+
* Was this error the USER declining, whatever transport delivered it?
|
|
46
|
+
*
|
|
47
|
+
* Three dialects say "the user said no":
|
|
48
|
+
* - EIP-1193: code 4001 (UserRejectedRequest).
|
|
49
|
+
* - WalletConnect sdkErrors: codes 5000-5005 (USER_REJECTED and its
|
|
50
|
+
* variants). A LIVE MetaMask Mobile session rejecting a transaction
|
|
51
|
+
* produced one of these and the SDK's 4001-only match reported nothing -
|
|
52
|
+
* every WalletConnect rejection was silently uncounted.
|
|
53
|
+
* - viem: a typed UserRejectedRequestError, sometimes without the numeric
|
|
54
|
+
* code surviving the wrapping.
|
|
55
|
+
*
|
|
56
|
+
* The real code often hides under `cause` (viem nests, WC wraps), so the
|
|
57
|
+
* chain is walked a few levels.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isUserRejectionError(error: unknown): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The wallet on the far side of a WalletConnect session.
|
|
62
|
+
*
|
|
63
|
+
* WalletConnect is a transport, not a wallet: the signing wallet (Ledger
|
|
64
|
+
* Live, MetaMask Mobile, Safe, ...) identifies itself in the session's peer
|
|
65
|
+
* metadata. Reporting only "WalletConnect" hides every wallet behind it -
|
|
66
|
+
* production showed Ledger at effectively zero while its sessions were being
|
|
67
|
+
* tracked under the transport's name. Reads synchronous state only; never
|
|
68
|
+
* issues an RPC.
|
|
69
|
+
*/
|
|
70
|
+
export declare function readWalletConnectPeer(provider: EIP1193Provider): {
|
|
71
|
+
name: string;
|
|
72
|
+
url?: string;
|
|
73
|
+
} | undefined;
|
|
44
74
|
/**
|
|
45
75
|
* Validates that a provider implements the required EIP-1193 interface
|
|
46
76
|
*
|
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Provider detection utilities for identifying wallet providers
|
|
4
4
|
*/
|
|
5
|
+
var __assign = (this && this.__assign) || function () {
|
|
6
|
+
__assign = Object.assign || function(t) {
|
|
7
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
8
|
+
s = arguments[i];
|
|
9
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
10
|
+
t[p] = s[p];
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
13
|
+
};
|
|
14
|
+
return __assign.apply(this, arguments);
|
|
15
|
+
};
|
|
5
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
17
|
exports.DEFAULT_PROVIDER_ICON = void 0;
|
|
7
18
|
exports.detectInjectedProviderInfo = detectInjectedProviderInfo;
|
|
19
|
+
exports.isUserRejectionError = isUserRejectionError;
|
|
20
|
+
exports.readWalletConnectPeer = readWalletConnectPeer;
|
|
8
21
|
exports.isValidProvider = isValidProvider;
|
|
9
22
|
/**
|
|
10
23
|
* Default icon for providers without custom icons
|
|
@@ -81,6 +94,55 @@ function detectInjectedProviderInfo(provider) {
|
|
|
81
94
|
icon: exports.DEFAULT_PROVIDER_ICON,
|
|
82
95
|
};
|
|
83
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Was this error the USER declining, whatever transport delivered it?
|
|
99
|
+
*
|
|
100
|
+
* Three dialects say "the user said no":
|
|
101
|
+
* - EIP-1193: code 4001 (UserRejectedRequest).
|
|
102
|
+
* - WalletConnect sdkErrors: codes 5000-5005 (USER_REJECTED and its
|
|
103
|
+
* variants). A LIVE MetaMask Mobile session rejecting a transaction
|
|
104
|
+
* produced one of these and the SDK's 4001-only match reported nothing -
|
|
105
|
+
* every WalletConnect rejection was silently uncounted.
|
|
106
|
+
* - viem: a typed UserRejectedRequestError, sometimes without the numeric
|
|
107
|
+
* code surviving the wrapping.
|
|
108
|
+
*
|
|
109
|
+
* The real code often hides under `cause` (viem nests, WC wraps), so the
|
|
110
|
+
* chain is walked a few levels.
|
|
111
|
+
*/
|
|
112
|
+
function isUserRejectionError(error) {
|
|
113
|
+
var cursor = error;
|
|
114
|
+
for (var depth = 0; cursor && depth < 5; depth++) {
|
|
115
|
+
var code = cursor.code;
|
|
116
|
+
if (code === 4001 ||
|
|
117
|
+
(typeof code === "number" && code >= 5000 && code <= 5005) ||
|
|
118
|
+
cursor.name === "UserRejectedRequestError") {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
cursor = cursor.cause;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The wallet on the far side of a WalletConnect session.
|
|
127
|
+
*
|
|
128
|
+
* WalletConnect is a transport, not a wallet: the signing wallet (Ledger
|
|
129
|
+
* Live, MetaMask Mobile, Safe, ...) identifies itself in the session's peer
|
|
130
|
+
* metadata. Reporting only "WalletConnect" hides every wallet behind it -
|
|
131
|
+
* production showed Ledger at effectively zero while its sessions were being
|
|
132
|
+
* tracked under the transport's name. Reads synchronous state only; never
|
|
133
|
+
* issues an RPC.
|
|
134
|
+
*/
|
|
135
|
+
function readWalletConnectPeer(provider) {
|
|
136
|
+
var _a;
|
|
137
|
+
var session = provider.session;
|
|
138
|
+
var metadata = (_a = session === null || session === void 0 ? void 0 : session.peer) === null || _a === void 0 ? void 0 : _a.metadata;
|
|
139
|
+
if (!metadata || typeof metadata.name !== "string" || metadata.name.length === 0) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return __assign({ name: metadata.name }, (typeof metadata.url === "string" && metadata.url.length > 0
|
|
143
|
+
? { url: metadata.url }
|
|
144
|
+
: {}));
|
|
145
|
+
}
|
|
84
146
|
/**
|
|
85
147
|
* Validates that a provider implements the required EIP-1193 interface
|
|
86
148
|
*
|