@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.
- package/dist/cli/commands/claim.d.ts +9 -0
- package/dist/cli/commands/claim.d.ts.map +1 -1
- package/dist/cli/commands/claim.js +12 -1
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/refund.d.ts +2 -0
- package/dist/cli/commands/refund.d.ts.map +1 -1
- package/dist/cli/commands/refund.js +126 -8
- package/dist/cli/commands/refund.js.map +1 -1
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +25 -6
- package/dist/cli/status.js.map +1 -1
- package/dist/gateway/x402-opener-mode.d.ts.map +1 -1
- package/dist/gateway/x402-opener-mode.js +104 -7
- package/dist/gateway/x402-opener-mode.js.map +1 -1
- package/dist/x402-opener/block-floors.d.ts +35 -0
- package/dist/x402-opener/block-floors.d.ts.map +1 -0
- package/dist/x402-opener/block-floors.js +72 -0
- package/dist/x402-opener/block-floors.js.map +1 -0
- package/dist/x402-opener/facilitator-server.d.ts.map +1 -1
- package/dist/x402-opener/facilitator-server.js +7 -0
- package/dist/x402-opener/facilitator-server.js.map +1 -1
- package/dist/x402-opener/index.d.ts +1 -0
- package/dist/x402-opener/index.d.ts.map +1 -1
- package/dist/x402-opener/index.js +1 -0
- package/dist/x402-opener/index.js.map +1 -1
- package/dist/x402-opener/refund-relay.d.ts +62 -1
- package/dist/x402-opener/refund-relay.d.ts.map +1 -1
- package/dist/x402-opener/refund-relay.js +387 -208
- package/dist/x402-opener/refund-relay.js.map +1 -1
- package/dist/x402-opener/scheme.d.ts +8 -0
- package/dist/x402-opener/scheme.d.ts.map +1 -1
- package/dist/x402-opener/scheme.js +14 -0
- package/dist/x402-opener/scheme.js.map +1 -1
- package/docs/cli.md +23 -2
- package/docs/x402.md +83 -26
- package/package.json +2 -2
- package/src/cli/commands/claim.ts +17 -1
- package/src/cli/commands/refund.ts +149 -8
- package/src/cli/status.ts +28 -7
- package/src/gateway/x402-opener-mode.ts +131 -6
- package/src/x402-opener/block-floors.ts +78 -0
- package/src/x402-opener/facilitator-server.ts +7 -0
- package/src/x402-opener/index.ts +1 -0
- package/src/x402-opener/refund-relay.ts +486 -217
- 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
|
? {}
|
package/src/x402-opener/index.ts
CHANGED