@hashspan/core 0.3.0 → 0.5.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 +30 -13
- package/dist/index.cjs +256 -21
- package/dist/index.d.cts +214 -18
- package/dist/index.d.mts +214 -18
- package/dist/index.mjs +243 -22
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,8 @@ const ATTR_GEN_AI_AGENT_ID = "gen_ai.agent.id";
|
|
|
4
4
|
const ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name";
|
|
5
5
|
/**
|
|
6
6
|
* Agent identity as GenAI attributes. A field set in the static identity always wins; Baggage, which a remote caller
|
|
7
|
-
* can set, only fills fields it leaves unset, and is not read at all with `fromBaggage` false
|
|
7
|
+
* can set, only fills fields it leaves unset, and is not read at all with `fromBaggage` false
|
|
8
|
+
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/adr/0011-agent-identity-precedence.md).
|
|
8
9
|
*/
|
|
9
10
|
function agentAttributes(ctx, identity, fromBaggage = true) {
|
|
10
11
|
const baggage = fromBaggage ? propagation.getBaggage(ctx) : void 0;
|
|
@@ -20,8 +21,8 @@ function agentAttributes(ctx, identity, fromBaggage = true) {
|
|
|
20
21
|
/**
|
|
21
22
|
* Attribute keys emitted by hashspan.
|
|
22
23
|
*
|
|
23
|
-
* Stability: development. See docs/semconv.md for
|
|
24
|
-
* These names are a public contract: changes follow the deprecation policy in AGENTS.md.
|
|
24
|
+
* Stability: development. See https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/semconv.md for
|
|
25
|
+
* definitions and value types. These names are a public contract: changes follow the deprecation policy in AGENTS.md.
|
|
25
26
|
*/
|
|
26
27
|
const ATTR_BLOCKCHAIN_SYSTEM = "blockchain.system";
|
|
27
28
|
const ATTR_BLOCKCHAIN_CHAIN_ID = "blockchain.chain.id";
|
|
@@ -42,16 +43,46 @@ const ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON = "blockchain.tx.replacement.reason"
|
|
|
42
43
|
const ATTR_BLOCKCHAIN_BLOCK_NUMBER = "blockchain.block.number";
|
|
43
44
|
const ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_NAME = "blockchain.contract.function.name";
|
|
44
45
|
const ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_SELECTOR = "blockchain.contract.function.selector";
|
|
45
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Opt-in: decoded call arguments as a JSON array. See
|
|
48
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/adr/0004-privacy-defaults.md.
|
|
49
|
+
*/
|
|
46
50
|
const ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_ARGUMENTS = "blockchain.contract.function.arguments";
|
|
51
|
+
/**
|
|
52
|
+
* Payments settled on chain by a party other than the agent, e.g. an x402 facilitator. See
|
|
53
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/adr/0013-x402-payments.md.
|
|
54
|
+
*/
|
|
55
|
+
const ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL = "blockchain.payment.protocol";
|
|
56
|
+
const ATTR_BLOCKCHAIN_PAYMENT_PAYER = "blockchain.payment.payer";
|
|
57
|
+
const ATTR_BLOCKCHAIN_PAYMENT_RECIPIENT = "blockchain.payment.recipient";
|
|
58
|
+
const ATTR_BLOCKCHAIN_PAYMENT_ASSET = "blockchain.payment.asset";
|
|
59
|
+
const ATTR_BLOCKCHAIN_PAYMENT_AMOUNT = "blockchain.payment.amount";
|
|
60
|
+
const ATTR_BLOCKCHAIN_PAYMENT_STATUS = "blockchain.payment.status";
|
|
61
|
+
/** The amount the settling party reports it settled, e.g. less than the authorized maximum with x402 `upto`. */
|
|
62
|
+
const ATTR_BLOCKCHAIN_PAYMENT_SETTLED_AMOUNT = "blockchain.payment.settled_amount";
|
|
63
|
+
/** x402's own payment fields. */
|
|
64
|
+
const ATTR_X402_SCHEME = "x402.scheme";
|
|
65
|
+
const ATTR_X402_RESOURCE = "x402.resource";
|
|
47
66
|
/** Values for {@link ATTR_BLOCKCHAIN_SYSTEM}. */
|
|
48
67
|
const BLOCKCHAIN_SYSTEM_VALUE_EVM = "evm";
|
|
49
68
|
/** Values for {@link ATTR_BLOCKCHAIN_OPERATION_NAME}. */
|
|
50
69
|
const BLOCKCHAIN_OPERATION_NAME_VALUE_SEND = "send";
|
|
51
70
|
const BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM = "confirm";
|
|
71
|
+
const BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT = "payment";
|
|
72
|
+
/** Values for {@link ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL}. */
|
|
73
|
+
const BLOCKCHAIN_PAYMENT_PROTOCOL_VALUE_X402 = "x402";
|
|
74
|
+
/** Values for {@link ATTR_BLOCKCHAIN_PAYMENT_STATUS}. */
|
|
75
|
+
const BLOCKCHAIN_PAYMENT_STATUS_VALUE_SETTLED = "settled";
|
|
76
|
+
const BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING = "pending";
|
|
77
|
+
const BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED = "failed";
|
|
52
78
|
/** Values for {@link ATTR_BLOCKCHAIN_TX_STATUS}. */
|
|
53
79
|
const BLOCKCHAIN_TX_STATUS_VALUE_SUCCESS = "success";
|
|
54
80
|
const BLOCKCHAIN_TX_STATUS_VALUE_REVERTED = "reverted";
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated No longer recorded: a confirm span that gave up waiting records `error.type` `timeout` and no
|
|
83
|
+
* `blockchain.tx.status`. The constant is removed in 1.0. See
|
|
84
|
+
* https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/adr/0016-timeout-is-an-observer-outcome.md.
|
|
85
|
+
*/
|
|
55
86
|
const BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT = "timeout";
|
|
56
87
|
const BLOCKCHAIN_TX_STATUS_VALUE_REPLACED = "replaced";
|
|
57
88
|
/** Values for {@link ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON}, as reported by the instrumented library. */
|
|
@@ -299,9 +330,56 @@ function resolveErrorMessageMode(mode) {
|
|
|
299
330
|
diag.warn(`hashspan: unknown error message mode "${String(mode)}"; recording error types only`);
|
|
300
331
|
return "off";
|
|
301
332
|
}
|
|
333
|
+
function resolvePaymentResourceMode(mode) {
|
|
334
|
+
if (mode === void 0 || mode === "origin" || mode === "path" || mode === "off") return mode ?? "origin";
|
|
335
|
+
diag.warn(`hashspan: unknown payment resource mode "${String(mode)}"; not recording payment resources`);
|
|
336
|
+
return "off";
|
|
337
|
+
}
|
|
338
|
+
/** The origin of a URL: `scheme://`, then the host and port, without user info. */
|
|
339
|
+
const URL_ORIGIN = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)(?:[^/?#]*@)?([^/?#]+)/;
|
|
340
|
+
/**
|
|
341
|
+
* The part of a payment's resource that `mode` records, or undefined for none. Works on the text, so it never throws
|
|
342
|
+
* for a resource that is not a URL.
|
|
343
|
+
*/
|
|
344
|
+
function paymentResourceOf(resource, mode) {
|
|
345
|
+
if (mode === "off" || hidesUserInfo(resource)) return void 0;
|
|
346
|
+
const recorded = mode === "path" ? sanitizeResource(resource) : originOf(resource);
|
|
347
|
+
if (recorded === void 0) return void 0;
|
|
348
|
+
return recorded.length > MAX_RESOURCE_LENGTH ? `${recorded.slice(0, MAX_RESOURCE_LENGTH)}...` : recorded;
|
|
349
|
+
}
|
|
350
|
+
/** Longest `x402.resource` recorded; the value comes from the server that asks for the payment. */
|
|
351
|
+
const MAX_RESOURCE_LENGTH = 512;
|
|
352
|
+
function originOf(resource) {
|
|
353
|
+
const origin = URL_ORIGIN.exec(resource);
|
|
354
|
+
return origin ? `${origin[1]}${origin[2]}` : void 0;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* True for `scheme://` text whose user info contains `?` or `#`, such as `https://user:p?ss@host`. A valid URL
|
|
358
|
+
* percent-encodes them; reading such text by its first `?` or `#` would record part of the user info as the host.
|
|
359
|
+
*/
|
|
360
|
+
function hidesUserInfo(resource) {
|
|
361
|
+
const scheme = URL_SCHEME.exec(resource);
|
|
362
|
+
if (!scheme) return false;
|
|
363
|
+
const rest = resource.slice(scheme[0].length);
|
|
364
|
+
const slash = rest.indexOf("/");
|
|
365
|
+
const authority = slash === -1 ? rest : rest.slice(0, slash);
|
|
366
|
+
const at = authority.lastIndexOf("@");
|
|
367
|
+
return at !== -1 && /[?#]/.test(authority.slice(0, at));
|
|
368
|
+
}
|
|
369
|
+
const URL_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
|
|
370
|
+
/** `scheme://user:password@` at the start of a URL; the user info is removed. */
|
|
371
|
+
const URL_USER_INFO = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^/?#]*@/;
|
|
372
|
+
/**
|
|
373
|
+
* The resource of a payment without what can carry credentials: the query string, the fragment and the user info.
|
|
374
|
+
* Works on the text, so names that are not URLs are kept as they are.
|
|
375
|
+
*/
|
|
376
|
+
function sanitizeResource(resource) {
|
|
377
|
+
const end = resource.search(/[?#]/);
|
|
378
|
+
return (end === -1 ? resource : resource.slice(0, end)).replace(URL_USER_INFO, "$1");
|
|
379
|
+
}
|
|
302
380
|
//#endregion
|
|
303
381
|
//#region src/version.ts
|
|
304
|
-
const VERSION = "0.
|
|
382
|
+
const VERSION = "0.5.0";
|
|
305
383
|
//#endregion
|
|
306
384
|
//#region src/tracker.ts
|
|
307
385
|
const INSTRUMENTATION_NAME = "@hashspan/core";
|
|
@@ -321,18 +399,37 @@ const NON_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
321
399
|
ATTR_BLOCKCHAIN_TX_STATUS,
|
|
322
400
|
ATTR_BLOCKCHAIN_TX_REPLACEMENT_HASH,
|
|
323
401
|
ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON,
|
|
402
|
+
ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL,
|
|
403
|
+
ATTR_BLOCKCHAIN_PAYMENT_STATUS,
|
|
324
404
|
ATTR_ERROR_TYPE,
|
|
325
405
|
ATTR_EXCEPTION_TYPE
|
|
326
406
|
]);
|
|
327
407
|
const TX_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
408
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
409
|
+
/** A non-negative integer that fits in 256 bits. */
|
|
410
|
+
const AMOUNT = /^(0|[1-9][0-9]{0,77})$/;
|
|
411
|
+
/** `error.type` of a wait that gave up: a confirmation or a payment whose outcome was never learned. */
|
|
412
|
+
const OBSERVER_TIMEOUT = "timeout";
|
|
413
|
+
const PAYMENT_STATUSES = /* @__PURE__ */ new Set([
|
|
414
|
+
BLOCKCHAIN_PAYMENT_STATUS_VALUE_SETTLED,
|
|
415
|
+
BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING,
|
|
416
|
+
BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED
|
|
417
|
+
]);
|
|
328
418
|
const REPLACEMENT_REASONS = /* @__PURE__ */ new Set([
|
|
329
419
|
BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPRICED,
|
|
330
420
|
BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_CANCELLED,
|
|
331
421
|
BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPLACED
|
|
332
422
|
]);
|
|
333
|
-
|
|
423
|
+
/** Records nothing; its context is the parent, so a call run in it still nests under the caller. */
|
|
424
|
+
const noopSend = (parent) => ({
|
|
425
|
+
context: parent,
|
|
334
426
|
end: () => {},
|
|
335
427
|
fail: () => {}
|
|
428
|
+
});
|
|
429
|
+
const NOOP_PAYMENT = {
|
|
430
|
+
end: () => {},
|
|
431
|
+
fail: () => {},
|
|
432
|
+
timeout: () => {}
|
|
336
433
|
};
|
|
337
434
|
const NOOP_CONFIRM = {
|
|
338
435
|
end: () => {},
|
|
@@ -357,7 +454,47 @@ function errorType(error) {
|
|
|
357
454
|
return error instanceof Error && error.name ? error.name : ERROR_TYPE_VALUE_OTHER;
|
|
358
455
|
}
|
|
359
456
|
const ERROR_TYPE_OVERRIDE = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
457
|
+
/** `value` if it is a short identifier, the only kind of free text recorded from a remote party. */
|
|
458
|
+
function identifier(value) {
|
|
459
|
+
return typeof value === "string" && ERROR_TYPE_OVERRIDE.test(value) ? value : void 0;
|
|
460
|
+
}
|
|
461
|
+
/** A decimal amount, or undefined when `value` is not a non-negative integer. */
|
|
462
|
+
function amount(value) {
|
|
463
|
+
const text = typeof value === "bigint" ? value.toString() : value;
|
|
464
|
+
return typeof text === "string" && AMOUNT.test(text) ? text : void 0;
|
|
465
|
+
}
|
|
360
466
|
/** The `error.type` for a failure: an adapter's override when it is a short identifier, else the class name. */
|
|
467
|
+
/** A finite number, an `HrTime` pair or a `Date`: what the deprecated positional `endTime` argument takes. */
|
|
468
|
+
function isTimeInput(value) {
|
|
469
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
470
|
+
if (Array.isArray(value)) return value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number";
|
|
471
|
+
return Object.prototype.toString.call(value) === "[object Date]";
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Reads the options of a handle method called as `(what, options?)` or, deprecated, as `(what, endTime?, options?)`
|
|
475
|
+
* (ADR 0014). A positional end time wins over `options.endTime`. Never throws: an argument of neither form, or an
|
|
476
|
+
* end time that is not one, is ignored.
|
|
477
|
+
*/
|
|
478
|
+
function handleOptions(second, third) {
|
|
479
|
+
try {
|
|
480
|
+
const positional = isTimeInput(second) ? second : void 0;
|
|
481
|
+
const given = positional !== void 0 || second === void 0 ? third : second;
|
|
482
|
+
if (given !== void 0 && (typeof given !== "object" || given === null || isTimeInput(given))) {
|
|
483
|
+
diag.debug("hashspan: ignoring a handle argument that is neither options nor an end time");
|
|
484
|
+
return positional !== void 0 ? { endTime: positional } : {};
|
|
485
|
+
}
|
|
486
|
+
const options = given;
|
|
487
|
+
const endTime = positional ?? options?.endTime;
|
|
488
|
+
if (endTime !== void 0 && !isTimeInput(endTime)) diag.debug("hashspan: ignoring an end time that is not a TimeInput");
|
|
489
|
+
return {
|
|
490
|
+
endTime: isTimeInput(endTime) ? endTime : void 0,
|
|
491
|
+
errorType: options?.errorType
|
|
492
|
+
};
|
|
493
|
+
} catch (error) {
|
|
494
|
+
diag.debug(`hashspan: could not read handle options (${errorType(error)})`);
|
|
495
|
+
return {};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
361
498
|
function reportedErrorType(error, options) {
|
|
362
499
|
const override = options?.errorType;
|
|
363
500
|
if (override === void 0) return errorType(error);
|
|
@@ -365,6 +502,12 @@ function reportedErrorType(error, options) {
|
|
|
365
502
|
diag.debug("hashspan: ignoring an error type that is not a short identifier");
|
|
366
503
|
return errorType(error);
|
|
367
504
|
}
|
|
505
|
+
/**
|
|
506
|
+
* Creates a tracker that records transactions as `send` and `confirm` spans, and payments as `payment` spans, with
|
|
507
|
+
* `@opentelemetry/api` (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/semconv.md). It makes
|
|
508
|
+
* no network calls; the caller passes hashes and receipts. Its methods and handles never throw: failures are logged
|
|
509
|
+
* via `diag`, and a method that fails returns a handle that records nothing.
|
|
510
|
+
*/
|
|
368
511
|
function createTxTracker(options = {}) {
|
|
369
512
|
const links = new LinkStore({
|
|
370
513
|
ttlMs: options.linkTtlMs ?? DEFAULT_LINK_TTL_MS,
|
|
@@ -376,6 +519,7 @@ function createTxTracker(options = {}) {
|
|
|
376
519
|
});
|
|
377
520
|
const formatAddress = safely("configure address mode", () => resolveAddressFormatter(options.address), OFF_ADDRESS_FORMATTER);
|
|
378
521
|
const errorMessages = safely("configure error message mode", () => resolveErrorMessageMode(options.errorMessages), "off");
|
|
522
|
+
const paymentResource = safely("configure payment resource mode", () => resolvePaymentResourceMode(options.paymentResource), "off");
|
|
379
523
|
let tracer;
|
|
380
524
|
const getTracer = () => {
|
|
381
525
|
tracer ??= (options.tracerProvider ?? trace.getTracerProvider()).getTracer(INSTRUMENTATION_NAME, VERSION);
|
|
@@ -398,8 +542,9 @@ function createTxTracker(options = {}) {
|
|
|
398
542
|
return redacted;
|
|
399
543
|
};
|
|
400
544
|
/**
|
|
401
|
-
* Exception event attributes for `error`, per the error message mode. The error object itself is never handed
|
|
402
|
-
*
|
|
545
|
+
* Exception event attributes for `error`, per the error message mode. The error object itself is never handed to
|
|
546
|
+
* the SDK: its message and stack can carry addresses and calldata
|
|
547
|
+
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/adr/0006-error-privacy.md).
|
|
403
548
|
*/
|
|
404
549
|
const exceptionAttributes = (type, error) => {
|
|
405
550
|
const attributes = { [ATTR_EXCEPTION_TYPE]: type };
|
|
@@ -453,6 +598,10 @@ function createTxTracker(options = {}) {
|
|
|
453
598
|
const formatted = formatAddress(address);
|
|
454
599
|
if (formatted !== void 0) attributes[key] = formatted;
|
|
455
600
|
};
|
|
601
|
+
/** Records `address` only if it is one: payment addresses come from remote parties. */
|
|
602
|
+
const setPaymentAddress = (attributes, key, address) => {
|
|
603
|
+
if (typeof address === "string" && ADDRESS.test(address)) setAddress(attributes, key, address);
|
|
604
|
+
};
|
|
456
605
|
const baseAttributes = (chainId, operation, ctx) => ({
|
|
457
606
|
[ATTR_BLOCKCHAIN_SYSTEM]: "evm",
|
|
458
607
|
[ATTR_BLOCKCHAIN_CHAIN_ID]: chainId,
|
|
@@ -480,14 +629,23 @@ function createTxTracker(options = {}) {
|
|
|
480
629
|
}, parent);
|
|
481
630
|
const finish = finisher(span);
|
|
482
631
|
return {
|
|
483
|
-
|
|
632
|
+
context: trace.setSpan(parent, span),
|
|
633
|
+
end: (result, second) => finish("record transaction hash", () => {
|
|
634
|
+
const hash = typeof result === "string" ? result : result?.hash;
|
|
635
|
+
if (typeof hash !== "string") {
|
|
636
|
+
diag.debug("hashspan: ending a send span without a transaction hash");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
484
639
|
links.set(input.chainId, hash, {
|
|
485
640
|
spanContext: span.spanContext(),
|
|
486
641
|
parent
|
|
487
642
|
});
|
|
488
643
|
span.setAttributes(redact({ [ATTR_BLOCKCHAIN_TX_HASH]: hash }));
|
|
489
|
-
}, endTime),
|
|
490
|
-
fail: (error,
|
|
644
|
+
}, handleOptions(second).endTime),
|
|
645
|
+
fail: (error, second, third) => {
|
|
646
|
+
const options = handleOptions(second, third);
|
|
647
|
+
finish("record send failure", () => markError(span, reportedErrorType(error, options), error, errorType(error)), options.endTime);
|
|
648
|
+
}
|
|
491
649
|
};
|
|
492
650
|
};
|
|
493
651
|
const receiptAttributes = (receipt) => {
|
|
@@ -534,10 +692,7 @@ function createTxTracker(options = {}) {
|
|
|
534
692
|
span.setAttributes(redact(receiptAttributes(receipt)));
|
|
535
693
|
if (receipt.status === "reverted") markError(span, BLOCKCHAIN_TX_STATUS_VALUE_REVERTED);
|
|
536
694
|
}, endTime),
|
|
537
|
-
timeout: (endTime) => finish("record confirmation timeout", () =>
|
|
538
|
-
span.setAttributes(redact({ [ATTR_BLOCKCHAIN_TX_STATUS]: BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT }));
|
|
539
|
-
markError(span, BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT);
|
|
540
|
-
}, endTime),
|
|
695
|
+
timeout: (endTime) => finish("record confirmation timeout", () => markError(span, OBSERVER_TIMEOUT), endTime),
|
|
541
696
|
fail: (error, endTime) => finish("record confirmation failure", () => markError(span, errorType(error), error), endTime),
|
|
542
697
|
replaced: (hash, reason, endTime) => finish("record replacement", () => {
|
|
543
698
|
const attributes = {
|
|
@@ -567,7 +722,10 @@ function createTxTracker(options = {}) {
|
|
|
567
722
|
const { replacementReason: _reason, ...mined } = receipt;
|
|
568
723
|
confirm.receipt(mined, endTime);
|
|
569
724
|
};
|
|
570
|
-
/**
|
|
725
|
+
/**
|
|
726
|
+
* Ends `shared` with `receipt`, attributing it to the transaction that was mined
|
|
727
|
+
* (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.5.0/docs/adr/0008-replaced-transactions.md).
|
|
728
|
+
*/
|
|
571
729
|
const endWithReceipt = (chainId, hash, shared, receipt, endTime) => {
|
|
572
730
|
const mined = receipt.transactionHash;
|
|
573
731
|
if (mined === void 0) {
|
|
@@ -616,21 +774,84 @@ function createTxTracker(options = {}) {
|
|
|
616
774
|
end();
|
|
617
775
|
};
|
|
618
776
|
return {
|
|
619
|
-
end: (receipt,
|
|
777
|
+
end: (receipt, second) => {
|
|
778
|
+
const { endTime } = handleOptions(second);
|
|
620
779
|
if (done || shared.ended) return;
|
|
621
780
|
done = true;
|
|
622
781
|
shared.active -= 1;
|
|
623
782
|
shared.ended = true;
|
|
624
783
|
safely("record receipt", () => endWithReceipt(chainId, hash, shared, receipt, endTime), void 0);
|
|
625
784
|
},
|
|
626
|
-
timeout: (
|
|
627
|
-
|
|
785
|
+
timeout: (second) => {
|
|
786
|
+
const { endTime } = handleOptions(second);
|
|
787
|
+
withdraw(() => shared.timeout(endTime));
|
|
788
|
+
},
|
|
789
|
+
fail: (error, second) => {
|
|
790
|
+
const { endTime } = handleOptions(second);
|
|
791
|
+
withdraw(() => shared.fail(error, endTime));
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
};
|
|
795
|
+
const startPayment = (input, parentCtx) => {
|
|
796
|
+
const parent = parentCtx ?? context.active();
|
|
797
|
+
const attributes = baseAttributes(input.chainId, BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT, parent);
|
|
798
|
+
const protocol = identifier(input.protocol);
|
|
799
|
+
if (protocol !== void 0) attributes[ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL] = protocol;
|
|
800
|
+
setPaymentAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_PAYER, input.payer);
|
|
801
|
+
const knownPayer = typeof input.payer === "string" && ADDRESS.test(input.payer);
|
|
802
|
+
setPaymentAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_RECIPIENT, input.recipient);
|
|
803
|
+
setPaymentAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_ASSET, input.asset);
|
|
804
|
+
const paid = amount(input.amount);
|
|
805
|
+
if (paid !== void 0) attributes[ATTR_BLOCKCHAIN_PAYMENT_AMOUNT] = paid;
|
|
806
|
+
const scheme = identifier(input.x402?.scheme);
|
|
807
|
+
if (scheme !== void 0) attributes[ATTR_X402_SCHEME] = scheme;
|
|
808
|
+
const resource = input.x402?.resource;
|
|
809
|
+
const recorded = typeof resource === "string" ? paymentResourceOf(resource, paymentResource) : void 0;
|
|
810
|
+
if (recorded) attributes[ATTR_X402_RESOURCE] = formatAddressesIn(recorded, formatAddress);
|
|
811
|
+
const span = getTracer().startSpan(`payment ${input.chainId}`, {
|
|
812
|
+
kind: SpanKind.CLIENT,
|
|
813
|
+
attributes: redact(attributes),
|
|
814
|
+
...input.startTime !== void 0 ? { startTime: input.startTime } : {}
|
|
815
|
+
}, parent);
|
|
816
|
+
const finish = finisher(span);
|
|
817
|
+
const recordSettlement = (settlement) => {
|
|
818
|
+
const status = settlement.status;
|
|
819
|
+
if (!PAYMENT_STATUSES.has(status)) {
|
|
820
|
+
diag.debug("hashspan: ignoring a payment settlement with an unknown status");
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const settled = { [ATTR_BLOCKCHAIN_PAYMENT_STATUS]: status };
|
|
824
|
+
const hash = settlement.hash;
|
|
825
|
+
if (typeof hash === "string" && TX_HASH.test(hash)) {
|
|
826
|
+
if (!links.get(input.chainId, hash)) links.set(input.chainId, hash, {
|
|
827
|
+
spanContext: span.spanContext(),
|
|
828
|
+
parent
|
|
829
|
+
});
|
|
830
|
+
settled[ATTR_BLOCKCHAIN_TX_HASH] = hash;
|
|
831
|
+
}
|
|
832
|
+
if (!knownPayer) setPaymentAddress(settled, ATTR_BLOCKCHAIN_PAYMENT_PAYER, settlement.payer);
|
|
833
|
+
const settledAmount = amount(settlement.amount);
|
|
834
|
+
if (settledAmount !== void 0) {
|
|
835
|
+
settled[ATTR_BLOCKCHAIN_PAYMENT_SETTLED_AMOUNT] = settledAmount;
|
|
836
|
+
if (paid === void 0) settled[ATTR_BLOCKCHAIN_PAYMENT_AMOUNT] = settledAmount;
|
|
837
|
+
}
|
|
838
|
+
span.setAttributes(redact(settled));
|
|
839
|
+
if (status === "failed") markError(span, identifier(settlement.errorReason) ?? "_OTHER");
|
|
840
|
+
};
|
|
841
|
+
return {
|
|
842
|
+
end: (settlement, options) => finish("record payment settlement", () => recordSettlement(settlement), handleOptions(options).endTime),
|
|
843
|
+
fail: (error, options) => {
|
|
844
|
+
const read = handleOptions(options);
|
|
845
|
+
finish("record payment failure", () => markError(span, reportedErrorType(error, read), error, errorType(error)), read.endTime);
|
|
846
|
+
},
|
|
847
|
+
timeout: (options) => finish("record payment timeout", () => markError(span, OBSERVER_TIMEOUT), handleOptions(options).endTime)
|
|
628
848
|
};
|
|
629
849
|
};
|
|
630
850
|
return {
|
|
631
|
-
startSend: (input, parent) => safely("start send span", () => startSend(input, parent),
|
|
632
|
-
startConfirm: (input, parent) => safely("start confirm span", () => startConfirm(input, parent), NOOP_CONFIRM)
|
|
851
|
+
startSend: (input, parent) => safely("start send span", () => startSend(input, parent), noopSend(parent ?? context.active())),
|
|
852
|
+
startConfirm: (input, parent) => safely("start confirm span", () => startConfirm(input, parent), NOOP_CONFIRM),
|
|
853
|
+
startPayment: (input, parent) => safely("start payment span", () => startPayment(input, parent), NOOP_PAYMENT)
|
|
633
854
|
};
|
|
634
855
|
}
|
|
635
856
|
//#endregion
|
|
636
|
-
export { ATTR_BLOCKCHAIN_BLOCK_NUMBER, ATTR_BLOCKCHAIN_CHAIN_ID, ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_ARGUMENTS, ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_NAME, ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_SELECTOR, ATTR_BLOCKCHAIN_OPERATION_NAME, ATTR_BLOCKCHAIN_SYSTEM, ATTR_BLOCKCHAIN_TX_EFFECTIVE_GAS_PRICE, ATTR_BLOCKCHAIN_TX_FEE, ATTR_BLOCKCHAIN_TX_FROM, ATTR_BLOCKCHAIN_TX_GAS_USED, ATTR_BLOCKCHAIN_TX_HASH, ATTR_BLOCKCHAIN_TX_L1_FEE, ATTR_BLOCKCHAIN_TX_NONCE, ATTR_BLOCKCHAIN_TX_REPLACEMENT_HASH, ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON, ATTR_BLOCKCHAIN_TX_REVERT_REASON, ATTR_BLOCKCHAIN_TX_STATUS, ATTR_BLOCKCHAIN_TX_TO, ATTR_BLOCKCHAIN_TX_VALUE, ATTR_ERROR_TYPE, ATTR_GEN_AI_AGENT_ID, ATTR_GEN_AI_AGENT_NAME, BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM, BLOCKCHAIN_OPERATION_NAME_VALUE_SEND, BLOCKCHAIN_SYSTEM_VALUE_EVM, BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_CANCELLED, BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPLACED, BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPRICED, BLOCKCHAIN_TX_STATUS_VALUE_REPLACED, BLOCKCHAIN_TX_STATUS_VALUE_REVERTED, BLOCKCHAIN_TX_STATUS_VALUE_SUCCESS, BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT, ERROR_TYPE_VALUE_OTHER, VERSION, createTxTracker };
|
|
857
|
+
export { ATTR_BLOCKCHAIN_BLOCK_NUMBER, ATTR_BLOCKCHAIN_CHAIN_ID, ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_ARGUMENTS, ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_NAME, ATTR_BLOCKCHAIN_CONTRACT_FUNCTION_SELECTOR, ATTR_BLOCKCHAIN_OPERATION_NAME, ATTR_BLOCKCHAIN_PAYMENT_AMOUNT, ATTR_BLOCKCHAIN_PAYMENT_ASSET, ATTR_BLOCKCHAIN_PAYMENT_PAYER, ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL, ATTR_BLOCKCHAIN_PAYMENT_RECIPIENT, ATTR_BLOCKCHAIN_PAYMENT_SETTLED_AMOUNT, ATTR_BLOCKCHAIN_PAYMENT_STATUS, ATTR_BLOCKCHAIN_SYSTEM, ATTR_BLOCKCHAIN_TX_EFFECTIVE_GAS_PRICE, ATTR_BLOCKCHAIN_TX_FEE, ATTR_BLOCKCHAIN_TX_FROM, ATTR_BLOCKCHAIN_TX_GAS_USED, ATTR_BLOCKCHAIN_TX_HASH, ATTR_BLOCKCHAIN_TX_L1_FEE, ATTR_BLOCKCHAIN_TX_NONCE, ATTR_BLOCKCHAIN_TX_REPLACEMENT_HASH, ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON, ATTR_BLOCKCHAIN_TX_REVERT_REASON, ATTR_BLOCKCHAIN_TX_STATUS, ATTR_BLOCKCHAIN_TX_TO, ATTR_BLOCKCHAIN_TX_VALUE, ATTR_ERROR_TYPE, ATTR_GEN_AI_AGENT_ID, ATTR_GEN_AI_AGENT_NAME, ATTR_X402_RESOURCE, ATTR_X402_SCHEME, BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM, BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT, BLOCKCHAIN_OPERATION_NAME_VALUE_SEND, BLOCKCHAIN_PAYMENT_PROTOCOL_VALUE_X402, BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED, BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING, BLOCKCHAIN_PAYMENT_STATUS_VALUE_SETTLED, BLOCKCHAIN_SYSTEM_VALUE_EVM, BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_CANCELLED, BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPLACED, BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPRICED, BLOCKCHAIN_TX_STATUS_VALUE_REPLACED, BLOCKCHAIN_TX_STATUS_VALUE_REVERTED, BLOCKCHAIN_TX_STATUS_VALUE_SUCCESS, BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT, ERROR_TYPE_VALUE_OTHER, VERSION, createTxTracker };
|