@absol-labs/agent 0.12.0-rc.3 → 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.
Files changed (53) hide show
  1. package/dist/cli/commands/claim.d.ts +6 -0
  2. package/dist/cli/commands/claim.d.ts.map +1 -1
  3. package/dist/cli/commands/claim.js +53 -22
  4. package/dist/cli/commands/claim.js.map +1 -1
  5. package/dist/cli/commands/pay.d.ts +10 -0
  6. package/dist/cli/commands/pay.d.ts.map +1 -1
  7. package/dist/cli/commands/pay.js +246 -50
  8. package/dist/cli/commands/pay.js.map +1 -1
  9. package/dist/cli/commands/refund.d.ts +21 -12
  10. package/dist/cli/commands/refund.d.ts.map +1 -1
  11. package/dist/cli/commands/refund.js +61 -206
  12. package/dist/cli/commands/refund.js.map +1 -1
  13. package/dist/cli/deps.d.ts +6 -0
  14. package/dist/cli/deps.d.ts.map +1 -1
  15. package/dist/cli/deps.js +3 -0
  16. package/dist/cli/deps.js.map +1 -1
  17. package/dist/cli/errors.d.ts +8 -1
  18. package/dist/cli/errors.d.ts.map +1 -1
  19. package/dist/cli/errors.js.map +1 -1
  20. package/dist/mcp/server.d.ts +10 -0
  21. package/dist/mcp/server.d.ts.map +1 -1
  22. package/dist/mcp/server.js +21 -1
  23. package/dist/mcp/server.js.map +1 -1
  24. package/dist/mcp/x402-tools.d.ts +7 -1
  25. package/dist/mcp/x402-tools.d.ts.map +1 -1
  26. package/dist/mcp/x402-tools.js +159 -6
  27. package/dist/mcp/x402-tools.js.map +1 -1
  28. package/dist/x402-buyer/index.d.ts +1 -0
  29. package/dist/x402-buyer/index.d.ts.map +1 -1
  30. package/dist/x402-buyer/index.js +1 -0
  31. package/dist/x402-buyer/index.js.map +1 -1
  32. package/dist/x402-buyer/pay.d.ts +38 -2
  33. package/dist/x402-buyer/pay.d.ts.map +1 -1
  34. package/dist/x402-buyer/pay.js +52 -2
  35. package/dist/x402-buyer/pay.js.map +1 -1
  36. package/dist/x402-buyer/refund-actions.d.ts +168 -0
  37. package/dist/x402-buyer/refund-actions.d.ts.map +1 -0
  38. package/dist/x402-buyer/refund-actions.js +351 -0
  39. package/dist/x402-buyer/refund-actions.js.map +1 -0
  40. package/docs/cli.md +39 -7
  41. package/docs/mcp-reference.md +23 -0
  42. package/docs/x402.md +43 -3
  43. package/package.json +1 -1
  44. package/src/cli/commands/claim.ts +55 -29
  45. package/src/cli/commands/pay.ts +299 -51
  46. package/src/cli/commands/refund.ts +89 -240
  47. package/src/cli/deps.ts +9 -0
  48. package/src/cli/errors.ts +8 -1
  49. package/src/mcp/server.ts +30 -1
  50. package/src/mcp/x402-tools.ts +222 -7
  51. package/src/x402-buyer/index.ts +1 -0
  52. package/src/x402-buyer/pay.ts +99 -2
  53. package/src/x402-buyer/refund-actions.ts +486 -0
@@ -6,6 +6,13 @@ import type {
6
6
  } from "@absol-labs/sdk/x402";
7
7
 
8
8
  import { verifyMandateOwnerSignature } from "../../mandates/mandate.js";
9
+ import {
10
+ RefundActionsError,
11
+ runRefundActions,
12
+ type FinishedReason,
13
+ type RefundActionsResult,
14
+ type SkipReason,
15
+ } from "../../x402-buyer/refund-actions.js";
9
16
  import { booleanFlag, integerFlag, stringFlag } from "../args.js";
10
17
  import { formatUsdc } from "../amounts.js";
11
18
  import { assertTestnetRpc, parseBytes32 } from "../config.js";
@@ -20,20 +27,15 @@ import {
20
27
  } from "../wallet.js";
21
28
  import type { CommandContext, CommandSpec } from "../context.js";
22
29
 
30
+ export { finishedReason } from "../../x402-buyer/refund-actions.js";
31
+ export type { FinishedReason, SkipReason };
32
+
23
33
  /** Environment fallback for `--refund-url`. */
