@hashspan/core 0.7.0 → 0.8.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/README.md +23 -12
- package/dist/index.cjs +297 -50
- package/dist/index.d.cts +175 -27
- package/dist/index.d.mts +175 -27
- package/dist/index.mjs +287 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Payments that another party settles on chain, such as x402 payments, become a **
|
|
|
12
12
|
of a `send` span.
|
|
13
13
|
|
|
14
14
|
The core is library-agnostic and read-only: it never signs, sends or fetches anything. Adapters such as
|
|
15
|
-
[`@hashspan/viem`](https://github.com/selimaytac/hashspan/tree/@hashspan/core@0.
|
|
15
|
+
[`@hashspan/viem`](https://github.com/selimaytac/hashspan/tree/@hashspan/core@0.8.0/packages/viem) call it for you. Use the core directly to instrument any other send path.
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -69,14 +69,23 @@ stays the class name.
|
|
|
69
69
|
`tracker.startPayment({ chainId, protocol, payer, recipient, asset, amount })` records a payment that another
|
|
70
70
|
party settles on chain, such as an x402 facilitator, as a `payment {chainId}` span; end it with
|
|
71
71
|
`end({ status, hash })` or `fail(error)`. A settlement with a hash links the transaction's confirm span to the payment
|
|
72
|
-
span ([ADR 0013](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
span ([ADR 0013](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0013-x402-payments.md)).
|
|
73
|
+
|
|
74
|
+
`tracker.startUserOperationSend({ chainId, sender, entryPoint, callCount })` records a user operation of an ERC-4337
|
|
75
|
+
smart account handed to a bundler as a `send {chainId}` span; end it with `end({ userOpHash })` or `fail(error)`.
|
|
76
|
+
`tracker.startUserOperationConfirm({ chainId, userOpHash })` joins its confirm span, as `startConfirm` does for a
|
|
77
|
+
transaction, and `end(receipt)` takes the operation's receipt: `success`, `actualGasCost`, `actualGasUsed`, `sender`,
|
|
78
|
+
`nonce`, `paymaster`, `entryPoint`, `revertReason`, and the bundle transaction's `transactionHash` and `blockNumber`.
|
|
79
|
+
A receipt with `success: false` ends the span with `error.type` `reverted`; the bundle transaction's status and fee
|
|
80
|
+
are not recorded, since they cover every operation in the bundle
|
|
81
|
+
([ADR 0021](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0021-user-operations.md)).
|
|
82
|
+
|
|
83
|
+
All of these calls accept an explicit parent `Context` as a second argument. An integration that learns about a call only
|
|
75
84
|
after it started can record it after the fact: pass `startTime` in the input and `endTime` in the options of the
|
|
76
|
-
handle method, e.g. `send.end({ hash }, { endTime })` ([ADR 0009](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
85
|
+
handle method, e.g. `send.end({ hash }, { endTime })` ([ADR 0009](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0009-telemetry-off-the-call-path.md)). Every method is safe to call: failures inside
|
|
77
86
|
the instrumentation are reported through `diag` and never thrown into your code. The positional forms of earlier
|
|
78
87
|
releases, `send.end(hash, endTime)` and `send.fail(error, endTime, { errorType })`, still work and are deprecated
|
|
79
|
-
until 1.0 ([ADR 0014](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
88
|
+
until 1.0 ([ADR 0014](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0014-core-api-boundary.md)).
|
|
80
89
|
|
|
81
90
|
## Options
|
|
82
91
|
|
|
@@ -85,7 +94,7 @@ until 1.0 ([ADR 0014](https://github.com/selimaytac/hashspan/blob/@hashspan/core
|
|
|
85
94
|
| `tracerProvider` | global provider | Tracer provider to use |
|
|
86
95
|
| `meterProvider` | global provider | Meter provider for the [metrics](#metrics) |
|
|
87
96
|
| `address` | `'raw'` | `'raw'`, `'hashed'`, `'off'`, or `{ mode: 'hashed', hash: (address) => string }` |
|
|
88
|
-
| `errorMessages` | `'off'` | What failed spans record about the error: `'off'` (type only), `'sanitized'` (first line, addresses per `address` mode, calldata removed; in `hashed` and `off` mode any hex longer than an address) or `'raw'` (full message and stack trace). `'raw'` can record RPC URLs that include API keys, as some libraries put the request URL in the message; `'sanitized'` keeps only the first line (viem puts the URL on a later line), which is best effort. See [ADR 0006](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
97
|
+
| `errorMessages` | `'off'` | What failed spans record about the error: `'off'` (type only), `'sanitized'` (first line, addresses per `address` mode, calldata removed; in `hashed` and `off` mode any hex longer than an address) or `'raw'` (full message and stack trace). `'raw'` can record RPC URLs that include API keys, as some libraries put the request URL in the message; `'sanitized'` keeps only the first line (viem puts the URL on a later line), which is best effort. See [ADR 0006](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0006-error-privacy.md) |
|
|
89
98
|
| `paymentResource` | `'origin'` | How much of a paid resource's URL `x402.resource` records: `'origin'` (scheme, host and port; nothing for a resource that is not a URL), `'path'` (also the path, never the query string, fragment or user info) or `'off'`. Paths often carry user or account identifiers. At most 512 characters are recorded |
|
|
90
99
|
| `recordFunctionArguments` | `false` | Record `functionArguments` as a JSON array in `blockchain.contract.function.arguments`: bigints as decimal strings, addresses per `address` mode (longer hex values become `<hex>` in `hashed` and `off` mode), at most 4096 characters. Reads only own enumerable data properties: `toJSON()` and getters are never called, so a `Date` records as `{}`; a Proxy's traps still run |
|
|
91
100
|
| `agent` | none | Agent `{ id, name }`; a field set here always wins, unset fields come from the Baggage entries `gen_ai.agent.id` / `gen_ai.agent.name` |
|
|
@@ -97,20 +106,22 @@ until 1.0 ([ADR 0014](https://github.com/selimaytac/hashspan/blob/@hashspan/core
|
|
|
97
106
|
## What is recorded
|
|
98
107
|
|
|
99
108
|
Chain id, transaction hash, sender/recipient (per `address` mode), value, nonce, function name and selector, and,
|
|
100
|
-
on confirmation, status, block number, gas used, effective gas price, L1 fee, total fee and revert reason.
|
|
109
|
+
on confirmation, status, block number, gas used, effective gas price, L1 fee, total fee and revert reason. For user operations: their hash,
|
|
110
|
+
smart account, EntryPoint, number of calls, success, gas used, cost, nonce and paymaster. Decoded
|
|
101
111
|
call arguments are recorded only with `recordFunctionArguments`, and error messages only with `errorMessages`.
|
|
102
112
|
Attribute definitions:
|
|
103
|
-
[docs/semconv.md](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
113
|
+
[docs/semconv.md](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/semconv.md).
|
|
104
114
|
|
|
105
115
|
## Metrics
|
|
106
116
|
|
|
107
117
|
With an OpenTelemetry metrics SDK set up (or `meterProvider`), the tracker records three histograms:
|
|
108
118
|
`blockchain.client.send.duration` and `blockchain.client.confirmation.duration` in seconds, and
|
|
109
119
|
`blockchain.client.fee` in wei. Their attributes are the chain and the outcome only, never an address, hash or
|
|
110
|
-
agent identity
|
|
120
|
+
agent identity; an `error.type` that is neither an error class name nor a lower-case code is recorded as `_OTHER`.
|
|
121
|
+
The `redact` hook does not run on metrics: a fee it removes from spans is still recorded by
|
|
111
122
|
`blockchain.client.fee`. To keep a histogram out of your backend, drop it with a View of your metrics SDK (drop
|
|
112
123
|
aggregation). Definitions:
|
|
113
|
-
[docs/semconv.md](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
124
|
+
[docs/semconv.md](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/semconv.md#metrics).
|
|
114
125
|
|
|
115
126
|
## Privacy notes
|
|
116
127
|
|
|
@@ -125,7 +136,7 @@ aggregation). Definitions:
|
|
|
125
136
|
propagated, or strip the entries before outbound calls.
|
|
126
137
|
- **Inbound Baggage can claim an identity.** A caller can send Baggage entries with any agent id. A field set in the
|
|
127
138
|
`agent` option cannot be overridden that way; to ignore identity from Baggage entirely, set `agentFromBaggage: false`
|
|
128
|
-
([ADR 0011](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
139
|
+
([ADR 0011](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0011-agent-identity-precedence.md)).
|
|
129
140
|
- The redaction hook (`redact`) runs last on every span attribute set and on exception attributes; use it for
|
|
130
141
|
anything else your policy forbids. It does not run on [metrics](#metrics), which carry no address or hash.
|
|
131
142
|
- **Your callbacks' errors go to the diagnostic logger.** If a custom `hash` function or the `redact` hook throws,
|
package/dist/index.cjs
CHANGED
|
@@ -6,7 +6,7 @@ const ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name";
|
|
|
6
6
|
/**
|
|
7
7
|
* Agent identity as GenAI attributes. A field set in the static identity always wins; Baggage, which a remote caller
|
|
8
8
|
* can set, only fills fields it leaves unset, and is not read at all with `fromBaggage` false
|
|
9
|
-
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
9
|
+
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0011-agent-identity-precedence.md).
|
|
10
10
|
*/
|
|
11
11
|
function agentAttributes(ctx, identity, fromBaggage = true) {
|
|
12
12
|
const baggage = fromBaggage ? _opentelemetry_api.propagation.getBaggage(ctx) : void 0;
|
|
@@ -22,7 +22,7 @@ function agentAttributes(ctx, identity, fromBaggage = true) {
|
|
|
22
22
|
/**
|
|
23
23
|
* Attribute keys emitted by hashspan.
|
|
24
24
|
*
|
|
25
|
-
* Stability: development. See https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
25
|
+
* Stability: development. See https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/semconv.md for
|
|
26
26
|
* definitions and value types. These names are a public contract: changes follow the deprecation policy in AGENTS.md.
|
|
27
27
|
*/
|
|
28
28
|
const ATTR_BLOCKCHAIN_SYSTEM = "blockchain.system";
|
|
@@ -46,12 +46,12 @@ const ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_NAME = "blockchain.contract.function.nam
|
|
|
46
46
|
const ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_SELECTOR = "blockchain.contract.function.selector";
|
|
47
47
|
/**
|
|
48
48
|
* Opt-in: decoded call arguments as a JSON array. See
|
|
49
|
-
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
49
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0004-privacy-defaults.md.
|
|
50
50
|
*/
|
|
51
51
|
const ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_ARGUMENTS = "blockchain.contract.function.arguments";
|
|
52
52
|
/**
|
|
53
53
|
* Payments settled on chain by a party other than the agent, e.g. an x402 facilitator. See
|
|
54
|
-
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
54
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0013-x402-payments.md.
|
|
55
55
|
*/
|
|
56
56
|
const ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL = "blockchain.payment.protocol";
|
|
57
57
|
const ATTR_BLOCKCHAIN_PAYMENT_PAYER = "blockchain.payment.payer";
|
|
@@ -64,12 +64,33 @@ const ATTR_BLOCKCHAIN_PAYMENT_SETTLED_AMOUNT = "blockchain.payment.settled_amoun
|
|
|
64
64
|
/**
|
|
65
65
|
* Whether the settlement transaction's receipt carries the payment, as checked by the adapter; absent when no check
|
|
66
66
|
* was possible. See
|
|
67
|
-
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
67
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0017-x402-payment-verification.md.
|
|
68
68
|
*/
|
|
69
69
|
const ATTR_BLOCKCHAIN_PAYMENT_VERIFIED = "blockchain.payment.verified";
|
|
70
70
|
/** x402's own payment fields. */
|
|
71
71
|
const ATTR_X402_SCHEME = "x402.scheme";
|
|
72
72
|
const ATTR_X402_RESOURCE = "x402.resource";
|
|
73
|
+
/**
|
|
74
|
+
* User operations of ERC-4337 smart accounts. See
|
|
75
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0021-user-operations.md.
|
|
76
|
+
*/
|
|
77
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_HASH = "blockchain.user_operation.hash";
|
|
78
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_SENDER = "blockchain.user_operation.sender";
|
|
79
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_ENTRY_POINT = "blockchain.user_operation.entry_point";
|
|
80
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_CALL_COUNT = "blockchain.user_operation.call_count";
|
|
81
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_NONCE = "blockchain.user_operation.nonce";
|
|
82
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS = "blockchain.user_operation.success";
|
|
83
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_GAS_USED = "blockchain.user_operation.gas.used";
|
|
84
|
+
/** What the operation itself paid, in wei; the bundle transaction's fee covers every operation in the bundle. */
|
|
85
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_GAS_COST = "blockchain.user_operation.gas.cost";
|
|
86
|
+
const ATTR_BLOCKCHAIN_USER_OPERATION_PAYMASTER = "blockchain.user_operation.paymaster";
|
|
87
|
+
/**
|
|
88
|
+
* Metrics only: what a send, confirmation or fee sample is about. Recorded as `user_operation` on samples of user
|
|
89
|
+
* operations, and absent on those of transactions.
|
|
90
|
+
*/
|
|
91
|
+
const ATTR_BLOCKCHAIN_OPERATION_SUBJECT = "blockchain.operation.subject";
|
|
92
|
+
/** Values for {@link ATTR_BLOCKCHAIN_OPERATION_SUBJECT}. */
|
|
93
|
+
const BLOCKCHAIN_OPERATION_SUBJECT_VALUE_USER_OPERATION = "user_operation";
|
|
73
94
|
/** Values for {@link ATTR_BLOCKCHAIN_SYSTEM}. */
|
|
74
95
|
const BLOCKCHAIN_SYSTEM_VALUE_EVM = "evm";
|
|
75
96
|
/** Values for {@link ATTR_BLOCKCHAIN_OPERATION_NAME}. */
|
|
@@ -88,7 +109,7 @@ const BLOCKCHAIN_TX_STATUS_VALUE_REVERTED = "reverted";
|
|
|
88
109
|
/**
|
|
89
110
|
* @deprecated No longer recorded: a confirm span that gave up waiting records `error.type` `timeout` and no
|
|
90
111
|
* `blockchain.tx.status`. The constant is removed in 1.0. See
|
|
91
|
-
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
112
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0016-timeout-is-an-observer-outcome.md.
|
|
92
113
|
*/
|
|
93
114
|
const BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT = "timeout";
|
|
94
115
|
const BLOCKCHAIN_TX_STATUS_VALUE_REPLACED = "replaced";
|
|
@@ -102,11 +123,20 @@ const ATTR_ERROR_TYPE = "error.type";
|
|
|
102
123
|
const ERROR_TYPE_VALUE_OTHER = "_OTHER";
|
|
103
124
|
//#endregion
|
|
104
125
|
//#region src/metrics.ts
|
|
105
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Duration of a send of a transaction or user operation: from the start of the sending call until the hash is known or
|
|
128
|
+
* the call failed.
|
|
129
|
+
*/
|
|
106
130
|
const METRIC_BLOCKCHAIN_CLIENT_SEND_DURATION = "blockchain.client.send.duration";
|
|
107
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Duration of a confirmation of a transaction or user operation: from the start of the wait until the receipt, a
|
|
133
|
+
* timeout or a failure.
|
|
134
|
+
*/
|
|
108
135
|
const METRIC_BLOCKCHAIN_CLIENT_CONFIRMATION_DURATION = "blockchain.client.confirmation.duration";
|
|
109
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Total fee of a mined transaction (execution fee plus L1 data fee), or the cost of a user operation, in the chain's
|
|
138
|
+
* smallest unit (wei).
|
|
139
|
+
*/
|
|
110
140
|
const METRIC_BLOCKCHAIN_CLIENT_FEE = "blockchain.client.fee";
|
|
111
141
|
const DURATION_BUCKETS = [
|
|
112
142
|
.05,
|
|
@@ -124,6 +154,21 @@ const DURATION_BUCKETS = [
|
|
|
124
154
|
300
|
|
125
155
|
];
|
|
126
156
|
const FEE_BUCKETS = Array.from({ length: 11 }, (_, i) => 10 ** (i + 8));
|
|
157
|
+
const ERROR_CLASS = /^[A-Z][A-Za-z]{0,62}Error$|^Error$/;
|
|
158
|
+
const ERROR_CODE = /^[a-z]{1,32}(_[a-z]{1,32}){0,7}$/;
|
|
159
|
+
/**
|
|
160
|
+
* `attributes` with `error.type` kept only if it is an error class name or a lower-case code, else `_OTHER`. Error
|
|
161
|
+
* names are free text (any `Error.name`), and metrics do not pass through the `redact` hook; the span keeps its own
|
|
162
|
+
* `error.type`.
|
|
163
|
+
*/
|
|
164
|
+
function withMetricErrorType(attributes) {
|
|
165
|
+
const type = attributes[ATTR_ERROR_TYPE];
|
|
166
|
+
if (type === void 0) return attributes;
|
|
167
|
+
return typeof type === "string" && (type === "_OTHER" || ERROR_CLASS.test(type) || ERROR_CODE.test(type)) ? attributes : {
|
|
168
|
+
...attributes,
|
|
169
|
+
[ATTR_ERROR_TYPE]: ERROR_TYPE_VALUE_OTHER
|
|
170
|
+
};
|
|
171
|
+
}
|
|
127
172
|
/** Milliseconds since the epoch of a span time, as the OpenTelemetry API accepts it. */
|
|
128
173
|
function toEpochMs(time) {
|
|
129
174
|
if (time === void 0) return Date.now();
|
|
@@ -141,17 +186,17 @@ function createTxMetrics(meterProvider, name, version) {
|
|
|
141
186
|
return {
|
|
142
187
|
send: meter.createHistogram(METRIC_BLOCKCHAIN_CLIENT_SEND_DURATION, {
|
|
143
188
|
unit: "s",
|
|
144
|
-
description: "Duration of sending a transaction, until its hash is known",
|
|
189
|
+
description: "Duration of sending a transaction or user operation, until its hash is known",
|
|
145
190
|
advice: { explicitBucketBoundaries: DURATION_BUCKETS }
|
|
146
191
|
}),
|
|
147
192
|
confirmation: meter.createHistogram(METRIC_BLOCKCHAIN_CLIENT_CONFIRMATION_DURATION, {
|
|
148
193
|
unit: "s",
|
|
149
|
-
description: "Duration of waiting for a transaction receipt",
|
|
194
|
+
description: "Duration of waiting for a transaction or user operation receipt",
|
|
150
195
|
advice: { explicitBucketBoundaries: DURATION_BUCKETS }
|
|
151
196
|
}),
|
|
152
197
|
fee: meter.createHistogram(METRIC_BLOCKCHAIN_CLIENT_FEE, {
|
|
153
198
|
unit: "{wei}",
|
|
154
|
-
description: "Total fee of a mined transaction",
|
|
199
|
+
description: "Total fee of a mined transaction, or the cost of a user operation",
|
|
155
200
|
advice: { explicitBucketBoundaries: FEE_BUCKETS }
|
|
156
201
|
})
|
|
157
202
|
};
|
|
@@ -167,10 +212,10 @@ function createTxMetrics(meterProvider, name, version) {
|
|
|
167
212
|
};
|
|
168
213
|
return {
|
|
169
214
|
sendDuration: (seconds, attributes) => record("send duration", () => {
|
|
170
|
-
if (seconds >= 0) get().send.record(seconds, attributes);
|
|
215
|
+
if (seconds >= 0) get().send.record(seconds, withMetricErrorType(attributes));
|
|
171
216
|
}),
|
|
172
217
|
confirmationDuration: (seconds, attributes) => record("confirmation duration", () => {
|
|
173
|
-
if (seconds >= 0) get().confirmation.record(seconds, attributes);
|
|
218
|
+
if (seconds >= 0) get().confirmation.record(seconds, withMetricErrorType(attributes));
|
|
174
219
|
}),
|
|
175
220
|
fee: (wei, attributes) => record("fee", () => {
|
|
176
221
|
if (wei >= 0n) get().fee.record(Number(wei), attributes);
|
|
@@ -463,7 +508,7 @@ function sanitizeResource(resource) {
|
|
|
463
508
|
}
|
|
464
509
|
//#endregion
|
|
465
510
|
//#region src/version.ts
|
|
466
|
-
const VERSION = "0.
|
|
511
|
+
const VERSION = "0.8.0";
|
|
467
512
|
//#endregion
|
|
468
513
|
//#region src/tracker.ts
|
|
469
514
|
const INSTRUMENTATION_NAME = "@hashspan/core";
|
|
@@ -486,13 +531,20 @@ const NON_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
486
531
|
ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL,
|
|
487
532
|
ATTR_BLOCKCHAIN_PAYMENT_STATUS,
|
|
488
533
|
ATTR_BLOCKCHAIN_PAYMENT_VERIFIED,
|
|
534
|
+
ATTR_BLOCKCHAIN_USER_OPERATION_HASH,
|
|
535
|
+
ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS,
|
|
489
536
|
ATTR_ERROR_TYPE,
|
|
490
537
|
ATTR_EXCEPTION_TYPE
|
|
491
538
|
]);
|
|
492
539
|
const TX_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
493
540
|
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
541
|
+
const ZERO_ADDRESS = /^0x0{40}$/;
|
|
494
542
|
/** A non-negative integer that fits in 256 bits. */
|
|
495
543
|
const AMOUNT = /^(0|[1-9][0-9]{0,77})$/;
|
|
544
|
+
/** A `0x` hex quantity of at most 256 bits, as JSON-RPC encodes integers. */
|
|
545
|
+
const HEX_QUANTITY = /^0x[0-9a-fA-F]{1,64}$/;
|
|
546
|
+
const MAX_UINT256 = 2n ** 256n - 1n;
|
|
547
|
+
const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
|
|
496
548
|
/** `error.type` of a wait that gave up: a confirmation or a payment whose outcome was never learned. */
|
|
497
549
|
const OBSERVER_TIMEOUT = "timeout";
|
|
498
550
|
const PAYMENT_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -511,6 +563,11 @@ const noopSend = (parent) => ({
|
|
|
511
563
|
end: () => {},
|
|
512
564
|
fail: () => {}
|
|
513
565
|
});
|
|
566
|
+
const noopUserOperationSend = (parent) => ({
|
|
567
|
+
context: parent,
|
|
568
|
+
end: () => {},
|
|
569
|
+
fail: () => {}
|
|
570
|
+
});
|
|
514
571
|
const NOOP_PAYMENT = {
|
|
515
572
|
end: () => {},
|
|
516
573
|
fail: () => {},
|
|
@@ -522,6 +579,11 @@ const NOOP_CONFIRM = {
|
|
|
522
579
|
timeout: () => {},
|
|
523
580
|
fail: () => {}
|
|
524
581
|
};
|
|
582
|
+
const NOOP_USER_OPERATION_CONFIRM = {
|
|
583
|
+
end: () => {},
|
|
584
|
+
timeout: () => {},
|
|
585
|
+
fail: () => {}
|
|
586
|
+
};
|
|
525
587
|
/** Runs `fn`, logging instead of throwing: instrumentation must never break the caller. */
|
|
526
588
|
function safely(what, fn, fallback) {
|
|
527
589
|
try {
|
|
@@ -544,6 +606,22 @@ const ERROR_TYPE_OVERRIDE = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
|
544
606
|
function identifier(value) {
|
|
545
607
|
return typeof value === "string" && ERROR_TYPE_OVERRIDE.test(value) ? value : void 0;
|
|
546
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* A non-negative integer of at most 256 bits, from a bigint, a safe integer, or a decimal or `0x` hex string, as
|
|
611
|
+
* bundlers return them; undefined for anything else.
|
|
612
|
+
*/
|
|
613
|
+
function quantity(value) {
|
|
614
|
+
let parsed;
|
|
615
|
+
if (typeof value === "bigint") parsed = value;
|
|
616
|
+
else if (typeof value === "number" && Number.isSafeInteger(value)) parsed = BigInt(value);
|
|
617
|
+
else if (typeof value === "string" && (AMOUNT.test(value) || HEX_QUANTITY.test(value))) parsed = BigInt(value);
|
|
618
|
+
return parsed !== void 0 && parsed >= 0n && parsed <= MAX_UINT256 ? parsed : void 0;
|
|
619
|
+
}
|
|
620
|
+
/** A quantity as a number, or undefined when it is none or too large to be one exactly. */
|
|
621
|
+
function smallQuantity(value) {
|
|
622
|
+
const parsed = quantity(value);
|
|
623
|
+
return parsed !== void 0 && parsed <= MAX_SAFE_INTEGER ? Number(parsed) : void 0;
|
|
624
|
+
}
|
|
547
625
|
/** A decimal amount, or undefined when `value` is not a non-negative integer. */
|
|
548
626
|
function amount(value) {
|
|
549
627
|
const text = typeof value === "bigint" ? value.toString() : value;
|
|
@@ -589,8 +667,44 @@ function reportedErrorType(error, options) {
|
|
|
589
667
|
return errorType(error);
|
|
590
668
|
}
|
|
591
669
|
/**
|
|
670
|
+
* Joins the confirm span for `hash` in `registry`, opening it with `open` for the first handle; undefined when the
|
|
671
|
+
* key recently got a receipt. A receipt from any handle ends the span; a timeout or failure only ends it when it is the
|
|
672
|
+
* last handle still waiting.
|
|
673
|
+
*/
|
|
674
|
+
function joinConfirm(registry, chainId, hash, open) {
|
|
675
|
+
const current = registry.get(chainId, hash);
|
|
676
|
+
if (current === "settled") return void 0;
|
|
677
|
+
let confirm = current;
|
|
678
|
+
if (!confirm) {
|
|
679
|
+
confirm = open();
|
|
680
|
+
registry.start(chainId, hash, confirm);
|
|
681
|
+
}
|
|
682
|
+
const shared = confirm;
|
|
683
|
+
shared.active += 1;
|
|
684
|
+
let done = false;
|
|
685
|
+
return {
|
|
686
|
+
shared,
|
|
687
|
+
receive: () => {
|
|
688
|
+
if (done || shared.ended) return false;
|
|
689
|
+
done = true;
|
|
690
|
+
shared.active -= 1;
|
|
691
|
+
shared.ended = true;
|
|
692
|
+
return true;
|
|
693
|
+
},
|
|
694
|
+
withdraw: (end) => {
|
|
695
|
+
if (done || shared.ended) return;
|
|
696
|
+
done = true;
|
|
697
|
+
shared.active -= 1;
|
|
698
|
+
if (shared.active > 0) return;
|
|
699
|
+
shared.ended = true;
|
|
700
|
+
registry.release(chainId, hash, shared);
|
|
701
|
+
end();
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
592
706
|
* Creates a tracker that records transactions as `send` and `confirm` spans, and payments as `payment` spans, with
|
|
593
|
-
* `@opentelemetry/api` (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
707
|
+
* `@opentelemetry/api` (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/semconv.md). It makes
|
|
594
708
|
* no network calls; the caller passes hashes and receipts. Its methods and handles never throw: failures are logged
|
|
595
709
|
* via `diag`, and a method that fails returns a handle that records nothing.
|
|
596
710
|
*/
|
|
@@ -606,6 +720,14 @@ function createTxTracker(options = {}) {
|
|
|
606
720
|
const formatAddress = safely("configure address mode", () => resolveAddressFormatter(options.address), OFF_ADDRESS_FORMATTER);
|
|
607
721
|
const errorMessages = safely("configure error message mode", () => resolveErrorMessageMode(options.errorMessages), "off");
|
|
608
722
|
const paymentResource = safely("configure payment resource mode", () => resolvePaymentResourceMode(options.paymentResource), "off");
|
|
723
|
+
const userOperationLinks = new LinkStore({
|
|
724
|
+
ttlMs: options.linkTtlMs ?? DEFAULT_LINK_TTL_MS,
|
|
725
|
+
maxEntries: options.maxTrackedTransactions ?? DEFAULT_MAX_TRACKED
|
|
726
|
+
});
|
|
727
|
+
const userOperationConfirmations = new ConfirmRegistry({
|
|
728
|
+
ttlMs: options.linkTtlMs ?? DEFAULT_LINK_TTL_MS,
|
|
729
|
+
maxEntries: options.maxTrackedTransactions ?? DEFAULT_MAX_TRACKED
|
|
730
|
+
});
|
|
609
731
|
const txMetrics = createTxMetrics(options.meterProvider, INSTRUMENTATION_NAME, VERSION);
|
|
610
732
|
/** Attributes of a metric: low-cardinality only, never an address, hash or agent identity. */
|
|
611
733
|
const metricAttributes = (chainId, extra = {}) => ({
|
|
@@ -613,6 +735,11 @@ function createTxTracker(options = {}) {
|
|
|
613
735
|
[ATTR_BLOCKCHAIN_CHAIN_ID]: chainId,
|
|
614
736
|
...extra
|
|
615
737
|
});
|
|
738
|
+
/** Metric attributes of a user operation sample: as for transactions, plus what the sample is about. */
|
|
739
|
+
const userOperationMetricAttributes = (chainId, extra = {}) => metricAttributes(chainId, {
|
|
740
|
+
[ATTR_BLOCKCHAIN_OPERATION_SUBJECT]: BLOCKCHAIN_OPERATION_SUBJECT_VALUE_USER_OPERATION,
|
|
741
|
+
...extra
|
|
742
|
+
});
|
|
616
743
|
const secondsSince = (startMs, endTime) => (toEpochMs(endTime) - startMs) / 1e3;
|
|
617
744
|
let tracer;
|
|
618
745
|
const getTracer = () => {
|
|
@@ -638,7 +765,7 @@ function createTxTracker(options = {}) {
|
|
|
638
765
|
/**
|
|
639
766
|
* Exception event attributes for `error`, per the error message mode. The error object itself is never handed to
|
|
640
767
|
* the SDK: its message and stack can carry addresses and calldata
|
|
641
|
-
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
768
|
+
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0006-error-privacy.md).
|
|
642
769
|
*/
|
|
643
770
|
const exceptionAttributes = (type, error) => {
|
|
644
771
|
const attributes = { [ATTR_EXCEPTION_TYPE]: type };
|
|
@@ -693,8 +820,8 @@ function createTxTracker(options = {}) {
|
|
|
693
820
|
const formatted = formatAddress(address);
|
|
694
821
|
if (formatted !== void 0) attributes[key] = formatted;
|
|
695
822
|
};
|
|
696
|
-
/** Records `address` only if it is one: payment addresses come from remote parties. */
|
|
697
|
-
const
|
|
823
|
+
/** Records `address` only if it is one: payment and user operation addresses come from remote parties. */
|
|
824
|
+
const setRemoteAddress = (attributes, key, address) => {
|
|
698
825
|
if (typeof address === "string" && ADDRESS.test(address)) setAddress(attributes, key, address);
|
|
699
826
|
};
|
|
700
827
|
const baseAttributes = (chainId, operation, ctx) => ({
|
|
@@ -830,7 +957,7 @@ function createTxTracker(options = {}) {
|
|
|
830
957
|
};
|
|
831
958
|
/**
|
|
832
959
|
* Ends `shared` with `receipt`, attributing it to the transaction that was mined
|
|
833
|
-
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.
|
|
960
|
+
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.8.0/docs/adr/0008-replaced-transactions.md).
|
|
834
961
|
*/
|
|
835
962
|
const endWithReceipt = (chainId, hash, shared, receipt, endTime) => {
|
|
836
963
|
const mined = receipt.transactionHash;
|
|
@@ -860,41 +987,22 @@ function createTxTracker(options = {}) {
|
|
|
860
987
|
*/
|
|
861
988
|
const startConfirm = (input, parentCtx) => {
|
|
862
989
|
const { chainId, hash } = input;
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
865
|
-
|
|
866
|
-
if (!confirm) {
|
|
867
|
-
confirm = openConfirm(input, parentCtx);
|
|
868
|
-
confirmations.start(chainId, hash, confirm);
|
|
869
|
-
}
|
|
870
|
-
const shared = confirm;
|
|
871
|
-
shared.active += 1;
|
|
872
|
-
let done = false;
|
|
873
|
-
const withdraw = (end) => {
|
|
874
|
-
if (done || shared.ended) return;
|
|
875
|
-
done = true;
|
|
876
|
-
shared.active -= 1;
|
|
877
|
-
if (shared.active > 0) return;
|
|
878
|
-
shared.ended = true;
|
|
879
|
-
confirmations.release(chainId, hash, shared);
|
|
880
|
-
end();
|
|
881
|
-
};
|
|
990
|
+
const claim = joinConfirm(confirmations, chainId, hash, () => openConfirm(input, parentCtx));
|
|
991
|
+
if (!claim) return NOOP_CONFIRM;
|
|
992
|
+
const { shared } = claim;
|
|
882
993
|
return {
|
|
883
994
|
end: (receipt, second) => {
|
|
884
995
|
const { endTime } = handleOptions(second);
|
|
885
|
-
if (
|
|
886
|
-
done = true;
|
|
887
|
-
shared.active -= 1;
|
|
888
|
-
shared.ended = true;
|
|
996
|
+
if (!claim.receive()) return;
|
|
889
997
|
safely("record receipt", () => endWithReceipt(chainId, hash, shared, receipt, endTime), void 0);
|
|
890
998
|
},
|
|
891
999
|
timeout: (second) => {
|
|
892
1000
|
const { endTime } = handleOptions(second);
|
|
893
|
-
withdraw(() => shared.timeout(endTime));
|
|
1001
|
+
claim.withdraw(() => shared.timeout(endTime));
|
|
894
1002
|
},
|
|
895
1003
|
fail: (error, second) => {
|
|
896
1004
|
const { endTime } = handleOptions(second);
|
|
897
|
-
withdraw(() => shared.fail(error, endTime));
|
|
1005
|
+
claim.withdraw(() => shared.fail(error, endTime));
|
|
898
1006
|
}
|
|
899
1007
|
};
|
|
900
1008
|
};
|
|
@@ -903,10 +1011,10 @@ function createTxTracker(options = {}) {
|
|
|
903
1011
|
const attributes = baseAttributes(input.chainId, BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT, parent);
|
|
904
1012
|
const protocol = identifier(input.protocol);
|
|
905
1013
|
if (protocol !== void 0) attributes[ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL] = protocol;
|
|
906
|
-
|
|
1014
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_PAYER, input.payer);
|
|
907
1015
|
const knownPayer = typeof input.payer === "string" && ADDRESS.test(input.payer);
|
|
908
|
-
|
|
909
|
-
|
|
1016
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_RECIPIENT, input.recipient);
|
|
1017
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_ASSET, input.asset);
|
|
910
1018
|
const paid = amount(input.amount);
|
|
911
1019
|
if (paid !== void 0) attributes[ATTR_BLOCKCHAIN_PAYMENT_AMOUNT] = paid;
|
|
912
1020
|
const scheme = identifier(input.x402?.scheme);
|
|
@@ -938,7 +1046,7 @@ function createTxTracker(options = {}) {
|
|
|
938
1046
|
const settled = { [ATTR_BLOCKCHAIN_PAYMENT_STATUS]: status };
|
|
939
1047
|
const hash = settlement.hash;
|
|
940
1048
|
if (linkHash(hash)) settled[ATTR_BLOCKCHAIN_TX_HASH] = hash;
|
|
941
|
-
if (!knownPayer)
|
|
1049
|
+
if (!knownPayer) setRemoteAddress(settled, ATTR_BLOCKCHAIN_PAYMENT_PAYER, settlement.payer);
|
|
942
1050
|
const settledAmount = amount(settlement.amount);
|
|
943
1051
|
if (settledAmount !== void 0) {
|
|
944
1052
|
settled[ATTR_BLOCKCHAIN_PAYMENT_SETTLED_AMOUNT] = settledAmount;
|
|
@@ -959,10 +1067,138 @@ function createTxTracker(options = {}) {
|
|
|
959
1067
|
link: (hash) => safely("link the payment span", () => void linkHash(hash), void 0)
|
|
960
1068
|
};
|
|
961
1069
|
};
|
|
1070
|
+
const startUserOperationSend = (input, parentCtx) => {
|
|
1071
|
+
const parent = parentCtx ?? _opentelemetry_api.context.active();
|
|
1072
|
+
const { chainId } = input;
|
|
1073
|
+
const attributes = baseAttributes(chainId, BLOCKCHAIN_OPERATION_NAME_VALUE_SEND, parent);
|
|
1074
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_USER_OPERATION_SENDER, input.sender);
|
|
1075
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_USER_OPERATION_ENTRY_POINT, input.entryPoint);
|
|
1076
|
+
const callCount = smallQuantity(input.callCount);
|
|
1077
|
+
if (callCount !== void 0) attributes[ATTR_BLOCKCHAIN_USER_OPERATION_CALL_COUNT] = callCount;
|
|
1078
|
+
const span = getTracer().startSpan(`send ${chainId}`, {
|
|
1079
|
+
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
1080
|
+
attributes: redact(attributes),
|
|
1081
|
+
...input.startTime !== void 0 ? { startTime: input.startTime } : {}
|
|
1082
|
+
}, parent);
|
|
1083
|
+
const finish = finisher(span);
|
|
1084
|
+
const startMs = toEpochMs(input.startTime);
|
|
1085
|
+
const recordSend = (endTime, errorType) => txMetrics.sendDuration(secondsSince(startMs, endTime), userOperationMetricAttributes(chainId, errorType === void 0 ? {} : { [ATTR_ERROR_TYPE]: errorType }));
|
|
1086
|
+
return {
|
|
1087
|
+
context: _opentelemetry_api.trace.setSpan(parent, span),
|
|
1088
|
+
end: (result, second) => {
|
|
1089
|
+
const { endTime } = handleOptions(second);
|
|
1090
|
+
finish("record user operation hash", () => {
|
|
1091
|
+
const hash = result?.userOpHash;
|
|
1092
|
+
if (typeof hash !== "string" || !TX_HASH.test(hash)) {
|
|
1093
|
+
_opentelemetry_api.diag.debug("hashspan: ending a send span without a valid user operation hash");
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
userOperationLinks.set(chainId, hash, {
|
|
1097
|
+
spanContext: span.spanContext(),
|
|
1098
|
+
parent
|
|
1099
|
+
});
|
|
1100
|
+
span.setAttributes(redact({ [ATTR_BLOCKCHAIN_USER_OPERATION_HASH]: hash }));
|
|
1101
|
+
recordSend(endTime);
|
|
1102
|
+
}, endTime);
|
|
1103
|
+
},
|
|
1104
|
+
fail: (error, second) => {
|
|
1105
|
+
const read = handleOptions(second);
|
|
1106
|
+
finish("record user operation send failure", () => recordSend(read.endTime, markError(span, reportedErrorType(error, read), error, errorType(error))), read.endTime);
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
};
|
|
1110
|
+
/**
|
|
1111
|
+
* Attributes of a user operation receipt. Its values come from a bundler: what is malformed is left out. The bundle
|
|
1112
|
+
* transaction's status, gas and fee are not recorded: they cover every operation in the bundle (ADR 0021).
|
|
1113
|
+
*/
|
|
1114
|
+
const userOperationReceiptAttributes = (receipt) => {
|
|
1115
|
+
const attributes = {};
|
|
1116
|
+
const success = receipt.success;
|
|
1117
|
+
if (typeof success === "boolean") attributes[ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS] = success;
|
|
1118
|
+
const gasUsed = smallQuantity(receipt.actualGasUsed);
|
|
1119
|
+
if (gasUsed !== void 0) attributes[ATTR_BLOCKCHAIN_USER_OPERATION_GAS_USED] = gasUsed;
|
|
1120
|
+
const gasCost = quantity(receipt.actualGasCost);
|
|
1121
|
+
if (gasCost !== void 0) attributes[ATTR_BLOCKCHAIN_USER_OPERATION_GAS_COST] = gasCost.toString();
|
|
1122
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_USER_OPERATION_SENDER, receipt.sender);
|
|
1123
|
+
const nonce = quantity(receipt.nonce);
|
|
1124
|
+
if (nonce !== void 0) attributes[ATTR_BLOCKCHAIN_USER_OPERATION_NONCE] = nonce.toString();
|
|
1125
|
+
const paymaster = receipt.paymaster;
|
|
1126
|
+
if (typeof paymaster === "string" && !ZERO_ADDRESS.test(paymaster)) setRemoteAddress(attributes, ATTR_BLOCKCHAIN_USER_OPERATION_PAYMASTER, paymaster);
|
|
1127
|
+
setRemoteAddress(attributes, ATTR_BLOCKCHAIN_USER_OPERATION_ENTRY_POINT, receipt.entryPoint);
|
|
1128
|
+
const reason = receipt.revertReason;
|
|
1129
|
+
if (typeof reason === "string") attributes[ATTR_BLOCKCHAIN_TX_REVERT_REASON] = formatAddressesIn(reason, formatAddress);
|
|
1130
|
+
const bundle = receipt.transactionHash;
|
|
1131
|
+
if (typeof bundle === "string" && TX_HASH.test(bundle)) attributes[ATTR_BLOCKCHAIN_TX_HASH] = bundle;
|
|
1132
|
+
const block = smallQuantity(receipt.blockNumber);
|
|
1133
|
+
if (block !== void 0) attributes[ATTR_BLOCKCHAIN_BLOCK_NUMBER] = block;
|
|
1134
|
+
return attributes;
|
|
1135
|
+
};
|
|
1136
|
+
/** Opens the confirm span of a user operation. */
|
|
1137
|
+
const openUserOperationConfirm = (input, parentCtx) => {
|
|
1138
|
+
const { chainId, userOpHash } = input;
|
|
1139
|
+
const sent = userOperationLinks.get(chainId, userOpHash);
|
|
1140
|
+
const active = _opentelemetry_api.context.active();
|
|
1141
|
+
const parent = parentCtx ?? (_opentelemetry_api.trace.getSpan(active) ? active : sent?.parent ?? active);
|
|
1142
|
+
const attributes = baseAttributes(chainId, BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM, parent);
|
|
1143
|
+
attributes[ATTR_BLOCKCHAIN_USER_OPERATION_HASH] = userOpHash;
|
|
1144
|
+
const span = getTracer().startSpan(`confirm ${chainId}`, {
|
|
1145
|
+
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
1146
|
+
attributes: redact(attributes),
|
|
1147
|
+
links: sent ? [{ context: sent.spanContext }] : [],
|
|
1148
|
+
...input.startTime !== void 0 ? { startTime: input.startTime } : {}
|
|
1149
|
+
}, parent);
|
|
1150
|
+
const finish = finisher(span);
|
|
1151
|
+
const startMs = toEpochMs(input.startTime);
|
|
1152
|
+
const recordConfirmation = (endTime, outcome) => txMetrics.confirmationDuration(secondsSince(startMs, endTime), userOperationMetricAttributes(chainId, outcome));
|
|
1153
|
+
return {
|
|
1154
|
+
active: 0,
|
|
1155
|
+
ended: false,
|
|
1156
|
+
receipt: (receipt, endTime) => finish("record user operation receipt", () => {
|
|
1157
|
+
const attributes = userOperationReceiptAttributes(receipt ?? {});
|
|
1158
|
+
span.setAttributes(redact(attributes));
|
|
1159
|
+
const success = attributes[ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS];
|
|
1160
|
+
if (success === false) markError(span, BLOCKCHAIN_TX_STATUS_VALUE_REVERTED);
|
|
1161
|
+
const outcome = typeof success === "boolean" ? { [ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS]: success } : {};
|
|
1162
|
+
recordConfirmation(endTime, outcome);
|
|
1163
|
+
const cost = attributes[ATTR_BLOCKCHAIN_USER_OPERATION_GAS_COST];
|
|
1164
|
+
if (typeof cost === "string") txMetrics.fee(BigInt(cost), userOperationMetricAttributes(chainId, outcome));
|
|
1165
|
+
}, endTime),
|
|
1166
|
+
timeout: (endTime) => finish("record user operation confirmation timeout", () => recordConfirmation(endTime, { [ATTR_ERROR_TYPE]: markError(span, OBSERVER_TIMEOUT) }), endTime),
|
|
1167
|
+
fail: (error, read) => finish("record user operation confirmation failure", () => recordConfirmation(read.endTime, { [ATTR_ERROR_TYPE]: markError(span, reportedErrorType(error, read), error, errorType(error)) }), read.endTime)
|
|
1168
|
+
};
|
|
1169
|
+
};
|
|
1170
|
+
const startUserOperationConfirm = (input, parentCtx) => {
|
|
1171
|
+
const { chainId, userOpHash } = input;
|
|
1172
|
+
if (typeof userOpHash !== "string" || !TX_HASH.test(userOpHash)) {
|
|
1173
|
+
_opentelemetry_api.diag.debug("hashspan: not confirming a user operation without a valid hash");
|
|
1174
|
+
return NOOP_USER_OPERATION_CONFIRM;
|
|
1175
|
+
}
|
|
1176
|
+
const claim = joinConfirm(userOperationConfirmations, chainId, userOpHash, () => openUserOperationConfirm(input, parentCtx));
|
|
1177
|
+
if (!claim) return NOOP_USER_OPERATION_CONFIRM;
|
|
1178
|
+
const { shared } = claim;
|
|
1179
|
+
return {
|
|
1180
|
+
end: (receipt, second) => {
|
|
1181
|
+
const { endTime } = handleOptions(second);
|
|
1182
|
+
if (!claim.receive()) return;
|
|
1183
|
+
userOperationConfirmations.settle(chainId, userOpHash, shared);
|
|
1184
|
+
shared.receipt(receipt, endTime);
|
|
1185
|
+
},
|
|
1186
|
+
timeout: (second) => {
|
|
1187
|
+
const { endTime } = handleOptions(second);
|
|
1188
|
+
claim.withdraw(() => shared.timeout(endTime));
|
|
1189
|
+
},
|
|
1190
|
+
fail: (error, second) => {
|
|
1191
|
+
const read = handleOptions(second);
|
|
1192
|
+
claim.withdraw(() => shared.fail(error, read));
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
};
|
|
962
1196
|
return {
|
|
963
1197
|
startSend: (input, parent) => safely("start send span", () => startSend(input, parent), noopSend(parent ?? _opentelemetry_api.context.active())),
|
|
964
1198
|
startConfirm: (input, parent) => safely("start confirm span", () => startConfirm(input, parent), NOOP_CONFIRM),
|
|
965
|
-
startPayment: (input, parent) => safely("start payment span", () => startPayment(input, parent), NOOP_PAYMENT)
|
|
1199
|
+
startPayment: (input, parent) => safely("start payment span", () => startPayment(input, parent), NOOP_PAYMENT),
|
|
1200
|
+
startUserOperationSend: (input, parent) => safely("start user operation send span", () => startUserOperationSend(input, parent), noopUserOperationSend(parent ?? _opentelemetry_api.context.active())),
|
|
1201
|
+
startUserOperationConfirm: (input, parent) => safely("start user operation confirm span", () => startUserOperationConfirm(input, parent), NOOP_USER_OPERATION_CONFIRM)
|
|
966
1202
|
};
|
|
967
1203
|
}
|
|
968
1204
|
//#endregion
|
|
@@ -972,6 +1208,7 @@ exports.ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_ARGUMENTS = ATTR_BLOCKCHAIN_CONTRACT_F
|
|
|
972
1208
|
exports.ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_NAME = ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_NAME;
|
|
973
1209
|
exports.ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_SELECTOR = ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_SELECTOR;
|
|
974
1210
|
exports.ATTR_BLOCKCHAIN_OPERATION_NAME = ATTR_BLOCKCHAIN_OPERATION_NAME;
|
|
1211
|
+
exports.ATTR_BLOCKCHAIN_OPERATION_SUBJECT = ATTR_BLOCKCHAIN_OPERATION_SUBJECT;
|
|
975
1212
|
exports.ATTR_BLOCKCHAIN_PAYMENT_AMOUNT = ATTR_BLOCKCHAIN_PAYMENT_AMOUNT;
|
|
976
1213
|
exports.ATTR_BLOCKCHAIN_PAYMENT_ASSET = ATTR_BLOCKCHAIN_PAYMENT_ASSET;
|
|
977
1214
|
exports.ATTR_BLOCKCHAIN_PAYMENT_PAYER = ATTR_BLOCKCHAIN_PAYMENT_PAYER;
|
|
@@ -994,6 +1231,15 @@ exports.ATTR_BLOCKCHAIN_TX_REVERT_REASON = ATTR_BLOCKCHAIN_TX_REVERT_REASON;
|
|
|
994
1231
|
exports.ATTR_BLOCKCHAIN_TX_STATUS = ATTR_BLOCKCHAIN_TX_STATUS;
|
|
995
1232
|
exports.ATTR_BLOCKCHAIN_TX_TO = ATTR_BLOCKCHAIN_TX_TO;
|
|
996
1233
|
exports.ATTR_BLOCKCHAIN_TX_VALUE = ATTR_BLOCKCHAIN_TX_VALUE;
|
|
1234
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_CALL_COUNT = ATTR_BLOCKCHAIN_USER_OPERATION_CALL_COUNT;
|
|
1235
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_ENTRY_POINT = ATTR_BLOCKCHAIN_USER_OPERATION_ENTRY_POINT;
|
|
1236
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_GAS_COST = ATTR_BLOCKCHAIN_USER_OPERATION_GAS_COST;
|
|
1237
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_GAS_USED = ATTR_BLOCKCHAIN_USER_OPERATION_GAS_USED;
|
|
1238
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_HASH = ATTR_BLOCKCHAIN_USER_OPERATION_HASH;
|
|
1239
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_NONCE = ATTR_BLOCKCHAIN_USER_OPERATION_NONCE;
|
|
1240
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_PAYMASTER = ATTR_BLOCKCHAIN_USER_OPERATION_PAYMASTER;
|
|
1241
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_SENDER = ATTR_BLOCKCHAIN_USER_OPERATION_SENDER;
|
|
1242
|
+
exports.ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS = ATTR_BLOCKCHAIN_USER_OPERATION_SUCCESS;
|
|
997
1243
|
exports.ATTR_ERROR_TYPE = ATTR_ERROR_TYPE;
|
|
998
1244
|
exports.ATTR_GEN_AI_AGENT_ID = ATTR_GEN_AI_AGENT_ID;
|
|
999
1245
|
exports.ATTR_GEN_AI_AGENT_NAME = ATTR_GEN_AI_AGENT_NAME;
|
|
@@ -1002,6 +1248,7 @@ exports.ATTR_X402_SCHEME = ATTR_X402_SCHEME;
|
|
|
1002
1248
|
exports.BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM = BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM;
|
|
1003
1249
|
exports.BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT = BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT;
|
|
1004
1250
|
exports.BLOCKCHAIN_OPERATION_NAME_VALUE_SEND = BLOCKCHAIN_OPERATION_NAME_VALUE_SEND;
|
|
1251
|
+
exports.BLOCKCHAIN_OPERATION_SUBJECT_VALUE_USER_OPERATION = BLOCKCHAIN_OPERATION_SUBJECT_VALUE_USER_OPERATION;
|
|
1005
1252
|
exports.BLOCKCHAIN_PAYMENT_PROTOCOL_VALUE_X402 = BLOCKCHAIN_PAYMENT_PROTOCOL_VALUE_X402;
|
|
1006
1253
|
exports.BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED = BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED;
|
|
1007
1254
|
exports.BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING = BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING;
|