@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
|
/**
|
|
@@ -56,10 +56,20 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
56
56
|
}
|
|
57
57
|
return t;
|
|
58
58
|
};
|
|
59
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
60
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
61
|
+
if (ar || !(i in from)) {
|
|
62
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
63
|
+
ar[i] = from[i];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
67
|
+
};
|
|
59
68
|
import { logger } from "../logger";
|
|
69
|
+
import { isUserRejectionError } from "../provider";
|
|
60
70
|
import { parseChainId } from "../utils/chain";
|
|
61
71
|
import { validateAndChecksumAddress } from "../utils/address";
|
|
62
|
-
import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "../types";
|
|
72
|
+
import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, WRAPPED_REQUEST_OWNER_SYMBOL, } from "../types";
|
|
63
73
|
import { readBatchId, readBatchStatusCode, batchCallOutcome, batchReceiptForCall, } from "./batch";
|
|
64
74
|
/**
|
|
65
75
|
* Decode a hex-encoded `personal_sign` message.
|
|
@@ -97,6 +107,19 @@ function hexToUtf8(hex) {
|
|
|
97
107
|
* probed - an SDK-issued lookup on a serialising transport can wedge the
|
|
98
108
|
* user's wallet, which is never an acceptable price for a label.
|
|
99
109
|
*/
|
|
110
|
+
/**
|
|
111
|
+
* Providers with a dispatch already on the synchronous call stack.
|
|
112
|
+
*
|
|
113
|
+
* A page can end up with LAYERED Formo wrappers: another library wraps our
|
|
114
|
+
* wrapper, the SDK rebuilds, and the new instance wraps the outer function
|
|
115
|
+
* because the marker is not on it. Both layers route to the same newest
|
|
116
|
+
* live tracker, which would instrument one user request twice. Every
|
|
117
|
+
* dispatch issues its underlying request synchronously (deliberately -
|
|
118
|
+
* the wallet call always goes out first), so an inner shim reached during
|
|
119
|
+
* that window belongs to the SAME user request and passes straight
|
|
120
|
+
* through.
|
|
121
|
+
*/
|
|
122
|
+
var dispatchInFlight = new WeakSet();
|
|
100
123
|
var EvmRequestTracker = /** @class */ (function () {
|
|
101
124
|
function EvmRequestTracker(wallet, registry, deps) {
|
|
102
125
|
this.wallet = wallet;
|
|
@@ -112,12 +135,28 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
112
135
|
*/
|
|
113
136
|
this.polls = new Set();
|
|
114
137
|
this.disposed = false;
|
|
138
|
+
/** Providers whose owner list includes this instance; pruned on cleanup. */
|
|
139
|
+
this.wrappedProviders = new Set();
|
|
115
140
|
}
|
|
116
141
|
/** Stop every poll in flight. Terminal, like the event queue's close(). */
|
|
117
142
|
EvmRequestTracker.prototype.cleanup = function () {
|
|
143
|
+
var _this = this;
|
|
118
144
|
this.disposed = true;
|
|
119
145
|
this.polls.forEach(function (timer) { return clearTimeout(timer); });
|
|
120
146
|
this.polls.clear();
|
|
147
|
+
// Remove this instance from every provider's owner list. The lists
|
|
148
|
+
// live on LONG-LIVED provider objects; leaving disposed trackers in
|
|
149
|
+
// them retains each old instance's whole object graph across rebuilds,
|
|
150
|
+
// growing without bound under HMR.
|
|
151
|
+
this.wrappedProviders.forEach(function (provider) {
|
|
152
|
+
var owners = provider[WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
153
|
+
if (Array.isArray(owners)) {
|
|
154
|
+
var idx = owners.indexOf(_this);
|
|
155
|
+
if (idx !== -1)
|
|
156
|
+
owners.splice(idx, 1);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.wrappedProviders.clear();
|
|
121
160
|
};
|
|
122
161
|
/** Re-arm a poll, unless this tracker has been torn down. */
|
|
123
162
|
EvmRequestTracker.prototype.schedulePoll = function (fn, delayMs) {
|
|
@@ -143,19 +182,118 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
143
182
|
logger.error("Provider not found for request (signature, transaction) tracking");
|
|
144
183
|
return false;
|
|
145
184
|
}
|
|
146
|
-
//
|
|
185
|
+
// Already wrapped: take OWNERSHIP rather than skipping. The wrapper
|
|
186
|
+
// survives an SDK rebuild and closes over the instance that installed
|
|
187
|
+
// it, whose queue is closed after cleanup - "skip" made the rebuilt
|
|
188
|
+
// instance report success while every request-derived event died in
|
|
189
|
+
// the dead instance's queue. The wrapper reads the owner slot per call.
|
|
147
190
|
var currentRequest = provider.request;
|
|
148
191
|
if (this.registry.isWrapped(provider, currentRequest)) {
|
|
149
|
-
|
|
192
|
+
var owners = provider[WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
193
|
+
if (!Array.isArray(owners)) {
|
|
194
|
+
// A wrapper without its owner list cannot be taken over, and
|
|
195
|
+
// claiming success would silence every request event.
|
|
196
|
+
logger.warn("wrapped without owner list; cannot rebind");
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
var idx = owners.indexOf(this);
|
|
200
|
+
if (idx !== -1)
|
|
201
|
+
owners.splice(idx, 1);
|
|
202
|
+
owners.push(this);
|
|
203
|
+
this.wrappedProviders.add(provider);
|
|
204
|
+
logger.info("Provider already wrapped; rebinding the wrapper to this instance.");
|
|
150
205
|
return true;
|
|
151
206
|
}
|
|
152
207
|
var request = provider.request.bind(provider);
|
|
153
208
|
var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
209
|
+
var owners, liveOwner;
|
|
210
|
+
var method = _b.method, params = _b.params;
|
|
211
|
+
return __generator(this, function (_c) {
|
|
212
|
+
owners = provider[WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
213
|
+
if (dispatchInFlight.has(provider)) {
|
|
214
|
+
// An outer Formo wrapper is already instrumenting this very
|
|
215
|
+
// request; this layer only forwards.
|
|
216
|
+
return [2 /*return*/, request({ method: method, params: params })];
|
|
217
|
+
}
|
|
218
|
+
liveOwner = Array.isArray(owners)
|
|
219
|
+
? __spreadArray([], owners, true).reverse().find(function (o) { return !o.disposed; })
|
|
220
|
+
: undefined;
|
|
221
|
+
dispatchInFlight.add(provider);
|
|
222
|
+
try {
|
|
223
|
+
return [2 /*return*/, (liveOwner !== null && liveOwner !== void 0 ? liveOwner : this).dispatchWrappedRequest({ method: method, params: params }, provider, request)];
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
// Cleared as soon as the dispatch call RETURNS its promise: the
|
|
227
|
+
// underlying request has been issued synchronously by then, so
|
|
228
|
+
// the window covers exactly the nested layers of this one call
|
|
229
|
+
// and never a concurrent request.
|
|
230
|
+
dispatchInFlight.delete(provider);
|
|
231
|
+
}
|
|
232
|
+
return [2 /*return*/];
|
|
233
|
+
});
|
|
234
|
+
}); };
|
|
235
|
+
try {
|
|
236
|
+
// MERGE with any existing list rather than overwriting: a wallet that
|
|
237
|
+
// replaced `request` forces a re-wrap, and discarding the prior list
|
|
238
|
+
// would drop other live instances from ownership - the newest-live
|
|
239
|
+
// fallback then has nobody to fall back to.
|
|
240
|
+
var slot = provider;
|
|
241
|
+
var prior = slot[WRAPPED_REQUEST_OWNER_SYMBOL];
|
|
242
|
+
var owners = Array.isArray(prior) ? prior : [];
|
|
243
|
+
var idx = owners.indexOf(this);
|
|
244
|
+
if (idx !== -1)
|
|
245
|
+
owners.splice(idx, 1);
|
|
246
|
+
owners.push(this);
|
|
247
|
+
if (!Array.isArray(prior)) {
|
|
248
|
+
slot[WRAPPED_REQUEST_OWNER_SYMBOL] = owners;
|
|
249
|
+
}
|
|
250
|
+
this.wrappedProviders.add(provider);
|
|
251
|
+
}
|
|
252
|
+
catch (_a) {
|
|
253
|
+
/* frozen provider: the request write below fails too and aborts */
|
|
254
|
+
}
|
|
255
|
+
return this.installWrappedRequest(provider, wrappedRequest);
|
|
256
|
+
};
|
|
257
|
+
/** Install the wrapper function onto the provider; separated so the
|
|
258
|
+
* routing shim above stays small. */
|
|
259
|
+
EvmRequestTracker.prototype.installWrappedRequest = function (provider, wrappedRequest) {
|
|
260
|
+
// Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
|
|
261
|
+
wrappedRequest[WRAPPED_REQUEST_SYMBOL] = true;
|
|
262
|
+
// Both writes go inside the try. A frozen or non-extensible provider
|
|
263
|
+
// throws on the symbol assignment just as readily as on `request`, and
|
|
264
|
+
// that one used to sit outside the guard, so an unwrappable provider
|
|
265
|
+
// aborted registration instead of being skipped.
|
|
266
|
+
try {
|
|
267
|
+
provider[WRAPPED_REQUEST_REF_SYMBOL] =
|
|
268
|
+
wrappedRequest;
|
|
269
|
+
provider.request = wrappedRequest;
|
|
270
|
+
// Read back: an accessor or Proxy can ACCEPT the assignment without
|
|
271
|
+
// installing it, and success here is a promise that capture works.
|
|
272
|
+
if (provider.request !== wrappedRequest) {
|
|
273
|
+
logger.warn("request assignment swallowed; not wrapped");
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
logger.warn("Failed to wrap provider.request; skipping", e);
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* The wrapper body proper: everything a request observation does, run
|
|
285
|
+
* against THIS instance's registry, wallet state, and event queue. Kept
|
|
286
|
+
* as a method so a surviving wrapper installed by a previous SDK
|
|
287
|
+
* instance can hand calls to the current one.
|
|
288
|
+
*/
|
|
289
|
+
EvmRequestTracker.prototype.dispatchWrappedRequest = function (_a, provider_1, request_1) {
|
|
290
|
+
return __awaiter(this, arguments, void 0, function (_b, provider, request) {
|
|
154
291
|
var generation_1, responsePromise, capturedChainId_1, response_1, error_1, rpcError, txPromise, txChainId_1, transactionHash_1, error_2, rpcError;
|
|
155
292
|
var _this = this;
|
|
293
|
+
var _c, _d, _e, _f;
|
|
156
294
|
var method = _b.method, params = _b.params;
|
|
157
|
-
return __generator(this, function (
|
|
158
|
-
switch (
|
|
295
|
+
return __generator(this, function (_g) {
|
|
296
|
+
switch (_g.label) {
|
|
159
297
|
case 0:
|
|
160
298
|
// Learn the chain from a call the APP was making anyway.
|
|
161
299
|
//
|
|
@@ -184,6 +322,10 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
184
322
|
logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
|
|
185
323
|
return [2 /*return*/, request({ method: method, params: params })];
|
|
186
324
|
}
|
|
325
|
+
if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, method, params)) {
|
|
326
|
+
// A pending wagmi mutation owns this capture.
|
|
327
|
+
return [2 /*return*/, request({ method: method, params: params })];
|
|
328
|
+
}
|
|
187
329
|
responsePromise = request({ method: method, params: params });
|
|
188
330
|
// Attach a no-op handler now so a rejection arriving before the await
|
|
189
331
|
// below is never reported as unhandled. The real handling is there.
|
|
@@ -196,7 +338,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
196
338
|
switch (_a.label) {
|
|
197
339
|
case 0:
|
|
198
340
|
_a.trys.push([0, 2, , 3]);
|
|
199
|
-
return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
|
|
341
|
+
return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
|
|
200
342
|
case 1:
|
|
201
343
|
_a.sent();
|
|
202
344
|
return [3 /*break*/, 3];
|
|
@@ -208,12 +350,12 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
208
350
|
}
|
|
209
351
|
});
|
|
210
352
|
}); })();
|
|
211
|
-
|
|
353
|
+
_g.label = 1;
|
|
212
354
|
case 1:
|
|
213
|
-
|
|
355
|
+
_g.trys.push([1, 3, , 4]);
|
|
214
356
|
return [4 /*yield*/, responsePromise];
|
|
215
357
|
case 2:
|
|
216
|
-
response_1 =
|
|
358
|
+
response_1 = _g.sent();
|
|
217
359
|
// Track signature confirmation only for truthy responses
|
|
218
360
|
if (response_1) {
|
|
219
361
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
@@ -222,7 +364,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
222
364
|
switch (_a.label) {
|
|
223
365
|
case 0:
|
|
224
366
|
_a.trys.push([0, 2, , 3]);
|
|
225
|
-
return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)))];
|
|
367
|
+
return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)), this.attributionFor(provider))];
|
|
226
368
|
case 1:
|
|
227
369
|
_a.sent();
|
|
228
370
|
return [3 /*break*/, 3];
|
|
@@ -237,9 +379,9 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
237
379
|
}
|
|
238
380
|
return [2 /*return*/, response_1];
|
|
239
381
|
case 3:
|
|
240
|
-
error_1 =
|
|
382
|
+
error_1 = _g.sent();
|
|
241
383
|
rpcError = error_1;
|
|
242
|
-
if ((rpcError
|
|
384
|
+
if (isUserRejectionError(rpcError)) {
|
|
243
385
|
// Use the already cast rpcError to avoid duplication
|
|
244
386
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
245
387
|
var e_3;
|
|
@@ -247,7 +389,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
247
389
|
switch (_a.label) {
|
|
248
390
|
case 0:
|
|
249
391
|
_a.trys.push([0, 2, , 3]);
|
|
250
|
-
return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
|
|
392
|
+
return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
|
|
251
393
|
case 1:
|
|
252
394
|
_a.sent();
|
|
253
395
|
return [3 /*break*/, 3];
|
|
@@ -279,6 +421,10 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
279
421
|
logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
|
|
280
422
|
return [2 /*return*/, request({ method: method, params: params })];
|
|
281
423
|
}
|
|
424
|
+
if ((_f = (_e = this.deps).shouldSkipRequestCapture) === null || _f === void 0 ? void 0 : _f.call(_e, method, params)) {
|
|
425
|
+
// A pending wagmi mutation owns this capture.
|
|
426
|
+
return [2 /*return*/, request({ method: method, params: params })];
|
|
427
|
+
}
|
|
282
428
|
txPromise = request({ method: method, params: params });
|
|
283
429
|
txPromise.catch(function () { return undefined; });
|
|
284
430
|
txChainId_1 = this.registry.resolveChainId(provider);
|
|
@@ -291,7 +437,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
291
437
|
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
292
438
|
case 1:
|
|
293
439
|
payload = _a.sent();
|
|
294
|
-
return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.STARTED }, payload))];
|
|
440
|
+
return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.STARTED }, payload), this.attributionFor(provider))];
|
|
295
441
|
case 2:
|
|
296
442
|
_a.sent();
|
|
297
443
|
return [3 /*break*/, 4];
|
|
@@ -303,12 +449,12 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
303
449
|
}
|
|
304
450
|
});
|
|
305
451
|
}); })();
|
|
306
|
-
|
|
452
|
+
_g.label = 5;
|
|
307
453
|
case 5:
|
|
308
|
-
|
|
454
|
+
_g.trys.push([5, 7, , 8]);
|
|
309
455
|
return [4 /*yield*/, txPromise];
|
|
310
456
|
case 6:
|
|
311
|
-
transactionHash_1 =
|
|
457
|
+
transactionHash_1 = _g.sent();
|
|
312
458
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
313
459
|
var payload, e_5;
|
|
314
460
|
return __generator(this, function (_a) {
|
|
@@ -318,7 +464,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
318
464
|
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
319
465
|
case 1:
|
|
320
466
|
payload = _a.sent();
|
|
321
|
-
return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }))];
|
|
467
|
+
return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }), this.attributionFor(provider))];
|
|
322
468
|
case 2:
|
|
323
469
|
_a.sent();
|
|
324
470
|
// Start async polling for transaction receipt
|
|
@@ -334,9 +480,9 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
334
480
|
}); })();
|
|
335
481
|
return [2 /*return*/, transactionHash_1];
|
|
336
482
|
case 7:
|
|
337
|
-
error_2 =
|
|
483
|
+
error_2 = _g.sent();
|
|
338
484
|
rpcError = error_2;
|
|
339
|
-
if ((rpcError
|
|
485
|
+
if (isUserRejectionError(rpcError)) {
|
|
340
486
|
// Use the already cast rpcError to avoid duplication
|
|
341
487
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
342
488
|
var payload, e_6;
|
|
@@ -347,7 +493,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
347
493
|
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
348
494
|
case 1:
|
|
349
495
|
payload = _a.sent();
|
|
350
|
-
return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.REJECTED }, payload))];
|
|
496
|
+
return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.REJECTED }, payload), this.attributionFor(provider))];
|
|
351
497
|
case 2:
|
|
352
498
|
_a.sent();
|
|
353
499
|
return [3 /*break*/, 4];
|
|
@@ -364,23 +510,19 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
364
510
|
case 8: return [2 /*return*/, request({ method: method, params: params })];
|
|
365
511
|
}
|
|
366
512
|
});
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
catch (e) {
|
|
381
|
-
logger.warn("Failed to wrap provider.request; skipping", e);
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
513
|
+
});
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* Wallet attribution for request-derived events.
|
|
517
|
+
*
|
|
518
|
+
* Live per read through the registry, so a WalletConnect session names
|
|
519
|
+
* its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
|
|
520
|
+
* rows had provider_name EMPTY on every signature and transaction, which
|
|
521
|
+
* made per-wallet activity unanswerable in the warehouse.
|
|
522
|
+
*/
|
|
523
|
+
EvmRequestTracker.prototype.attributionFor = function (provider) {
|
|
524
|
+
var info = this.registry.infoFor(provider);
|
|
525
|
+
return { providerName: info.name, rdns: info.rdns };
|
|
384
526
|
};
|
|
385
527
|
EvmRequestTracker.prototype.buildSignatureEventPayload = function (method, params,
|
|
386
528
|
// Intentionally not read. Kept for positional call-site arity.
|
|
@@ -476,7 +618,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
476
618
|
// status: 1 = success, 0 = reverted
|
|
477
619
|
if (receipt.status === "0x1" || receipt.status === 1) {
|
|
478
620
|
this.deps
|
|
479
|
-
.transaction(__assign(__assign({ status: TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }))
|
|
621
|
+
.transaction(__assign(__assign({ status: TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
|
|
480
622
|
.catch(function (e) {
|
|
481
623
|
return logger.error("Formo: Failed to track transaction confirmation", e);
|
|
482
624
|
});
|
|
@@ -484,7 +626,7 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
484
626
|
}
|
|
485
627
|
else if (receipt.status === "0x0" || receipt.status === 0) {
|
|
486
628
|
this.deps
|
|
487
|
-
.transaction(__assign(__assign({ status: TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }))
|
|
629
|
+
.transaction(__assign(__assign({ status: TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
|
|
488
630
|
.catch(function (e) {
|
|
489
631
|
return logger.error("Formo: Failed to track transaction revert", e);
|
|
490
632
|
});
|
|
@@ -527,10 +669,10 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
527
669
|
*/
|
|
528
670
|
EvmRequestTracker.prototype.trackBatchedCalls = function (provider, request, params) {
|
|
529
671
|
return __awaiter(this, void 0, void 0, function () {
|
|
530
|
-
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;
|
|
531
|
-
var _c;
|
|
532
|
-
return __generator(this, function (
|
|
533
|
-
switch (
|
|
672
|
+
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;
|
|
673
|
+
var _c, _d, _e;
|
|
674
|
+
return __generator(this, function (_f) {
|
|
675
|
+
switch (_f.label) {
|
|
534
676
|
case 0:
|
|
535
677
|
if (!this.deps.isAutocaptureEnabled("transaction")) {
|
|
536
678
|
logger.debug("Transaction event skipped (autocapture.transaction: false)", {
|
|
@@ -538,13 +680,17 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
538
680
|
});
|
|
539
681
|
return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
|
|
540
682
|
}
|
|
683
|
+
if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, "wallet_sendCalls", params)) {
|
|
684
|
+
// A pending wagmi sendCalls mutation owns this capture.
|
|
685
|
+
return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
|
|
686
|
+
}
|
|
541
687
|
sendPromise = request({ method: "wallet_sendCalls", params: params });
|
|
542
688
|
sendPromise.catch(function () { return undefined; });
|
|
543
689
|
batch = params[0];
|
|
544
690
|
calls = Array.isArray(batch === null || batch === void 0 ? void 0 : batch.calls) ? batch.calls : [];
|
|
545
691
|
declared = typeof (batch === null || batch === void 0 ? void 0 : batch.chainId) === "string" ? parseChainId(batch.chainId) : undefined;
|
|
546
692
|
chainId = declared || this.registry.resolveChainId(provider);
|
|
547
|
-
address = validateAndChecksumAddress((
|
|
693
|
+
address = validateAndChecksumAddress((_e = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _e !== void 0 ? _e : "");
|
|
548
694
|
if (!address) {
|
|
549
695
|
// Nothing can be attributed without a sender, and inventing one would
|
|
550
696
|
// be worse than reporting nothing. The user's call still goes through.
|
|
@@ -563,16 +709,14 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
563
709
|
if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
|
|
564
710
|
this.wallet.backfill(address, chainId, provider);
|
|
565
711
|
}
|
|
712
|
+
attribution = this.attributionFor(provider);
|
|
566
713
|
payloads = calls.map(function (call, index) { return ({
|
|
567
714
|
chainId: chainId,
|
|
568
715
|
address: address,
|
|
569
716
|
to: call === null || call === void 0 ? void 0 : call.to,
|
|
570
717
|
value: call === null || call === void 0 ? void 0 : call.value,
|
|
571
718
|
data: call === null || call === void 0 ? void 0 : call.data,
|
|
572
|
-
properties: {
|
|
573
|
-
batch_size: calls.length,
|
|
574
|
-
batch_index: index,
|
|
575
|
-
},
|
|
719
|
+
properties: __assign({ batch_size: calls.length, batch_index: index }, attribution),
|
|
576
720
|
}); });
|
|
577
721
|
// STARTED carries no batch id: the wallet has not issued one yet, exactly
|
|
578
722
|
// as `eth_sendTransaction` has no hash at this point. Position within the
|
|
@@ -584,12 +728,12 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
584
728
|
.transaction(__assign({ status: TransactionStatus.STARTED }, rest), properties)
|
|
585
729
|
.catch(function (e) { return logger.error("Formo: Failed to track batch call start", e); });
|
|
586
730
|
}
|
|
587
|
-
|
|
731
|
+
_f.label = 1;
|
|
588
732
|
case 1:
|
|
589
|
-
|
|
733
|
+
_f.trys.push([1, 3, , 4]);
|
|
590
734
|
return [4 /*yield*/, sendPromise];
|
|
591
735
|
case 2:
|
|
592
|
-
result =
|
|
736
|
+
result = _f.sent();
|
|
593
737
|
batchId = readBatchId(result);
|
|
594
738
|
for (_a = 0, payloads_2 = payloads; _a < payloads_2.length; _a++) {
|
|
595
739
|
p = payloads_2[_a];
|
|
@@ -604,9 +748,9 @@ var EvmRequestTracker = /** @class */ (function () {
|
|
|
604
748
|
this.pollBatchStatus(provider, batchId, payloads);
|
|
605
749
|
return [2 /*return*/, result];
|
|
606
750
|
case 3:
|
|
607
|
-
error_3 =
|
|
751
|
+
error_3 = _f.sent();
|
|
608
752
|
rpcError = error_3;
|
|
609
|
-
if ((rpcError
|
|
753
|
+
if (isUserRejectionError(rpcError)) {
|
|
610
754
|
// One rejection dismisses the whole prompt, so every call in it is
|
|
611
755
|
// rejected. Reporting only the first would undercount.
|
|
612
756
|
for (_b = 0, payloads_3 = payloads; _b < payloads_3.length; _b++) {
|
|
@@ -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
|
*
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider detection utilities for identifying wallet providers
|
|
3
3
|
*/
|
|
4
|
+
var __assign = (this && this.__assign) || function () {
|
|
5
|
+
__assign = Object.assign || function(t) {
|
|
6
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
7
|
+
s = arguments[i];
|
|
8
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
9
|
+
t[p] = s[p];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
return __assign.apply(this, arguments);
|
|
14
|
+
};
|
|
4
15
|
/**
|
|
5
16
|
* Default icon for providers without custom icons
|
|
6
17
|
*/
|
|
@@ -76,6 +87,55 @@ export function detectInjectedProviderInfo(provider) {
|
|
|
76
87
|
icon: DEFAULT_PROVIDER_ICON,
|
|
77
88
|
};
|
|
78
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Was this error the USER declining, whatever transport delivered it?
|
|
92
|
+
*
|
|
93
|
+
* Three dialects say "the user said no":
|
|
94
|
+
* - EIP-1193: code 4001 (UserRejectedRequest).
|
|
95
|
+
* - WalletConnect sdkErrors: codes 5000-5005 (USER_REJECTED and its
|
|
96
|
+
* variants). A LIVE MetaMask Mobile session rejecting a transaction
|
|
97
|
+
* produced one of these and the SDK's 4001-only match reported nothing -
|
|
98
|
+
* every WalletConnect rejection was silently uncounted.
|
|
99
|
+
* - viem: a typed UserRejectedRequestError, sometimes without the numeric
|
|
100
|
+
* code surviving the wrapping.
|
|
101
|
+
*
|
|
102
|
+
* The real code often hides under `cause` (viem nests, WC wraps), so the
|
|
103
|
+
* chain is walked a few levels.
|
|
104
|
+
*/
|
|
105
|
+
export function isUserRejectionError(error) {
|
|
106
|
+
var cursor = error;
|
|
107
|
+
for (var depth = 0; cursor && depth < 5; depth++) {
|
|
108
|
+
var code = cursor.code;
|
|
109
|
+
if (code === 4001 ||
|
|
110
|
+
(typeof code === "number" && code >= 5000 && code <= 5005) ||
|
|
111
|
+
cursor.name === "UserRejectedRequestError") {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
cursor = cursor.cause;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The wallet on the far side of a WalletConnect session.
|
|
120
|
+
*
|
|
121
|
+
* WalletConnect is a transport, not a wallet: the signing wallet (Ledger
|
|
122
|
+
* Live, MetaMask Mobile, Safe, ...) identifies itself in the session's peer
|
|
123
|
+
* metadata. Reporting only "WalletConnect" hides every wallet behind it -
|
|
124
|
+
* production showed Ledger at effectively zero while its sessions were being
|
|
125
|
+
* tracked under the transport's name. Reads synchronous state only; never
|
|
126
|
+
* issues an RPC.
|
|
127
|
+
*/
|
|
128
|
+
export function readWalletConnectPeer(provider) {
|
|
129
|
+
var _a;
|
|
130
|
+
var session = provider.session;
|
|
131
|
+
var metadata = (_a = session === null || session === void 0 ? void 0 : session.peer) === null || _a === void 0 ? void 0 : _a.metadata;
|
|
132
|
+
if (!metadata || typeof metadata.name !== "string" || metadata.name.length === 0) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return __assign({ name: metadata.name }, (typeof metadata.url === "string" && metadata.url.length > 0
|
|
136
|
+
? { url: metadata.url }
|
|
137
|
+
: {}));
|
|
138
|
+
}
|
|
79
139
|
/**
|
|
80
140
|
* Validates that a provider implements the required EIP-1193 interface
|
|
81
141
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider-related exports
|
|
3
3
|
*/
|
|
4
|
-
export { detectInjectedProviderInfo, isValidProvider, DEFAULT_PROVIDER_ICON, } from './detection';
|
|
4
|
+
export { detectInjectedProviderInfo, isValidProvider, readWalletConnectPeer, isUserRejectionError, DEFAULT_PROVIDER_ICON, } from './detection';
|
|
5
5
|
export type { WalletProviderFlags, ProviderInfo, } from './detection';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider-related exports
|
|
3
3
|
*/
|
|
4
|
-
export { detectInjectedProviderInfo, isValidProvider, DEFAULT_PROVIDER_ICON, } from './detection';
|
|
4
|
+
export { detectInjectedProviderInfo, isValidProvider, readWalletConnectPeer, isUserRejectionError, DEFAULT_PROVIDER_ICON, } from './detection';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|