@hashspan/core 0.2.0 → 0.4.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/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 (docs/adr/0011).
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.4.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 definitions and value types.
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.4.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,44 @@ 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
- /** Opt-in: decoded call arguments as a JSON array. See docs/adr/0004-privacy-defaults.md. */
46
+ /**
47
+ * Opt-in: decoded call arguments as a JSON array. See
48
+ * https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.4.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.4.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
+ /** x402's own payment fields. */
62
+ const ATTR_X402_SCHEME = "x402.scheme";
63
+ const ATTR_X402_RESOURCE = "x402.resource";
47
64
  /** Values for {@link ATTR_BLOCKCHAIN_SYSTEM}. */
48
65
  const BLOCKCHAIN_SYSTEM_VALUE_EVM = "evm";
49
66
  /** Values for {@link ATTR_BLOCKCHAIN_OPERATION_NAME}. */
50
67
  const BLOCKCHAIN_OPERATION_NAME_VALUE_SEND = "send";
51
68
  const BLOCKCHAIN_OPERATION_NAME_VALUE_CONFIRM = "confirm";
69
+ const BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT = "payment";
70
+ /** Values for {@link ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL}. */
71
+ const BLOCKCHAIN_PAYMENT_PROTOCOL_VALUE_X402 = "x402";
72
+ /** Values for {@link ATTR_BLOCKCHAIN_PAYMENT_STATUS}. */
73
+ const BLOCKCHAIN_PAYMENT_STATUS_VALUE_SETTLED = "settled";
74
+ const BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING = "pending";
75
+ const BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED = "failed";
52
76
  /** Values for {@link ATTR_BLOCKCHAIN_TX_STATUS}. */
53
77
  const BLOCKCHAIN_TX_STATUS_VALUE_SUCCESS = "success";
54
78
  const BLOCKCHAIN_TX_STATUS_VALUE_REVERTED = "reverted";
79
+ /**
80
+ * @deprecated A confirm span that gave up waiting records `error.type` `timeout`; this value of
81
+ * `blockchain.tx.status` stops being recorded in a later minor release and the constant is removed in 1.0. See
82
+ * https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.4.0/docs/adr/0016-timeout-is-an-observer-outcome.md.
83
+ */
55
84
  const BLOCKCHAIN_TX_STATUS_VALUE_TIMEOUT = "timeout";
56
85
  const BLOCKCHAIN_TX_STATUS_VALUE_REPLACED = "replaced";
57
86
  /** Values for {@link ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON}, as reported by the instrumented library. */
@@ -299,9 +328,56 @@ function resolveErrorMessageMode(mode) {
299
328
  diag.warn(`hashspan: unknown error message mode "${String(mode)}"; recording error types only`);
300
329
  return "off";
301
330
  }
