@absol-labs/agent 0.12.0-rc.2 → 0.12.0-rc.4
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 +15 -0
- package/dist/cli/commands/claim.d.ts.map +1 -1
- package/dist/cli/commands/claim.js +65 -23
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/pay.d.ts +10 -0
- package/dist/cli/commands/pay.d.ts.map +1 -1
- package/dist/cli/commands/pay.js +246 -50
- package/dist/cli/commands/pay.js.map +1 -1
- package/dist/cli/commands/refund.d.ts +23 -12
- package/dist/cli/commands/refund.d.ts.map +1 -1
- package/dist/cli/commands/refund.js +104 -131
- package/dist/cli/commands/refund.js.map +1 -1
- package/dist/cli/deps.d.ts +6 -0
- package/dist/cli/deps.d.ts.map +1 -1
- package/dist/cli/deps.js +3 -0
- package/dist/cli/deps.js.map +1 -1
- package/dist/cli/errors.d.ts +8 -1
- package/dist/cli/errors.d.ts.map +1 -1
- package/dist/cli/errors.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/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +21 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/x402-tools.d.ts +7 -1
- package/dist/mcp/x402-tools.d.ts.map +1 -1
- package/dist/mcp/x402-tools.js +159 -6
- package/dist/mcp/x402-tools.js.map +1 -1
- package/dist/x402-buyer/index.d.ts +1 -0
- package/dist/x402-buyer/index.d.ts.map +1 -1
- package/dist/x402-buyer/index.js +1 -0
- package/dist/x402-buyer/index.js.map +1 -1
- package/dist/x402-buyer/pay.d.ts +38 -2
- package/dist/x402-buyer/pay.d.ts.map +1 -1
- package/dist/x402-buyer/pay.js +52 -2
- package/dist/x402-buyer/pay.js.map +1 -1
- package/dist/x402-buyer/refund-actions.d.ts +168 -0
- package/dist/x402-buyer/refund-actions.d.ts.map +1 -0
- package/dist/x402-buyer/refund-actions.js +351 -0
- package/dist/x402-buyer/refund-actions.js.map +1 -0
- 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 +62 -9
- package/docs/mcp-reference.md +23 -0
- package/docs/x402.md +126 -29
- package/package.json +2 -2
- package/src/cli/commands/claim.ts +72 -30
- package/src/cli/commands/pay.ts +299 -51
- package/src/cli/commands/refund.ts +133 -143
- package/src/cli/deps.ts +9 -0
- package/src/cli/errors.ts +8 -1
- package/src/cli/status.ts +28 -7
- package/src/gateway/x402-opener-mode.ts +131 -6
- package/src/mcp/server.ts +30 -1
- package/src/mcp/x402-tools.ts +222 -7
- package/src/x402-buyer/index.ts +1 -0
- package/src/x402-buyer/pay.ts +99 -2
- package/src/x402-buyer/refund-actions.ts +486 -0
- 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,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buyer's own close + reclaim of an x402 stream, through the seller's
|
|
3
|
+
* gasless refund relay. ONE implementation behind `metrik refund`,
|
|
4
|
+
* `metrik pay --close-after`, the MCP `x402_pay` tool's `closeAfter` and
|
|
5
|
+
* {@link closeAfterPayment} (`@absol-labs/agent/x402-buyer`).
|
|
6
|
+
*
|
|
7
|
+
* Nothing here is an automatic refund: it runs only when the buyer asks for
|
|
8
|
+
* it, and it is the buyer's own client that signs the Close and asks the
|
|
9
|
+
* relay to send the permissionless reclaim. The relay pays the gas; the
|
|
10
|
+
* opener always sends the reclaim to the payer.
|
|
11
|
+
*
|
|
12
|
+
* The waits are bounded and the relay's own: `RefundClient.status()` waits
|
|
13
|
+
* out a `503 chain-lagging` (honouring `Retry-After`), and
|
|
14
|
+
* `RefundClient.reclaim()` retries, bounded by the client's `maxAttempts` /
|
|
15
|
+
* `maxDelayMs`, while the relay says the checkpoint covering the close is not
|
|
16
|
+
* posted yet.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* After a close/reclaim, how many times the summary re-reads the relay until
|
|
20
|
+
* it reflects the transaction just mined (a load-balanced RPC behind the relay
|
|
21
|
+
* can answer from a node that has not seen it yet), and how long it waits
|
|
22
|
+
* between reads. Bounded: past it the summary is derived from the verified
|
|
23
|
+
* receipts instead.
|
|
24
|
+
*/
|
|
25
|
+
export const POST_ACTION_READS = 6;
|
|
26
|
+
export const POST_ACTION_INTERVAL_MS = 1_500;
|
|
27
|
+
/**
|
|
28
|
+
* A close or reclaim that did not complete. `closed` says whether the stream
|
|
29
|
+
* is known to be closed (by this run or before it), so the caller can name the
|
|
30
|
+
* exact command that finishes the job. `cause` is the SDK's error, unchanged.
|
|
31
|
+
*/
|
|
32
|
+
export class RefundActionsError extends Error {
|
|
33
|
+
stage;
|
|
34
|
+
closed;
|
|
35
|
+
closeResult;
|
|
36
|
+
constructor(context) {
|
|
37
|
+
super(`the ${context.stage} did not finish: ${context.cause instanceof Error ? context.cause.message : String(context.cause)}`, { cause: context.cause });
|
|
38
|
+
this.name = "RefundActionsError";
|
|
39
|
+
this.stage = context.stage;
|
|
40
|
+
this.closed = context.closed;
|
|
41
|
+
this.closeResult = context.closeResult;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Close and/or reclaim one x402 stream through its refund relay, then read
|
|
46
|
+
* the state AFTER those actions. Finished states (already closed, already
|
|
47
|
+
* reclaimed, nothing to reclaim) are reported, not thrown. Anything else that
|
|
48
|
+
* fails is a {@link RefundActionsError} carrying the SDK's error as `cause`.
|
|
49
|
+
*/
|
|
50
|
+
export async function runRefundActions(input) {
|
|
51
|
+
const { refunds } = input;
|
|
52
|
+
let closeResult = null;
|
|
53
|
+
let closeSkipped = null;
|
|
54
|
+
// Set when the relay itself says there is nothing left: the stream was
|
|
55
|
+
// already reclaimed, or the verified delivery covers the whole deposit.
|
|
56
|
+
// Those are FINISHED recoveries, not failures — a second close + reclaim on
|
|
57
|
+
// a done stream must succeed and never point the payer at a gas-spending
|
|
58
|
+
// on-chain recovery that cannot recover a cent.
|
|
59
|
+
let finishedByRelay = null;
|
|
60
|
+
if (input.close) {
|
|
61
|
+
try {
|
|
62
|
+
// Ask first: a stream that is already closed (or already finished)
|
|
63
|
+
// needs no Close, so there is nothing to sign and no wallet is needed.
|
|
64
|
+
const before = await refunds.status();
|
|
65
|
+
const finishedBefore = finishedReason(before);
|
|
66
|
+
if (finishedBefore !== null || before.status === "closed") {
|
|
67
|
+
closeSkipped = finishedBefore ?? "already-closed";
|
|
68
|
+
finishedByRelay = finishedBefore;
|
|
69
|
+
input.note?.(finishedBefore === null
|
|
70
|
+
? "stream is already closed — no Close to sign."
|
|
71
|
+
: `stream is already closed and ${finishedBefore === "reclaimed" ? "reclaimed" : "has nothing left to reclaim"} — no Close to sign.`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const signer = await input.closeSigner();
|
|
75
|
+
try {
|
|
76
|
+
closeResult = await refunds.close({ signer });
|
|
77
|
+
input.note?.(`close relayed in ${closeResult.transaction}${closeResult.verified ? " (receipt verified on-chain)" : ""}`);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// The status read above said "active", but the stream closed in the
|
|
81
|
+
// meantime (it reached expiresAt, or another close landed): the
|
|
82
|
+
// state the payer asked for. Anything else is a real failure.
|
|
83
|
+
if (relayCode(error) !== "already-closed")
|
|
84
|
+
throw error;
|
|
85
|
+
closeSkipped = "already-closed";
|
|
86
|
+
input.note?.("stream was already closed — no Close was needed.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw new RefundActionsError({
|
|
92
|
+
stage: "close",
|
|
93
|
+
closed: false,
|
|
94
|
+
closeResult: null,
|
|
95
|
+
cause: error,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
let reclaimResult = null;
|
|
100
|
+
let reclaimSkipped = null;
|
|
101
|
+
if (input.reclaim) {
|
|
102
|
+
if (finishedByRelay !== null) {
|
|
103
|
+
reclaimSkipped = finishedByRelay;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
if (input.reclaimActivity !== undefined) {
|
|
107
|
+
input.setActivity?.(input.reclaimActivity);
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
reclaimResult = await refunds.reclaim();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const finished = finishedFromRelayCode(relayCode(error));
|
|
114
|
+
if (finished === null) {
|
|
115
|
+
throw new RefundActionsError({
|
|
116
|
+
stage: "reclaim",
|
|
117
|
+
closed: closeResult !== null || closeSkipped !== null,
|
|
118
|
+
closeResult,
|
|
119
|
+
cause: error,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
reclaimSkipped = finished;
|
|
123
|
+
finishedByRelay = finished;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
input.setActivity?.(null);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// The summary describes the state AFTER what this run did: re-read until
|
|
131
|
+
// the relay reflects the close/reclaim just mined (bounded), else derive it
|
|
132
|
+
// from the verified receipts. Never the pre-action state.
|
|
133
|
+
const status = await statusAfterActions(refunds, {
|
|
134
|
+
closed: closeResult !== null,
|
|
135
|
+
reclaim: reclaimResult,
|
|
136
|
+
payer: input.payer,
|
|
137
|
+
opener: input.opener,
|
|
138
|
+
streamId: input.streamId,
|
|
139
|
+
sleep: input.sleep,
|
|
140
|
+
});
|
|
141
|
+
// A reclaim THIS run performed is reported as a reclaim, not as "already
|
|
142
|
+
// finished".
|
|
143
|
+
const finished = reclaimResult === null ? (finishedReason(status) ?? finishedByRelay) : null;
|
|
144
|
+
return {
|
|
145
|
+
close: closeResult,
|
|
146
|
+
closeSkipped,
|
|
147
|
+
reclaim: reclaimResult,
|
|
148
|
+
reclaimSkipped,
|
|
149
|
+
status,
|
|
150
|
+
finished,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Right after a paid response, at the BUYER's request: close the stream the
|
|
155
|
+
* payment opened (the payer signs the Close, the relay pays the gas), wait for
|
|
156
|
+
* the checkpoint covering the close, and reclaim the unearned remainder to the
|
|
157
|
+
* payer. Never throws for a close/reclaim failure: the payment already
|
|
158
|
+
* succeeded, so the outcome says what did not finish instead.
|
|
159
|
+
*
|
|
160
|
+
* An upfront-only payment (no stream) has nothing to close: the outcome is
|
|
161
|
+
* `nothing-to-close` and nothing is requested, signed or sent.
|
|
162
|
+
*/
|
|
163
|
+
export async function closeAfterPayment(input) {
|
|
164
|
+
const { payment } = input;
|
|
165
|
+
const empty = {
|
|
166
|
+
close: null,
|
|
167
|
+
closeSkipped: null,
|
|
168
|
+
reclaim: null,
|
|
169
|
+
reclaimSkipped: null,
|
|
170
|
+
refundedUsdc: null,
|
|
171
|
+
finished: null,
|
|
172
|
+
status: null,
|
|
173
|
+
};
|
|
174
|
+
if (payment.streamId === null || /^0x0{64}$/i.test(payment.streamId)) {
|
|
175
|
+
return {
|
|
176
|
+
...empty,
|
|
177
|
+
outcome: "nothing-to-close",
|
|
178
|
+
streamId: null,
|
|
179
|
+
incomplete: null,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const streamId = payment.streamId;
|
|
183
|
+
if (payment.refundUrl === null) {
|
|
184
|
+
return {
|
|
185
|
+
...empty,
|
|
186
|
+
outcome: "incomplete",
|
|
187
|
+
streamId,
|
|
188
|
+
incomplete: {
|
|
189
|
+
stage: "no-relay",
|
|
190
|
+
closed: false,
|
|
191
|
+
reason: "the seller advertised no refund relay, so there is no gasless close; the stream accrues only for verified seconds until it expires, and anyone can reclaim the unearned remainder to the payer after that",
|
|
192
|
+
error: null,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
let refunds;
|
|
197
|
+
try {
|
|
198
|
+
refunds = await input.openRefunds();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
...empty,
|
|
203
|
+
outcome: "incomplete",
|
|
204
|
+
streamId,
|
|
205
|
+
incomplete: {
|
|
206
|
+
stage: "close",
|
|
207
|
+
closed: false,
|
|
208
|
+
reason: errorMessage(error),
|
|
209
|
+
error,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const result = await runRefundActions({
|
|
215
|
+
...input,
|
|
216
|
+
refunds,
|
|
217
|
+
streamId,
|
|
218
|
+
opener: payment.opener,
|
|
219
|
+
close: true,
|
|
220
|
+
reclaim: true,
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
outcome: "completed",
|
|
224
|
+
streamId,
|
|
225
|
+
close: result.close,
|
|
226
|
+
closeSkipped: result.closeSkipped,
|
|
227
|
+
reclaim: result.reclaim,
|
|
228
|
+
reclaimSkipped: result.reclaimSkipped,
|
|
229
|
+
refundedUsdc: result.reclaim !== null && result.reclaim.verified
|
|
230
|
+
? result.reclaim.amount
|
|
231
|
+
: null,
|
|
232
|
+
finished: result.finished,
|
|
233
|
+
status: result.status,
|
|
234
|
+
incomplete: null,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (!(error instanceof RefundActionsError))
|
|
239
|
+
throw error;
|
|
240
|
+
return {
|
|
241
|
+
...empty,
|
|
242
|
+
outcome: "incomplete",
|
|
243
|
+
streamId,
|
|
244
|
+
close: error.closeResult,
|
|
245
|
+
incomplete: {
|
|
246
|
+
stage: error.stage,
|
|
247
|
+
closed: error.closed,
|
|
248
|
+
reason: errorMessage(error.cause),
|
|
249
|
+
error: error.cause,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function errorMessage(error) {
|
|
255
|
+
return error instanceof Error ? error.message : String(error);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The relay's view of a stream AFTER this run's actions. Reads until it
|
|
259
|
+
* reflects them (closed after a close, reclaimed after a reclaim), bounded;
|
|
260
|
+
* if the relay still lags, the state those verified transactions imply is
|
|
261
|
+
* reported instead, marked as derived. With no action, one plain read.
|
|
262
|
+
*/
|
|
263
|
+
export async function statusAfterActions(refunds, done) {
|
|
264
|
+
const reclaimed = done.reclaim !== null;
|
|
265
|
+
if (!done.closed && !reclaimed)
|
|
266
|
+
return await refunds.status();
|
|
267
|
+
const reflects = (status) => (!done.closed || status.status === "closed" || status.reclaimed) &&
|
|
268
|
+
(!reclaimed || status.reclaimed);
|
|
269
|
+
let last = null;
|
|
270
|
+
for (let attempt = 1; attempt <= POST_ACTION_READS; attempt++) {
|
|
271
|
+
try {
|
|
272
|
+
const status = await refunds.status();
|
|
273
|
+
if (reflects(status))
|
|
274
|
+
return status;
|
|
275
|
+
last = status;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// A lagging relay (503 chain-lagging, a stale not-found, a blip): the
|
|
279
|
+
// transactions already happened, so keep going and derive if need be.
|
|
280
|
+
}
|
|
281
|
+
if (attempt < POST_ACTION_READS)
|
|
282
|
+
await done.sleep(POST_ACTION_INTERVAL_MS);
|
|
283
|
+
}
|
|
284
|
+
return derivedStatus(last, done);
|
|
285
|
+
}
|
|
286
|
+
/** The state this run's verified close/reclaim imply, when the relay lags. */
|
|
287
|
+
function derivedStatus(last, done) {
|
|
288
|
+
const reclaimed = done.reclaim !== null;
|
|
289
|
+
const note = "derived from this run's transaction receipts; the refund relay had not caught up with them yet";
|
|
290
|
+
return {
|
|
291
|
+
streamId: done.streamId,
|
|
292
|
+
chainId: last?.chainId ?? 0,
|
|
293
|
+
opener: last?.opener ?? done.opener,
|
|
294
|
+
payer: last?.payer ?? done.payer ?? done.opener,
|
|
295
|
+
status: "closed",
|
|
296
|
+
reclaimed: reclaimed || (last?.reclaimed ?? false),
|
|
297
|
+
chainTime: last?.chainTime ?? null,
|
|
298
|
+
reclaimable: null,
|
|
299
|
+
// Both legs this run verified are on-chain facts: the stream is closed.
|
|
300
|
+
state: "closed",
|
|
301
|
+
onChainStatus: "closed",
|
|
302
|
+
maxRefund: last?.maxRefund ?? null,
|
|
303
|
+
maxRefundMeaning: last?.maxRefundMeaning ?? null,
|
|
304
|
+
reclaimableMeaning: last?.reclaimableMeaning ?? null,
|
|
305
|
+
next: reclaimed
|
|
306
|
+
? {
|
|
307
|
+
action: "none",
|
|
308
|
+
message: "Reclaimed: the unearned remainder was returned to the payer. Nothing further to do.",
|
|
309
|
+
}
|
|
310
|
+
: {
|
|
311
|
+
action: "reclaim",
|
|
312
|
+
message: "Closed. Reclaim the unearned remainder once a checkpoint covering the close is posted (usually within a minute).",
|
|
313
|
+
},
|
|
314
|
+
raw: { derived: true, note },
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Whether the relay's status says nothing is left to recover. `reclaimed` is
|
|
319
|
+
* the chain's one-reclaim-per-stream flag; `next: none` with `reclaimable: 0`
|
|
320
|
+
* is the relay's "the verified delivery covers the whole deposit".
|
|
321
|
+
*/
|
|
322
|
+
export function finishedReason(status) {
|
|
323
|
+
if (status.reclaimed)
|
|
324
|
+
return "reclaimed";
|
|
325
|
+
if (status.next.action === "none" && status.reclaimable === 0n) {
|
|
326
|
+
return "nothing-to-reclaim";
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
/** The relay's 409 codes that mean "finished", not "failed". */
|
|
331
|
+
function finishedFromRelayCode(code) {
|
|
332
|
+
if (code === "already-reclaimed")
|
|
333
|
+
return "reclaimed";
|
|
334
|
+
if (code === "nothing-to-reclaim")
|
|
335
|
+
return "nothing-to-reclaim";
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
/** The relay's refusal code, looked up through a few `cause` links. */
|
|
339
|
+
export function relayCode(error) {
|
|
340
|
+
for (let current = error, depth = 0; depth < 4; depth++) {
|
|
341
|
+
if (!(current instanceof Error))
|
|
342
|
+
return null;
|
|
343
|
+
const code = current.code;
|
|
344
|
+
if (current.name === "RefundRelayError" && typeof code === "string") {
|
|
345
|
+
return code;
|
|
346
|
+
}
|
|
347
|
+
current = current.cause;
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=refund-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refund-actions.js","sourceRoot":"","sources":["../../src/x402-buyer/refund-actions.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAiD7C;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,KAAK,CAAoB;IACzB,MAAM,CAAU;IAChB,WAAW,CAA2B;IAE/C,YAAY,OAKX;QACC,KAAK,CACH,OAAO,OAAO,CAAC,KAAK,oBAAoB,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EACxH,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CACzB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAyB;IAEzB,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC1B,IAAI,WAAW,GAA6B,IAAI,CAAC;IACjD,IAAI,YAAY,GAAsB,IAAI,CAAC;IAC3C,uEAAuE;IACvE,wEAAwE;IACxE,4EAA4E;IAC5E,yEAAyE;IACzE,gDAAgD;IAChD,IAAI,eAAe,GAA0B,IAAI,CAAC;IAClD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,mEAAmE;YACnE,uEAAuE;YACvE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;YACtC,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,cAAc,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC1D,YAAY,GAAG,cAAc,IAAI,gBAAgB,CAAC;gBAClD,eAAe,GAAG,cAAc,CAAC;gBACjC,KAAK,CAAC,IAAI,EAAE,CACV,cAAc,KAAK,IAAI;oBACrB,CAAC,CAAC,8CAA8C;oBAChD,CAAC,CAAC,gCAAgC,cAAc,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,6BAA6B,sBAAsB,CACvI,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;gBACzC,IAAI,CAAC;oBACH,WAAW,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;oBAC9C,KAAK,CAAC,IAAI,EAAE,CACV,oBAAoB,WAAW,CAAC,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3G,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,oEAAoE;oBACpE,gEAAgE;oBAChE,8DAA8D;oBAC9D,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,gBAAgB;wBAAE,MAAM,KAAK,CAAC;oBACvD,YAAY,GAAG,gBAAgB,CAAC;oBAChC,KAAK,CAAC,IAAI,EAAE,CAAC,kDAAkD,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,kBAAkB,CAAC;gBAC3B,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,KAAK;gBACb,WAAW,EAAE,IAAI;gBACjB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,aAAa,GAA6B,IAAI,CAAC;IACnD,IAAI,cAAc,GAAsB,IAAI,CAAC;IAC7C,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;YAC7B,cAAc,GAAG,eAAe,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBACxC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YAC7C,CAAC;YACD,IAAI,CAAC;gBACH,aAAa,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;gBACzD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;oBACtB,MAAM,IAAI,kBAAkB,CAAC;wBAC3B,KAAK,EAAE,SAAS;wBAChB,MAAM,EAAE,WAAW,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI;wBACrD,WAAW;wBACX,KAAK,EAAE,KAAK;qBACb,CAAC,CAAC;gBACL,CAAC;gBACD,cAAc,GAAG,QAAQ,CAAC;gBAC1B,eAAe,GAAG,QAAQ,CAAC;YAC7B,CAAC;oBAAS,CAAC;gBACT,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,0DAA0D;IAC1D,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE;QAC/C,MAAM,EAAE,WAAW,KAAK,IAAI;QAC5B,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC,CAAC;IACH,yEAAyE;IACzE,aAAa;IACb,MAAM,QAAQ,GACZ,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9E,OAAO;QACL,KAAK,EAAE,WAAW;QAClB,YAAY;QACZ,OAAO,EAAE,aAAa;QACtB,cAAc;QACd,MAAM;QACN,QAAQ;KACT,CAAC;AACJ,CAAC;AA+CD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAA6B;IAE7B,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC1B,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,IAAI;QACX,YAAY,EAAE,IAAI;QAClB,OAAO,EAAE,IAAI;QACb,cAAc,EAAE,IAAI;QACpB,YAAY,EAAE,IAAI;QAClB,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;KACJ,CAAC;IACX,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrE,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,kBAAkB;YAC3B,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,YAAY;YACrB,QAAQ;YACR,UAAU,EAAE;gBACV,KAAK,EAAE,UAAU;gBACjB,MAAM,EAAE,KAAK;gBACb,MAAM,EACJ,2MAA2M;gBAC7M,KAAK,EAAE,IAAI;aACZ;SACF,CAAC;IACJ,CAAC;IACD,IAAI,OAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,YAAY;YACrB,QAAQ;YACR,UAAU,EAAE;gBACV,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC;gBAC3B,KAAK;aACN;SACF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC;YACpC,GAAG,KAAK;YACR,OAAO;YACP,QAAQ;YACR,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,IAAI;YACX,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,QAAQ;YACR,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,YAAY,EACV,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ;gBAChD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;gBACvB,CAAC,CAAC,IAAI;YACV,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,kBAAkB,CAAC;YAAE,MAAM,KAAK,CAAC;QACxD,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,YAAY;YACrB,QAAQ;YACR,KAAK,EAAE,KAAK,CAAC,WAAW;YACxB,UAAU,EAAE;gBACV,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;gBACjC,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAqB,EACrB,IAOC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IACxC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS;QAAE,OAAO,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;IAC9D,MAAM,QAAQ,GAAG,CAAC,MAAoB,EAAW,EAAE,CACjD,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC;QAChE,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,IAAI,GAAwB,IAAI,CAAC;IACrC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,iBAAiB,EAAE,OAAO,EAAE,EAAE,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;YACtC,IAAI,QAAQ,CAAC,MAAM,CAAC;gBAAE,OAAO,MAAM,CAAC;YACpC,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,sEAAsE;QACxE,CAAC;QACD,IAAI,OAAO,GAAG,iBAAiB;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,8EAA8E;AAC9E,SAAS,aAAa,CACpB,IAAyB,EACzB,IAMC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IACxC,MAAM,IAAI,GACR,gGAAgG,CAAC;IACnG,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC;QAC3B,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC,MAAM;QACnC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM;QAC/C,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,SAAS,IAAI,CAAC,IAAI,EAAE,SAAS,IAAI,KAAK,CAAC;QAClD,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,IAAI;QAClC,WAAW,EAAE,IAAI;QACjB,wEAAwE;QACxE,KAAK,EAAE,QAAQ;QACf,aAAa,EAAE,QAAQ;QACvB,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,IAAI;QAClC,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,IAAI,IAAI;QAChD,kBAAkB,EAAE,IAAI,EAAE,kBAAkB,IAAI,IAAI;QACpD,IAAI,EAAE,SAAS;YACb,CAAC,CAAC;gBACE,MAAM,EAAE,MAAM;gBACd,OAAO,EACL,qFAAqF;aACxF;YACH,CAAC,CAAC;gBACE,MAAM,EAAE,SAAS;gBACjB,OAAO,EACL,kHAAkH;aACrH;QACL,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;KACN,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,IAAI,MAAM,CAAC,SAAS;QAAE,OAAO,WAAW,CAAC;IACzC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,WAAW,KAAK,EAAE,EAAE,CAAC;QAC/D,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gEAAgE;AAChE,SAAS,qBAAqB,CAAC,IAAmB;IAChD,IAAI,IAAI,KAAK,mBAAmB;QAAE,OAAO,WAAW,CAAC;IACrD,IAAI,IAAI,KAAK,oBAAoB;QAAE,OAAO,oBAAoB,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,KAAK,IAAI,OAAO,GAAY,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QACjE,IAAI,CAAC,CAAC,OAAO,YAAY,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7C,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAC;QAClD,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 declare class StreamBlockFloors {
|
|
19
|
+
private readonly options;
|
|
20
|
+
private readonly floors;
|
|
21
|
+
constructor(options?: {
|
|
22
|
+
/** How long an entry is kept, ms. Default 1 h. */
|
|
23
|
+
readonly ttlMs?: number;
|
|
24
|
+
/** Hard cap on entries (oldest dropped first). Default 10 000. */
|
|
25
|
+
readonly maxEntries?: number;
|
|
26
|
+
readonly nowMs?: () => number;
|
|
27
|
+
});
|
|
28
|
+
private now;
|
|
29
|
+
/** Records that `streamId` changed in `block` (a mined receipt's block). */
|
|
30
|
+
note(streamId: string, block: bigint | number | null | undefined): void;
|
|
31
|
+
/** The floor for `streamId`, or `undefined` when none is known. */
|
|
32
|
+
get(streamId: string): bigint | undefined;
|
|
33
|
+
private prune;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=block-floors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-floors.d.ts","sourceRoot":"","sources":["../../src/x402-opener/block-floors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAiB;IAO1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGnB;gBAGe,OAAO,GAAE;QACxB,kDAAkD;QAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,kEAAkE;QAClE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;KAC1B;IAGR,OAAO,CAAC,GAAG;IAIX,4EAA4E;IAC5E,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAoBvE,mEAAmE;IACnE,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAWzC,OAAO,CAAC,KAAK;CAOd"}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
options;
|
|
20
|
+
floors = new Map();
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
}
|
|
24
|
+
now() {
|
|
25
|
+
return (this.options.nowMs ?? Date.now)();
|
|
26
|
+
}
|
|
27
|
+
/** Records that `streamId` changed in `block` (a mined receipt's block). */
|
|
28
|
+
note(streamId, block) {
|
|
29
|
+
if (block === null || block === undefined)
|
|
30
|
+
return;
|
|
31
|
+
const value = BigInt(block);
|
|
32
|
+
if (value <= 0n)
|
|
33
|
+
return;
|
|
34
|
+
const key = streamId.toLowerCase();
|
|
35
|
+
const current = this.get(key);
|
|
36
|
+
if (current !== undefined && current >= value)
|
|
37
|
+
return;
|
|
38
|
+
const max = this.options.maxEntries ?? 10_000;
|
|
39
|
+
if (!this.floors.has(key) && this.floors.size >= max) {
|
|
40
|
+
this.prune();
|
|
41
|
+
while (this.floors.size >= max) {
|
|
42
|
+
const oldest = this.floors.keys().next().value;
|
|
43
|
+
if (oldest === undefined)
|
|
44
|
+
break;
|
|
45
|
+
this.floors.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.floors.delete(key);
|
|
49
|
+
this.floors.set(key, { block: value, at: this.now() });
|
|
50
|
+
}
|
|
51
|
+
/** The floor for `streamId`, or `undefined` when none is known. */
|
|
52
|
+
get(streamId) {
|
|
53
|
+
const key = streamId.toLowerCase();
|
|
54
|
+
const entry = this.floors.get(key);
|
|
55
|
+
if (entry === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (this.now() - entry.at >= (this.options.ttlMs ?? 3_600_000)) {
|
|
58
|
+
this.floors.delete(key);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return entry.block;
|
|
62
|
+
}
|
|
63
|
+
prune() {
|
|
64
|
+
const ttl = this.options.ttlMs ?? 3_600_000;
|
|
65
|
+
const now = this.now();
|
|
66
|
+
for (const [key, entry] of this.floors) {
|
|
67
|
+
if (now - entry.at >= ttl)
|
|
68
|
+
this.floors.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=block-floors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-floors.js","sourceRoot":"","sources":["../../src/x402-opener/block-floors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,iBAAiB;IAOT;IANF,MAAM,GAAG,IAAI,GAAG,EAG9B,CAAC;IAEJ,YACmB,UAMb,EAAE;QANW,YAAO,GAAP,OAAO,CAMlB;IACL,CAAC;IAEI,GAAG;QACT,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5C,CAAC;IAED,4EAA4E;IAC5E,IAAI,CAAC,QAAgB,EAAE,KAAyC;QAC9D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,IAAI,EAAE;YAAE,OAAO;QACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK;YAAE,OAAO;QACtD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;gBAC/C,IAAI,MAAM,KAAK,SAAS;oBAAE,MAAM;gBAChC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,mEAAmE;IACnE,GAAG,CAAC,QAAgB;QAClB,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC1C,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAEO,KAAK;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,GAAG,GAAG,KAAK,CAAC,EAAE,IAAI,GAAG;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facilitator-server.d.ts","sourceRoot":"","sources":["../../src/x402-opener/facilitator-server.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,MAAM,EAEZ,MAAM,WAAW,CAAC;AAInB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAOL,KAAK,OAAO,EACZ,KAAK,GAAG,EACT,MAAM,MAAM,CAAC;AAId,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAIL,iBAAiB,EACjB,KAAK,kBAAkB,EAExB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"facilitator-server.d.ts","sourceRoot":"","sources":["../../src/x402-opener/facilitator-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { privateKeyToAccount } from "viem/accounts";
|
|
|
7
7
|
import { DEFAULT_METRIK_PUBLIC_REGISTRY_URL } from "../discovery/registry.js";
|
|
8
8
|
import { DEFAULT_LISTING_CACHE_SECONDS, RegistryListingResolver, } from "./listing-resolver.js";
|
|
9
9
|
import { DEFAULT_REFUND_READ_CACHE_SECONDS, DEFAULT_REFUND_SUBMISSIONS_PER_MINUTE, DEFAULT_REFUND_SUBMISSIONS_PER_STREAM_PER_MINUTE, OpenerRefundRelay, } from "./refund-relay.js";
|
|
10
|
+
import { StreamBlockFloors } from "./block-floors.js";
|
|
10
11
|
import { RelayerQueue } from "./relayer-queue.js";
|
|
11
12
|
import { MetrikOpenerExactScheme, } from "./scheme.js";
|
|
12
13
|
import { chainIdFromNetwork } from "./terms.js";
|
|
@@ -342,9 +343,14 @@ export async function buildOpenerFacilitator(options) {
|
|
|
342
343
|
const walletClient = createWalletClient({ account, chain, transport });
|
|
343
344
|
// ONE queue for every transaction this relayer key sends.
|
|
344
345
|
const relayerQueue = new RelayerQueue();
|
|
346
|
+
// ONE set of per-stream block floors: the scheme records each settlement's
|
|
347
|
+
// block, and the refund relay never answers below it (a lagging RPC node
|
|
348
|
+
// would otherwise call a stream settled a second ago "not found").
|
|
349
|
+
const streamFloors = new StreamBlockFloors();
|
|
345
350
|
const receiptTimeoutMs = options.receiptTimeoutMs;
|
|
346
351
|
const scheme = new MetrikOpenerExactScheme({
|
|
347
352
|
relayerQueue,
|
|
353
|
+
streamFloors,
|
|
348
354
|
advertiseRefundRelay: options.refundRelay !== undefined,
|
|
349
355
|
network: options.network,
|
|
350
356
|
factoryAddress: options.factoryAddress,
|
|
@@ -385,6 +391,7 @@ export async function buildOpenerFacilitator(options) {
|
|
|
385
391
|
publicClient,
|
|
386
392
|
walletClient,
|
|
387
393
|
relayerQueue,
|
|
394
|
+
streamFloors,
|
|
388
395
|
...(receiptTimeoutMs === undefined ? {} : { receiptTimeoutMs }),
|
|
389
396
|
...(relay.publicBaseUrl === undefined
|
|
390
397
|
? {}
|