24
34
  export const REFUND_URL_ENV = "METRIK_AGENT_REFUND_URL";
25
35
 
26
- const DEFAULT_ATTEMPTS = 5;
27
- const DEFAULT_MAX_WAIT_SECONDS = 60;
28
- /**
29
- * After a close/reclaim, how many times the summary re-reads the relay until
30
- * it reflects the transaction just mined (a load-balanced RPC behind the relay
31
- * can answer from a node that has not seen it yet), and how long it waits
32
- * between reads. Bounded: past it the summary is derived from the verified
33
- * receipts instead.
34
- */
35
- const POST_ACTION_READS = 6;
36
- const POST_ACTION_INTERVAL_MS = 1_500;
36
+ /** Relay attempts / longest single wait of a reclaim (also `pay --close-after`'s). */
37
+ export const DEFAULT_ATTEMPTS = 5;
38
+ export const DEFAULT_MAX_WAIT_SECONDS = 60;
37
39
 
38
40
  export const refundCommand: CommandSpec = {
39
41
  name: "refund",
@@ -106,112 +108,39 @@ export const refundCommand: CommandSpec = {
106
108
  maxDelayMs: maxWait * 1_000,
107
109
  });
108
110
 
109
- let closeResult: RefundRelayResult | null = null;
110
- let closeSkipped: SkipReason | null = null;
111
- // Set when the relay itself says there is nothing left: the stream was
112
- // already reclaimed, or the verified delivery covers the whole deposit.
113
- // Those are FINISHED recoveries, not failures — a second
114
- // `--close --reclaim` on a done stream must exit 0 and must never point
115
- // the payer at a gas-spending on-chain recovery that cannot recover a cent.
116
- let finishedByRelay: FinishedReason | null = null;
117
- if (doClose) {
118
- // Ask first: a stream that is already closed (or already finished) needs
119
- // no Close, so there is nothing to sign and no wallet is needed.
120
- const before = await relay(() => refunds.status());
121
- const finishedBefore = finishedReason(before);
122
- if (finishedBefore !== null || before.status === "closed") {
123
- closeSkipped = finishedBefore ?? "already-closed";
124
- finishedByRelay = finishedBefore;
125
- out.note(
126
- finishedBefore === null
127
- ? "stream is already closed — no Close to sign."
128
- : `stream is already closed and ${finishedBefore === "reclaimed" ? "reclaimed" : "has nothing left to reclaim"} — no Close to sign.`,
129
- );
130
- } else {
131
- const wallet = await resolveCliWallet(env, {
132
- purpose: "payer-close",
133
- note: out.note,
134
- rpcUrl: config.rpcUrl,
135
- });
136
- if (isPrivyWalletSource(wallet.source)) {
137
- throw configError(
138
- "metrik refund --close cannot sign with a Privy wallet",
139
- "the payer signs the opener's EIP-712 Close, which the Privy broker's typed-data policy does not allow. Use the key that paid.",
140
- );
141
- }
142
- // Buyer recovery: authorized on the mandate OWNER's signature alone,
143
- // like `metrik close` — never on caps or expiry, so nothing can strand
144
- // the payer's own money. The SDK then refuses a signer that is not
145
- // `payerOf(streamId)` before anything is signed for the relay.
146
- const mandate = await signRecoveryMandate(
147
- wallet.account,
148
- config.chainId,
149
- );
150
- const decision = await verifyMandateOwnerSignature(mandate);
151
- if (!decision.allowed) {
152
- throw new CliError(`mandate denied: ${decision.reason}`, {
153
- exitCode: 3,
154
- });
155
- }
156
- try {
157
- closeResult = await relay(() =>
158
- refunds.close({
159
- signer: wallet.account as unknown as X402PayerSigner,
111
+ let actions: RefundActionsResult;
112
+ try {
113
+ actions = await runRefundActions({
114
+ refunds,
115
+ streamId,
116
+ opener,
117
+ payer: stream.payer ?? null,
118
+ close: doClose,
119
+ reclaim: doReclaim,
120
+ closeSigner: () => payerCloseSigner(context, "metrik refund --close"),
121
+ sleep: deps.sleep,
122
+ note: out.note,
123
+ ...(context.progress === undefined
124
+ ? {}
125
+ : {
126
+ setActivity: (activity: string | null) =>
127
+ context.progress?.setActivity(activity),
160
128
  }),
161
- );
162
- out.note(
163
- `close relayed in ${closeResult.transaction}${closeResult.verified ? " (receipt verified on-chain)" : ""}`,
164
- );
165
- } catch (error) {
166
- // The status read above said "active", but the stream closed in the
167
- // meantime (it reached expiresAt, or another close landed): the
168
- // state the payer asked for. Anything else is a real failure.
169
- if (relayCode(error) !== "already-closed") throw error;
170
- closeSkipped = "already-closed";
171
- out.note("stream was already closed — no Close was needed.");
172
- }
173
- }
174
- }
175
-
176
- let reclaimResult: RefundRelayResult | null = null;
177
- let reclaimSkipped: SkipReason | null = null;
178
- if (doReclaim) {
179
- if (finishedByRelay !== null) {
180
- reclaimSkipped = finishedByRelay;
181
- } else {
182
- context.progress?.setActivity(
183
- `waiting on the refund relay's reclaim (it retries up to ${attempts} time${attempts === 1 ? "" : "s"} while the settlement window is open, at most ${maxWait}s per wait)`,
184
- );
185
- try {
186
- reclaimResult = await relay(() => refunds.reclaim());
187
- } catch (error) {
188
- const finished = finishedFromRelayCode(relayCode(error));
189
- if (finished === null) throw error;
190
- reclaimSkipped = finished;
191
- finishedByRelay = finished;
192
- } finally {
193
- context.progress?.setActivity(null);
194
- }
195
- }
129
+ reclaimActivity: reclaimActivity(attempts, maxWait),
130
+ });
131
+ } catch (error) {
132
+ throw explainRefundFailure(
133
+ error instanceof RefundActionsError ? error.cause : error,
134
+ );
196
135
  }
197
-
198
- // The summary describes the state AFTER what this run did: re-read until
199
- // the relay reflects the close/reclaim just mined (bounded), else derive
200
- // it from the verified receipts. Never the pre-action state.
201
- const status = await statusAfterActions(refunds, {
202
- closed: closeResult !== null,
136
+ const {
137
+ close: closeResult,
138
+ closeSkipped,
203
139
  reclaim: reclaimResult,
204
- payer: stream.payer ?? null,
205
- opener,
206
- streamId,
207
- sleep: deps.sleep,
208
- });
209
- // A reclaim THIS run performed is reported as a reclaim, not as "already
210
- // finished".
211
- const finished =
212
- reclaimResult === null
213
- ? (finishedReason(status) ?? finishedByRelay)
214
- : null;
140
+ reclaimSkipped,
141
+ status,
142
+ finished,
143
+ } = actions;
215
144
  out.result({
216
145
  streamId,
217
146
  opener,
@@ -264,87 +193,6 @@ export const refundCommand: CommandSpec = {
264
193
  },
265
194
  };
266
195
 
267
- /**
268
- * The relay's view of a stream AFTER this run's actions. Reads until it
269
- * reflects them (closed after a close, reclaimed after a reclaim), bounded;
270
- * if the relay still lags, the state those verified transactions imply is
271
- * reported instead, marked as derived. With no action, one plain read.
272
- */
273
- async function statusAfterActions(
274
- refunds: RefundClient,
275
- done: {
276
- readonly closed: boolean;
277
- readonly reclaim: RefundRelayResult | null;
278
- readonly payer: `0x${string}` | null;
279
- readonly opener: `0x${string}`;
280
- readonly streamId: `0x${string}`;
281
- readonly sleep: (ms: number) => Promise<void>;
282
- },
283
- ): Promise<RefundStatus> {
284
- const reclaimed = done.reclaim !== null;
285
- if (!done.closed && !reclaimed) return await relay(() => refunds.status());
286
- const reflects = (status: RefundStatus): boolean =>
287
- (!done.closed || status.status === "closed" || status.reclaimed) &&
288
- (!reclaimed || status.reclaimed);
289
- let last: RefundStatus | null = null;
290
- for (let attempt = 1; attempt <= POST_ACTION_READS; attempt++) {
291
- try {
292
- const status = await refunds.status();
293
- if (reflects(status)) return status;
294
- last = status;
295
- } catch {
296
- // A lagging relay (503 chain-lagging, a stale not-found, a blip): the
297
- // transactions already happened, so keep going and derive if need be.
298
- }
299
- if (attempt < POST_ACTION_READS) await done.sleep(POST_ACTION_INTERVAL_MS);
300
- }
301
- return derivedStatus(last, done);
302
- }
303
-
304
- /** The state this run's verified close/reclaim imply, when the relay lags. */
305
- function derivedStatus(
306
- last: RefundStatus | null,
307
- done: {
308
- readonly closed: boolean;
309
- readonly reclaim: RefundRelayResult | null;
310
- readonly payer: `0x${string}` | null;
311
- readonly opener: `0x${string}`;
312
- readonly streamId: `0x${string}`;
313
- },
314
- ): RefundStatus {
315
- const reclaimed = done.reclaim !== null;
316
- const note =
317
- "derived from this run's transaction receipts; the refund relay had not caught up with them yet";
318
- return {
319
- streamId: done.streamId,
320
- chainId: last?.chainId ?? 0,
321
- opener: last?.opener ?? done.opener,
322
- payer: last?.payer ?? done.payer ?? done.opener,
323
- status: "closed",
324
- reclaimed: reclaimed || (last?.reclaimed ?? false),
325
- chainTime: last?.chainTime ?? null,
326
- reclaimable: null,
327
- // Both legs this run verified are on-chain facts: the stream is closed.
328
- state: "closed",
329
- onChainStatus: "closed",
330
- maxRefund: last?.maxRefund ?? null,
331
- maxRefundMeaning: last?.maxRefundMeaning ?? null,
332
- reclaimableMeaning: last?.reclaimableMeaning ?? null,
333
- next: reclaimed
334
- ? {
335
- action: "none",
336
- message:
337
- "Reclaimed: the unearned remainder was returned to the payer. Nothing further to do.",
338
- }
339
- : {
340
- action: "reclaim",
341
- message:
342
- "Closed. Reclaim the unearned remainder once a checkpoint covering the close is posted (usually within a minute).",
343
- },
344
- raw: { derived: true, note },
345
- } satisfies RefundStatus;
346
- }
347
-
348
196
  /** `closed`, `expired` (Active on-chain but past expiresAt) or `active`. */
349
197
  export function stateOf(status: RefundStatus): "active" | "expired" | "closed" {
350
198
  if (status.status === "closed" || status.reclaimed) return "closed";
@@ -364,43 +212,6 @@ function describeReclaimed(result: RefundRelayResult): string {
364
212
  return `Reclaimed: ${result.amount === null ? "the unearned remainder" : formatUsdc(result.amount)} was returned to the payer${result.verified ? " (receipt verified on-chain)" : ""}. Nothing further to do for this stream.`;
365
213
  }
366
214
 
367
- /** Why a recovery is over. */
368
- export type FinishedReason = "reclaimed" | "nothing-to-reclaim";
369
- /** Why a requested `--close` / `--reclaim` sent nothing. */
370
- export type SkipReason = FinishedReason | "already-closed";
371
-
372
- /**
373
- * Whether the relay's status says nothing is left to recover. `reclaimed` is
374
- * the chain's one-reclaim-per-stream flag; `next: none` with `reclaimable: 0`
375
- * is the relay's "the verified delivery covers the whole deposit".
376
- */
377
- export function finishedReason(status: RefundStatus): FinishedReason | null {
378
- if (status.reclaimed) return "reclaimed";
379
- if (status.next.action === "none" && status.reclaimable === 0n) {
380
- return "nothing-to-reclaim";
381
- }
382
- return null;
383
- }
384
-
385
- /** The relay's 409 codes that mean "finished", not "failed". */
386
- function finishedFromRelayCode(code: string | null): FinishedReason | null {
387
- if (code === "already-reclaimed") return "reclaimed";
388
- if (code === "nothing-to-reclaim") return "nothing-to-reclaim";
389
- return null;
390
- }
391
-
392
- function relayCode(error: unknown): string | null {
393
- for (let current: unknown = error, depth = 0; depth < 4; depth++) {
394
- if (!(current instanceof Error)) return null;
395
- const code = (current as { code?: unknown }).code;
396
- if (current.name === "RefundRelayError" && typeof code === "string") {
397
- return code;
398
- }
399
- current = current.cause;
400
- }
401
- return null;
402
- }
403
-
404
215
  /** The `metrik refund` answer for an upfront-only payment (exit 0). */
405
216
  function reportNothingToRefund(
406
217
  context: CommandContext,
@@ -463,14 +274,6 @@ export function resolveRefundUrl(
463
274
  return url.toString();
464
275
  }
465
276
 
466
- async function relay<T>(task: () => Promise<T>): Promise<T> {
467
- try {
468
- return await task();
469
- } catch (error) {
470
- throw explainRefundFailure(error);
471
- }
472
- }
473
-
474
277
  export function explainRefundFailure(error: unknown): unknown {
475
278
  if (!(error instanceof Error)) return error;
476
279
  switch (error.name) {
@@ -613,4 +416,50 @@ function nextStep(
613
416
  }
614
417
  }
615
418
 
419
+ /**
420
+ * The payer's signer for an opener Close, authorized the way every buyer
421
+ * recovery is: on the mandate OWNER's signature alone (like `metrik close`) —
422
+ * never on caps or expiry, so nothing can strand the payer's own money. The
423
+ * SDK then refuses a signer that is not `payerOf(streamId)` before anything is
424
+ * signed for the relay.
425
+ */
426
+ export async function payerCloseSigner(
427
+ context: CommandContext,
428
+ commandLabel: string,
429
+ ): Promise<X402PayerSigner> {
430
+ const { config, env, out } = context;
431
+ const wallet = await resolveCliWallet(env, {
432
+ purpose: "payer-close",
433
+ note: out.note,
434
+ rpcUrl: config.rpcUrl,
435
+ });
436
+ if (isPrivyWalletSource(wallet.source)) {
437
+ throw configError(
438
+ `${commandLabel} cannot sign with a Privy wallet`,
439
+ "the payer signs the opener's EIP-712 Close, which the Privy broker's typed-data policy does not allow. Use the key that paid.",
440
+ );
441
+ }
442
+ return await authorizeCloseSigner(wallet.account, config.chainId);
443
+ }
444
+
445
+ /** {@link payerCloseSigner} for an account the command already holds. */
446
+ export async function authorizeCloseSigner(
447
+ account: Parameters<typeof signRecoveryMandate>[0],
448
+ chainId: number,
449
+ ): Promise<X402PayerSigner> {
450
+ const mandate = await signRecoveryMandate(account, chainId);
451
+ const decision = await verifyMandateOwnerSignature(mandate);
452
+ if (!decision.allowed) {
453
+ throw new CliError(`mandate denied: ${decision.reason}`, {
454
+ exitCode: 3,
455
+ });
456
+ }
457
+ return account as unknown as X402PayerSigner;
458
+ }
459
+
460
+ /** What the heartbeat says while the relay's reclaim waits for the checkpoint. */
461
+ export function reclaimActivity(attempts: number, maxWait: number): string {
462
+ return `waiting on the refund relay's reclaim (it retries up to ${attempts} time${attempts === 1 ? "" : "s"} while the settlement window is open, at most ${maxWait}s per wait)`;
463
+ }
464
+
616
465
  export type { RefundClient };
package/src/cli/deps.ts CHANGED
@@ -262,6 +262,12 @@ export interface CliX402 {
262
262
  readonly opener?: `0x${string}`;
263
263
  readonly maxAttempts?: number;
264
264
  readonly maxDelayMs?: number;
265
+ /**
266
+ * When the payment settled, unix ms (`pay --close-after`): a relay that
267
+ * has not seen the just-opened stream yet answers `404`, which is then
268
+ * waited out briefly instead of failing.
269
+ */
270
+ readonly settledAt?: number;
265
271
  },
266
272
  ) => Promise<RefundClient>;
267
273
  }
@@ -613,6 +619,9 @@ export const defaultCliDependencies: CliDependencies = {
613
619
  ...(target.maxDelayMs === undefined
614
620
  ? {}
615
621
  : { maxDelayMs: target.maxDelayMs }),
622
+ ...(target.settledAt === undefined
623
+ ? {}
624
+ : { settledAt: target.settledAt }),
616
625
  });
617
626
  },
618
627
  },
package/src/cli/errors.ts CHANGED
@@ -9,7 +9,14 @@ export type CliExitCode =
9
9
  /** Configuration refused: wrong chain, missing key, mainnet target. */
10
10
  | 3
11
11
  /** The operation itself failed (RPC, registry, escrow revert, HTTP). */
12
- | 4;
12
+ | 4
13
+ /**
14
+ * `metrik pay --close-after`: the payment and its response SUCCEEDED, but
15
+ * the buyer's close + reclaim of the stream did not finish. Nothing is lost
16
+ * (the unearned part stays in the escrow for the payer); the hint names the
17
+ * exact `metrik refund` command that finishes it.
18
+ */
19
+ | 5;
13
20
 
14
21
  export class CliError extends Error {
15
22
  readonly exitCode: CliExitCode;
package/src/mcp/server.ts CHANGED
@@ -389,6 +389,22 @@ export interface StoredStreamRecord {
389
389
  * seller at settlement). Informational: `budgetUsdc` always counts it.
390
390
  */
391
391
  readonly nonRefundableUsdc?: bigint;
392
+ /**
393
+ * x402 streams only: base units this server's own reclaim returned to the
394
+ * payer (`x402_pay` with `closeAfter`), as proven by the mined receipt's
395
+ * `ReclaimForwarded`. Subtracted from `budgetUsdc` when counting spend, so
396
+ * only what the payment finally kept counts against the mandate. Never set
397
+ * from a relay's unverified word.
398
+ */
399
+ readonly reclaimedUsdc?: bigint;
400
+ }
401
+
402
+ /** What a record counts against the spend mandate: the budget less a verified reclaim. */
403
+ export function countedSpendUsdc(record: StoredStreamRecord): bigint {
404
+ const reclaimed = record.reclaimedUsdc ?? 0n;
405
+ return reclaimed > 0n && reclaimed <= record.budgetUsdc
406
+ ? record.budgetUsdc - reclaimed
407
+ : record.budgetUsdc;
392
408
  }
393
409
 
394
410
  export interface StreamRegistry {
@@ -470,6 +486,7 @@ export class FileStreamRegistry implements StreamRegistry {
470
486
  x402Reservation: z.boolean().optional(),
471
487
  x402Charge: z.literal("upfront-only").optional(),
472
488
  nonRefundableUsdc: decimalStringSchema.optional(),
489
+ reclaimedUsdc: decimalStringSchema.optional(),
473
490
  }),
474
491
  )
475
492
  .parse(JSON.parse(contents));
@@ -500,6 +517,9 @@ export class FileStreamRegistry implements StreamRegistry {
500
517
  ...(record.nonRefundableUsdc === undefined
501
518
  ? {}
502
519
  : { nonRefundableUsdc: BigInt(record.nonRefundableUsdc) }),
520
+ ...(record.reclaimedUsdc === undefined
521
+ ? {}
522
+ : { reclaimedUsdc: BigInt(record.reclaimedUsdc) }),
503
523
  },
504
524
  ]),
