@absol-labs/agent 0.12.0-rc.2 → 0.12.0-rc.3

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.
Files changed (46) hide show
  1. package/dist/cli/commands/claim.d.ts +9 -0
  2. package/dist/cli/commands/claim.d.ts.map +1 -1
  3. package/dist/cli/commands/claim.js +12 -1
  4. package/dist/cli/commands/claim.js.map +1 -1
  5. package/dist/cli/commands/refund.d.ts +2 -0
  6. package/dist/cli/commands/refund.d.ts.map +1 -1
  7. package/dist/cli/commands/refund.js +126 -8
  8. package/dist/cli/commands/refund.js.map +1 -1
  9. package/dist/cli/status.d.ts +6 -0
  10. package/dist/cli/status.d.ts.map +1 -1
  11. package/dist/cli/status.js +25 -6
  12. package/dist/cli/status.js.map +1 -1
  13. package/dist/gateway/x402-opener-mode.d.ts.map +1 -1
  14. package/dist/gateway/x402-opener-mode.js +104 -7
  15. package/dist/gateway/x402-opener-mode.js.map +1 -1
  16. package/dist/x402-opener/block-floors.d.ts +35 -0
  17. package/dist/x402-opener/block-floors.d.ts.map +1 -0
  18. package/dist/x402-opener/block-floors.js +72 -0
  19. package/dist/x402-opener/block-floors.js.map +1 -0
  20. package/dist/x402-opener/facilitator-server.d.ts.map +1 -1
  21. package/dist/x402-opener/facilitator-server.js +7 -0
  22. package/dist/x402-opener/facilitator-server.js.map +1 -1
  23. package/dist/x402-opener/index.d.ts +1 -0
  24. package/dist/x402-opener/index.d.ts.map +1 -1
  25. package/dist/x402-opener/index.js +1 -0
  26. package/dist/x402-opener/index.js.map +1 -1
  27. package/dist/x402-opener/refund-relay.d.ts +62 -1
  28. package/dist/x402-opener/refund-relay.d.ts.map +1 -1
  29. package/dist/x402-opener/refund-relay.js +387 -208
  30. package/dist/x402-opener/refund-relay.js.map +1 -1
  31. package/dist/x402-opener/scheme.d.ts +8 -0
  32. package/dist/x402-opener/scheme.d.ts.map +1 -1
  33. package/dist/x402-opener/scheme.js +14 -0
  34. package/dist/x402-opener/scheme.js.map +1 -1
  35. package/docs/cli.md +23 -2
  36. package/docs/x402.md +83 -26
  37. package/package.json +2 -2
  38. package/src/cli/commands/claim.ts +17 -1
  39. package/src/cli/commands/refund.ts +149 -8
  40. package/src/cli/status.ts +28 -7
  41. package/src/gateway/x402-opener-mode.ts +131 -6
  42. package/src/x402-opener/block-floors.ts +78 -0
  43. package/src/x402-opener/facilitator-server.ts +7 -0
  44. package/src/x402-opener/index.ts +1 -0
  45. package/src/x402-opener/refund-relay.ts +486 -217
  46. package/src/x402-opener/scheme.ts +38 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The lowest block a read about a stream must be served from, per stream.