331
+ function resolvePaymentResourceMode(mode) {
332
+ if (mode === void 0 || mode === "origin" || mode === "path" || mode === "off") return mode ?? "origin";
333
+ diag.warn(`hashspan: unknown payment resource mode "${String(mode)}"; not recording payment resources`);
334
+ return "off";
335
+ }
336
+ /** The origin of a URL: `scheme://`, then the host and port, without user info. */
337
+ const URL_ORIGIN = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)(?:[^/?#]*@)?([^/?#]+)/;
338
+ /**
339
+ * The part of a payment's resource that `mode` records, or undefined for none. Works on the text, so it never throws
340
+ * for a resource that is not a URL.
341
+ */
342
+ function paymentResourceOf(resource, mode) {
343
+ if (mode === "off" || hidesUserInfo(resource)) return void 0;
344
+ const recorded = mode === "path" ? sanitizeResource(resource) : originOf(resource);
345
+ if (recorded === void 0) return void 0;
346
+ return recorded.length > MAX_RESOURCE_LENGTH ? `${recorded.slice(0, MAX_RESOURCE_LENGTH)}...` : recorded;
347
+ }
348
+ /** Longest `x402.resource` recorded; the value comes from the server that asks for the payment. */
349
+ const MAX_RESOURCE_LENGTH = 512;
350
+ function originOf(resource) {
351
+ const origin = URL_ORIGIN.exec(resource);
352
+ return origin ? `${origin[1]}${origin[2]}` : void 0;
353
+ }
354
+ /**
355
+ * True for `scheme://` text whose user info contains `?` or `#`, such as `https://user:p?ss@host`. A valid URL
356
+ * percent-encodes them; reading such text by its first `?` or `#` would record part of the user info as the host.
357
+ */
358
+ function hidesUserInfo(resource) {
359
+ const scheme = URL_SCHEME.exec(resource);
360
+ if (!scheme) return false;
361
+ const rest = resource.slice(scheme[0].length);
362
+ const slash = rest.indexOf("/");
363
+ const authority = slash === -1 ? rest : rest.slice(0, slash);
364
+ const at = authority.lastIndexOf("@");
365
+ return at !== -1 && /[?#]/.test(authority.slice(0, at));
366
+ }
367
+ const URL_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
368
+ /** `scheme://user:password@` at the start of a URL; the user info is removed. */
369
+ const URL_USER_INFO = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^/?#]*@/;
370
+ /**
371
+ * The resource of a payment without what can carry credentials: the query string, the fragment and the user info.
372
+ * Works on the text, so names that are not URLs are kept as they are.
373
+ */
374
+ function sanitizeResource(resource) {
375
+ const end = resource.search(/[?#]/);
376
+ return (end === -1 ? resource : resource.slice(0, end)).replace(URL_USER_INFO, "$1");
377
+ }
302
378
  //#endregion
303
379
  //#region src/version.ts
304
- const VERSION = "0.2.0";
380
+ const VERSION = "0.4.0";
305
381
  //#endregion
306
382
  //#region src/tracker.ts
307
383
  const INSTRUMENTATION_NAME = "@hashspan/core";
@@ -321,18 +397,37 @@ const NON_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
321
397
  ATTR_BLOCKCHAIN_TX_STATUS,
322
398
  ATTR_BLOCKCHAIN_TX_REPLACEMENT_HASH,
323
399
  ATTR_BLOCKCHAIN_TX_REPLACEMENT_REASON,
400
+ ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL,
401
+ ATTR_BLOCKCHAIN_PAYMENT_STATUS,
324
402
  ATTR_ERROR_TYPE,
325
403
  ATTR_EXCEPTION_TYPE
326
404
  ]);
327
405
  const TX_HASH = /^0x[0-9a-fA-F]{64}$/;
406
+ const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
407
+ /** A non-negative integer that fits in 256 bits. */
408
+ const AMOUNT = /^(0|[1-9][0-9]{0,77})$/;
409
+ /** `error.type` of a payment whose outcome was never learned, as for a confirmation that timed out. */
410
+ const PAYMENT_TIMEOUT = "timeout";
411
+ const PAYMENT_STATUSES = /* @__PURE__ */ new Set([
412
+ BLOCKCHAIN_PAYMENT_STATUS_VALUE_SETTLED,
413
+ BLOCKCHAIN_PAYMENT_STATUS_VALUE_PENDING,
414
+ BLOCKCHAIN_PAYMENT_STATUS_VALUE_FAILED
415
+ ]);
328
416
  const REPLACEMENT_REASONS = /* @__PURE__ */ new Set([
329
417
  BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPRICED,
330
418
  BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_CANCELLED,
331
419
  BLOCKCHAIN_TX_REPLACEMENT_REASON_VALUE_REPLACED
332
420
  ]);
333
- const NOOP_SEND = {
421
+ /** Records nothing; its context is the parent, so a call run in it still nests under the caller. */
422
+ const noopSend = (parent) => ({
423
+ context: parent,
334
424
  end: () => {},
335
425
  fail: () => {}
426
+ });
427
+ const NOOP_PAYMENT = {
428
+ end: () => {},
429
+ fail: () => {},
430
+ timeout: () => {}
336
431
  };
337
432
  const NOOP_CONFIRM = {
338
433
  end: () => {},
@@ -356,6 +451,61 @@ function toInt(value) {
356
451
  function errorType(error) {
357
452
  return error instanceof Error && error.name ? error.name : ERROR_TYPE_VALUE_OTHER;
358
453
  }
454
+ const ERROR_TYPE_OVERRIDE = /^[A-Za-z0-9_.-]{1,64}$/;
455
+ /** `value` if it is a short identifier, the only kind of free text recorded from a remote party. */
456
+ function identifier(value) {
457
+ return typeof value === "string" && ERROR_TYPE_OVERRIDE.test(value) ? value : void 0;
458
+ }
459
+ /** A decimal amount, or undefined when `value` is not a non-negative integer. */
460
+ function amount(value) {
461
+ const text = typeof value === "bigint" ? value.toString() : value;
462
+ return typeof text === "string" && AMOUNT.test(text) ? text : void 0;
463
+ }
464
+ /** The `error.type` for a failure: an adapter's override when it is a short identifier, else the class name. */
465
+ /** A finite number, an `HrTime` pair or a `Date`: what the deprecated positional `endTime` argument takes. */
466
+ function isTimeInput(value) {
467
+ if (typeof value === "number") return Number.isFinite(value);
468
+ if (Array.isArray(value)) return value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number";
469
+ return Object.prototype.toString.call(value) === "[object Date]";
470
+ }
471
+ /**
472
+ * Reads the options of a handle method called as `(what, options?)` or, deprecated, as `(what, endTime?, options?)`
473
+ * (ADR 0014). A positional end time wins over `options.endTime`. Never throws: an argument of neither form, or an
474
+ * end time that is not one, is ignored.
475
+ */
476
+ function handleOptions(second, third) {
477
+ try {
478
+ const positional = isTimeInput(second) ? second : void 0;
479
+ const given = positional !== void 0 || second === void 0 ? third : second;
480
+ if (given !== void 0 && (typeof given !== "object" || given === null || isTimeInput(given))) {
481
+ diag.debug("hashspan: ignoring a handle argument that is neither options nor an end time");
482
+ return positional !== void 0 ? { endTime: positional } : {};
483
+ }
484
+ const options = given;
485
+ const endTime = positional ?? options?.endTime;
486
+ if (endTime !== void 0 && !isTimeInput(endTime)) diag.debug("hashspan: ignoring an end time that is not a TimeInput");
487
+ return {
488
+ endTime: isTimeInput(endTime) ? endTime : void 0,
489
+ errorType: options?.errorType
490
+ };
491
+ } catch (error) {
492
+ diag.debug(`hashspan: could not read handle options (${errorType(error)})`);
493
+ return {};
494
+ }
495
+ }
496
+ function reportedErrorType(error, options) {
497
+ const override = options?.errorType;
498
+ if (override === void 0) return errorType(error);
499
+ if (typeof override === "string" && ERROR_TYPE_OVERRIDE.test(override)) return override;
500
+ diag.debug("hashspan: ignoring an error type that is not a short identifier");
501
+ return errorType(error);
502
+ }
503
+ /**
504
+ * Creates a tracker that records transactions as `send` and `confirm` spans, and payments as `payment` spans, with
505
+ * `@opentelemetry/api` (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.4.0/docs/semconv.md). It makes
506
+ * no network calls; the caller passes hashes and receipts. Its methods and handles never throw: failures are logged
507
+ * via `diag`, and a method that fails returns a handle that records nothing.
508
+ */
359
509
  function createTxTracker(options = {}) {
360
510
  const links = new LinkStore({
361
511
  ttlMs: options.linkTtlMs ?? DEFAULT_LINK_TTL_MS,
@@ -367,6 +517,7 @@ function createTxTracker(options = {}) {
367
517
  });
368
518
  const formatAddress = safely("configure address mode", () => resolveAddressFormatter(options.address), OFF_ADDRESS_FORMATTER);
369
519
  const errorMessages = safely("configure error message mode", () => resolveErrorMessageMode(options.errorMessages), "off");
520
+ const paymentResource = safely("configure payment resource mode", () => resolvePaymentResourceMode(options.paymentResource), "off");
370
521
  let tracer;
371
522
  const getTracer = () => {
372
523
  tracer ??= (options.tracerProvider ?? trace.getTracerProvider()).getTracer(INSTRUMENTATION_NAME, VERSION);
@@ -389,8 +540,9 @@ function createTxTracker(options = {}) {
389
540
  return redacted;
390
541
  };
391
542
  /**
392
- * Exception event attributes for `error`, per the error message mode. The error object itself is never handed
393
- * to the SDK: its message and stack can carry addresses and calldata (docs/adr/0006-error-privacy.md).
543
+ * Exception event attributes for `error`, per the error message mode. The error object itself is never handed to
544
+ * the SDK: its message and stack can carry addresses and calldata
545
+ * (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.4.0/docs/adr/0006-error-privacy.md).
394
546
  */
395
547
  const exceptionAttributes = (type, error) => {
396
548
  const attributes = { [ATTR_EXCEPTION_TYPE]: type };
@@ -405,12 +557,15 @@ function createTxTracker(options = {}) {
405
557
  if (error instanceof Error && error.stack) attributes[ATTR_EXCEPTION_STACKTRACE] = error.stack;
406
558
  return attributes;
407
559
  };
408
- /** Error names are free text too: they follow the address mode and pass through the redaction hook. */
409
- const markError = (span, errorName, error) => {
560
+ /**
561
+ * Error names are free text too: they follow the address mode and pass through the redaction hook.
562
+ * `exceptionName` is the class name for `exception.type` when `errorName` is an adapter's error type.
563
+ */
564
+ const markError = (span, errorName, error, exceptionName = errorName) => {
410
565
  const type = formatAddressesIn(errorName, formatAddress);
411
566
  let message;
412
567
  if (error !== void 0) {
413
- const exception = redact(exceptionAttributes(type, error));
568
+ const exception = redact(exceptionAttributes(formatAddressesIn(exceptionName, formatAddress), error));
414
569
  span.addEvent(EXCEPTION_EVENT, exception);
415
570
  const recorded = exception[ATTR_EXCEPTION_MESSAGE];
416
571
  if (typeof recorded === "string") message = recorded;
@@ -441,6 +596,10 @@ function createTxTracker(options = {}) {
441
596
  const formatted = formatAddress(address);
442
597
  if (formatted !== void 0) attributes[key] = formatted;
443
598
  };
599
+ /** Records `address` only if it is one: payment addresses come from remote parties. */
600
+ const setPaymentAddress = (attributes, key, address) => {
601
+ if (typeof address === "string" && ADDRESS.test(address)) setAddress(attributes, key, address);
602
+ };
444
603
  const baseAttributes = (chainId, operation, ctx) => ({
445
604
  [ATTR_BLOCKCHAIN_SYSTEM]: "evm",
446
605
  [ATTR_BLOCKCHAIN_CHAIN_ID]: chainId,
@@ -468,14 +627,23 @@ function createTxTracker(options = {}) {
468
627
  }, parent);
469
628
  const finish = finisher(span);
470
629
  return {
471
- end: (hash, endTime) => finish("record transaction hash", () => {
630
+ context: trace.setSpan(parent, span),
631
+ end: (result, second) => finish("record transaction hash", () => {
632
+ const hash = typeof result === "string" ? result : result?.hash;
633
+ if (typeof hash !== "string") {
634
+ diag.debug("hashspan: ending a send span without a transaction hash");
635
+ return;
636
+ }
472
637
  links.set(input.chainId, hash, {
473
638
  spanContext: span.spanContext(),
474
639
  parent
475
640
  });
476
641
  span.setAttributes(redact({ [ATTR_BLOCKCHAIN_TX_HASH]: hash }));
477
- }, endTime),
478
- fail: (error, endTime) => finish("record send failure", () => markError(span, errorType(error), error), endTime)
642
+ }, handleOptions(second).endTime),
643
+ fail: (error, second, third) => {
644
+ const options = handleOptions(second, third);
645
+ finish("record send failure", () => markError(span, reportedErrorType(error, options), error, errorType(error)), options.endTime);
646
+ }
479
647
  };
480
648
  };
481
649
  const receiptAttributes = (receipt) => {
@@ -555,7 +723,10 @@ function createTxTracker(options = {}) {
555
723
  const { replacementReason: _reason, ...mined } = receipt;
556
724
  confirm.receipt(mined, endTime);
557
725
  };
558
- /** Ends `shared` with `receipt`, attributing it to the transaction that was mined (docs/adr/0008). */
726
+ /**
727
+ * Ends `shared` with `receipt`, attributing it to the transaction that was mined
728
+ * (https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.4.0/docs/adr/0008-replaced-transactions.md).
729
+ */
559
730
  const endWithReceipt = (chainId, hash, shared, receipt, endTime) => {
560
731
  const mined = receipt.transactionHash;
561
732
  if (mined === void 0) {
@@ -604,21 +775,81 @@ function createTxTracker(options = {}) {
604
775
  end();
605
776
  };
606
777
  return {
607
- end: (receipt, endTime) => {
778
+ end: (receipt, second) => {
779
+ const { endTime } = handleOptions(second);
608
780
  if (done || shared.ended) return;
609
781
  done = true;
610
782
  shared.active -= 1;
611
783
  shared.ended = true;
612
784
  safely("record receipt", () => endWithReceipt(chainId, hash, shared, receipt, endTime), void 0);
613
785
  },
614
- timeout: (endTime) => withdraw(() => shared.timeout(endTime)),
615
- fail: (error, endTime) => withdraw(() => shared.fail(error, endTime))
786
+ timeout: (second) => {
787
+ const { endTime } = handleOptions(second);
788
+ withdraw(() => shared.timeout(endTime));
789
+ },
790
+ fail: (error, second) => {
791
+ const { endTime } = handleOptions(second);
792
+ withdraw(() => shared.fail(error, endTime));
793
+ }
794
+ };
795
+ };
796
+ const startPayment = (input, parentCtx) => {
797
+ const parent = parentCtx ?? context.active();
798
+ const attributes = baseAttributes(input.chainId, BLOCKCHAIN_OPERATION_NAME_VALUE_PAYMENT, parent);
799
+ const protocol = identifier(input.protocol);
800
+ if (protocol !== void 0) attributes[ATTR_BLOCKCHAIN_PAYMENT_PROTOCOL] = protocol;
801
+ setPaymentAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_PAYER, input.payer);
802
+ const knownPayer = typeof input.payer === "string" && ADDRESS.test(input.payer);
803
+ setPaymentAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_RECIPIENT, input.recipient);
804
+ setPaymentAddress(attributes, ATTR_BLOCKCHAIN_PAYMENT_ASSET, input.asset);
805
+ const paid = amount(input.amount);
806
+ if (paid !== void 0) attributes[ATTR_BLOCKCHAIN_PAYMENT_AMOUNT] = paid;
807
+ const scheme = identifier(input.x402?.scheme);
808
+ if (scheme !== void 0) attributes[ATTR_X402_SCHEME] = scheme;
809
+ const resource = input.x402?.resource;
810
+ const recorded = typeof resource === "string" ? paymentResourceOf(resource, paymentResource) : void 0;
811
+ if (recorded) attributes[ATTR_X402_RESOURCE] = formatAddressesIn(recorded, formatAddress);
812
+ const span = getTracer().startSpan(`payment ${input.chainId}`, {
813
+ kind: SpanKind.CLIENT,
814
+ attributes: redact(attributes),
815
+ ...input.startTime !== void 0 ? { startTime: input.startTime } : {}
816
+ }, parent);
817
+ const finish = finisher(span);
818
+ const recordSettlement = (settlement) => {
819
+ const status = settlement.status;
820
+ if (!PAYMENT_STATUSES.has(status)) {
821
+ diag.debug("hashspan: ignoring a payment settlement with an unknown status");
822
+ return;
823
+ }
824
+ const settled = { [ATTR_BLOCKCHAIN_PAYMENT_STATUS]: status };
825
+ const hash = settlement.hash;
826
+ if (typeof hash === "string" && TX_HASH.test(hash)) {
827
+ if (!links.get(input.chainId, hash)) links.set(input.chainId, hash, {
828
+ spanContext: span.spanContext(),
829
+ parent
830
+ });
831
+ settled[ATTR_BLOCKCHAIN_TX_HASH] = hash;
832
+ }
833
+ if (!knownPayer) setPaymentAddress(settled, ATTR_BLOCKCHAIN_PAYMENT_PAYER, settlement.payer);
834
+ const settledAmount = paid === void 0 ? amount(settlement.amount) : void 0;
835
+ if (settledAmount !== void 0) settled[ATTR_BLOCKCHAIN_PAYMENT_AMOUNT] = settledAmount;
836
+ span.setAttributes(redact(settled));
837
+ if (status === "failed") markError(span, identifier(settlement.errorReason) ?? "_OTHER");
838
+ };
839
+ return {
840
+ end: (settlement, options) => finish("record payment settlement", () => recordSettlement(settlement), handleOptions(options).endTime),
841
+ fail: (error, options) => {
842
+ const read = handleOptions(options);
843
+ finish("record payment failure", () => markError(span, reportedErrorType(error, read), error, errorType(error)), read.endTime);
844
+ },
845
+ timeout: (options) => finish("record payment timeout", () => markError(span, PAYMENT_TIMEOUT), handleOptions(options).endTime)
616
846
  };
617
847
  };
618
848
  return {
619
- startSend: (input, parent) => safely("start send span", () => startSend(input, parent), NOOP_SEND),
620
- startConfirm: (input, parent) => safely("start confirm span", () => startConfirm(input, parent), NOOP_CONFIRM)
849
+ startSend: (input, parent) => safely("start send span", () => startSend(input, parent), noopSend(parent ?? context.active())),
850
+ startConfirm: (input, parent) => safely("start confirm span", () => startConfirm(input, parent), NOOP_CONFIRM),
851
+ startPayment: (input, parent) => safely("start payment span", () => startPayment(input, parent), NOOP_PAYMENT)
621
852
  };
622
853
  }
623
854
  //#endregion
624
- 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 };
855
+ 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_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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashspan/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Transaction lifecycle tracing for on-chain actions of AI agents, built on OpenTelemetry.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Selim Aytac",