505
525
  );
@@ -519,6 +539,9 @@ export class FileStreamRegistry implements StreamRegistry {
519
539
  ...(record.nonRefundableUsdc === undefined
520
540
  ? {}
521
541
  : { nonRefundableUsdc: record.nonRefundableUsdc.toString() }),
542
+ ...(record.reclaimedUsdc === undefined
543
+ ? {}
544
+ : { reclaimedUsdc: record.reclaimedUsdc.toString() }),
522
545
  })),
523
546
  null,
524
547
  2,
@@ -711,7 +734,7 @@ export function createVerifiedStreamMcpServer(
711
734
  const records = await registry.list();
712
735
  return records
713
736
  .filter((record) => record.buyer.toLowerCase() === owner.toLowerCase())
714
- .reduce((sum, record) => sum + record.budgetUsdc, 0n);
737
+ .reduce((sum, record) => sum + countedSpendUsdc(record), 0n);
715
738
  };
716
739
  if (options.mandateStateResolver?.getRevokedMandateIds === undefined) {
717
740
  warnOnce(
@@ -1186,6 +1209,9 @@ export function createVerifiedStreamMcpServer(
1186
1209
  ...(record.nonRefundableUsdc === undefined
1187
1210
  ? {}
1188
1211
  : { nonRefundableUsdc: record.nonRefundableUsdc.toString() }),
1212
+ ...(record.reclaimedUsdc === undefined
1213
+ ? {}
1214
+ : { reclaimedUsdc: record.reclaimedUsdc.toString() }),
1189
1215
  status,
1190
1216
  };
1191
1217
  }),
@@ -1607,6 +1633,9 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1607
1633
  ? {}
1608
1634
  : { opener: target.opener }),
1609
1635
  ...(target.fetch === undefined ? {} : { fetch: target.fetch }),
1636
+ ...(target.settledAt === undefined
1637
+ ? {}
1638
+ : { settledAt: target.settledAt }),
1610
1639
  // Every relay "success" is checked against the receipt.
1611
1640
  verifyWith: readOnlyMetrikClient({
1612
1641
  escrowAddress: parsed.escrow,