3
+ *
4
+ * A public RPC endpoint is usually a load balancer over several nodes that are
5
+ * not all at the same height: the node that returned a transaction's receipt
6
+ * can be a block or two ahead of the node that answers the next `eth_call` at
7
+ * `latest`. Right after the facilitator settles a payment (or the refund relay
8
+ * mines a close or reclaim), a read at `latest` can therefore still see the
9
+ * stream as it was BEFORE that transaction — not opened yet, still active,
10
+ * not reclaimed (live finding 2026-10-03: the relay answered `not-found` for a
11
+ * stream it had settled half a second earlier).
12
+ *
13
+ * Whoever observes a mined receipt for a stream records its block here; the
14
+ * refund relay then refuses to answer from any block below it (it waits,
15
+ * bounded, for the RPC to catch up and pins every read of one answer to one
16
+ * block). In-memory, per process, bounded; an entry only ever raises.
17
+ */
18
+ export class StreamBlockFloors {
19
+ private readonly floors = new Map<
20
+ string,
21
+ { readonly block: bigint; readonly at: number }
22
+ >();
23
+
24
+ constructor(
25
+ private readonly options: {
26
+ /** How long an entry is kept, ms. Default 1 h. */
27
+ readonly ttlMs?: number;
28
+ /** Hard cap on entries (oldest dropped first). Default 10 000. */
29
+ readonly maxEntries?: number;
30
+ readonly nowMs?: () => number;
31
+ } = {},
32
+ ) {}
33
+
34
+ private now(): number {
35
+ return (this.options.nowMs ?? Date.now)();
36
+ }
37
+
38
+ /** Records that `streamId` changed in `block` (a mined receipt's block). */
39
+ note(streamId: string, block: bigint | number | null | undefined): void {
40
+ if (block === null || block === undefined) return;
41
+ const value = BigInt(block);
42
+ if (value <= 0n) return;
43
+ const key = streamId.toLowerCase();
44
+ const current = this.get(key);
45
+ if (current !== undefined && current >= value) return;
46
+ const max = this.options.maxEntries ?? 10_000;
47
+ if (!this.floors.has(key) && this.floors.size >= max) {
48
+ this.prune();
49
+ while (this.floors.size >= max) {
50
+ const oldest = this.floors.keys().next().value;
51
+ if (oldest === undefined) break;
52
+ this.floors.delete(oldest);
53
+ }
54
+ }
55
+ this.floors.delete(key);
56
+ this.floors.set(key, { block: value, at: this.now() });
57
+ }
58
+
59
+ /** The floor for `streamId`, or `undefined` when none is known. */
60
+ get(streamId: string): bigint | undefined {
61
+ const key = streamId.toLowerCase();
62
+ const entry = this.floors.get(key);
63
+ if (entry === undefined) return undefined;
64
+ if (this.now() - entry.at >= (this.options.ttlMs ?? 3_600_000)) {
65
+ this.floors.delete(key);
66
+ return undefined;
67
+ }
68
+ return entry.block;
69
+ }
70
+
71
+ private prune(): void {
72
+ const ttl = this.options.ttlMs ?? 3_600_000;
73
+ const now = this.now();
74
+ for (const [key, entry] of this.floors) {
75
+ if (now - entry.at >= ttl) this.floors.delete(key);
76
+ }
77
+ }
78
+ }
@@ -36,6 +36,7 @@ import {
36
36
  type RefundRelayHandler,
37
37
  type RefundRelayResponse,
38
38
  } from "./refund-relay.js";
39
+ import { StreamBlockFloors } from "./block-floors.js";
39
40
  import { RelayerQueue } from "./relayer-queue.js";
40
41
  import {
41
42
  MetrikOpenerExactScheme,
@@ -528,10 +529,15 @@ export async function buildOpenerFacilitator(
528
529
  const walletClient = createWalletClient({ account, chain, transport });
529
530
  // ONE queue for every transaction this relayer key sends.
530
531
  const relayerQueue = new RelayerQueue();
532
+ // ONE set of per-stream block floors: the scheme records each settlement's
533
+ // block, and the refund relay never answers below it (a lagging RPC node
534
+ // would otherwise call a stream settled a second ago "not found").
535
+ const streamFloors = new StreamBlockFloors();
531
536
  const receiptTimeoutMs = options.receiptTimeoutMs;
532
537
 
533
538
  const scheme = new MetrikOpenerExactScheme({
534
539
  relayerQueue,
540
+ streamFloors,
535
541
  advertiseRefundRelay: options.refundRelay !== undefined,
536
542
  network: options.network,
537
543
  factoryAddress: options.factoryAddress,
@@ -574,6 +580,7 @@ export async function buildOpenerFacilitator(
574
580
  publicClient,
575
581
  walletClient,
576
582
  relayerQueue,
583
+ streamFloors,
577
584
  ...(receiptTimeoutMs === undefined ? {} : { receiptTimeoutMs }),
578
585
  ...(relay.publicBaseUrl === undefined
579
586
  ? {}
@@ -11,3 +11,4 @@ export * from "./scheme.js";
11
11
  export * from "./facilitator-server.js";
12
12
  export * from "./refund-relay.js";
13
13
  export * from "./relayer-queue.js";
14
+ export * from "./block-floors.js";