@routedock/routedock 0.3.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.
Files changed (58) hide show
  1. package/README.md +113 -12
  2. package/dist/{MppSessionHandler-1EL8QBAn.d.cts → MppSessionHandler-3QfCHI5x.d.ts} +40 -2
  3. package/dist/{MppSessionHandler-Bq4S-JTE.d.ts → MppSessionHandler-xu4Kxu6N.d.cts} +40 -2
  4. package/dist/{ProviderRegistry-CA_TdREG.d.ts → ProviderRegistry-D9KqyuH-.d.cts} +16 -36
  5. package/dist/{ProviderRegistry-D-0rqK0N.d.cts → ProviderRegistry-DVi2uzgk.d.ts} +16 -36
  6. package/dist/{RouteDockClient-go9aopfF.d.cts → RouteDockClient-6rmgkbYb.d.cts} +124 -142
  7. package/dist/{RouteDockClient-Bh86bVly.d.ts → RouteDockClient-CnAkxwrW.d.ts} +124 -142
  8. package/dist/{SessionReconciler-Bw-PoBVl.d.ts → SessionReconciler-Cce9MXO1.d.ts} +10 -3
  9. package/dist/{SessionReconciler-Cz2nuOlJ.d.cts → SessionReconciler-Dpe9qXdD.d.cts} +10 -3
  10. package/dist/{SessionStore-BaYiegP0.d.ts → SessionStore-DkLBxFQu.d.cts} +28 -3
  11. package/dist/{SessionStore-B4-Hi4H8.d.cts → SessionStore-_ZkoBzFY.d.ts} +28 -3
  12. package/dist/SpendStore-BUclV02i.d.ts +73 -0
  13. package/dist/SpendStore-s_dfcemj.d.cts +73 -0
  14. package/dist/client.cjs +767 -487
  15. package/dist/client.d.cts +80 -10
  16. package/dist/client.d.ts +80 -10
  17. package/dist/client.js +733 -468
  18. package/dist/index.cjs +1295 -590
  19. package/dist/index.d.cts +13 -10
  20. package/dist/index.d.ts +13 -10
  21. package/dist/index.js +1247 -559
  22. package/dist/logger-DVa7VT4c.d.cts +41 -0
  23. package/dist/logger-DVa7VT4c.d.ts +41 -0
  24. package/dist/network.cjs +57 -0
  25. package/dist/network.d.cts +72 -0
  26. package/dist/network.d.ts +72 -0
  27. package/dist/network.js +49 -0
  28. package/dist/node.cjs +166 -0
  29. package/dist/node.d.cts +77 -0
  30. package/dist/node.d.ts +77 -0
  31. package/dist/node.js +159 -0
  32. package/dist/provider/fastify.cjs +847 -62
  33. package/dist/provider/fastify.d.cts +10 -3
  34. package/dist/provider/fastify.d.ts +10 -3
  35. package/dist/provider/fastify.js +848 -63
  36. package/dist/provider/hono.cjs +936 -60
  37. package/dist/provider/hono.d.cts +23 -8
  38. package/dist/provider/hono.d.ts +23 -8
  39. package/dist/provider/hono.js +937 -61
  40. package/dist/provider.cjs +894 -70
  41. package/dist/provider.d.cts +9 -8
  42. package/dist/provider.d.ts +9 -8
  43. package/dist/provider.js +895 -72
  44. package/dist/react.cjs +736 -436
  45. package/dist/react.d.cts +7 -5
  46. package/dist/react.d.ts +7 -5
  47. package/dist/react.js +725 -425
  48. package/dist/{routedockMiddleware-57gUaEMf.d.ts → routedockMiddleware-Cq9HOJEa.d.ts} +9 -2
  49. package/dist/{routedockMiddleware-DlyLrxp7.d.cts → routedockMiddleware-CxsWkSKa.d.cts} +9 -2
  50. package/dist/schema.cjs +7 -0
  51. package/dist/schema.js +7 -0
  52. package/dist/{sign-AKH67sft.d.cts → sign-Bq08SeFq.d.ts} +2 -2
  53. package/dist/{sign-BEq-OsSq.d.ts → sign-DqGE0Plv.d.cts} +2 -2
  54. package/dist/testing.d.cts +4 -3
  55. package/dist/testing.d.ts +4 -3
  56. package/dist/{logger-D8_Q2ytp.d.cts → types-DXaS5Wip.d.cts} +85 -47
  57. package/dist/{logger-D8_Q2ytp.d.ts → types-DXaS5Wip.d.ts} +85 -47
  58. package/package.json +50 -4
@@ -1,15 +1,22 @@
1
1
  import { Keypair } from '@stellar/stellar-sdk';
2
- import { g as RouteDockLogger, P as PaymentMode, h as RouteDockManifest, d as PreflightResult, c as PaymentResult, b as EstimateCostResult, n as SessionOptions, m as SessionHandle } from './logger-D8_Q2ytp.js';
2
+ import { P as PaymentMode, c as PaymentRequest, R as RouteDockManifest, d as PaymentResult, U as UrlPreflightResult, g as PreflightResult, b as EstimateCostResult, n as SessionOptions, m as SessionHandle } from './types-DXaS5Wip.js';
3
+ import { b as RouteDockLogger } from './logger-DVa7VT4c.js';
3
4
  import { ClientStellarSigner } from '@x402/stellar';
4
- import { SignAuthEntry } from '@stellar/stellar-sdk/contract';
5
- import { usdcToStroops } from '@routedock/nulth-sdk';
5
+ import { PaymentRequirements } from '@x402/core/types';
6
+ import { NulthSignerConfig, NulthStellarSigner, usdcToStroops } from '@routedock/nulth-sdk';
7
+ import { S as SpendStore } from './SpendStore-BUclV02i.js';
6
8
 
7
9
  interface RetryPolicy {
8
10
  /** Total attempts including the first call. Default: 4 (3 retries). */
9
11
  maxAttempts?: number;
10
12
  /** Base delay for exponential backoff in ms. Default: 250. */
11
13
  baseDelayMs?: number;
12
- /** Maximum jittered backoff delay in ms. Default: 30000. */
14
+ /**
15
+ * Maximum delay of any single retry sleep, in ms. Default: 30000. Caps both
16
+ * the jittered backoff and a server `Retry-After` hint, so a provider that
17
+ * asks for a longer wait is waited on for at most this long. The error's
18
+ * own `retryAfterMs` still carries the raw server value.
19
+ */
13
20
  maxDelayMs?: number;
14
21
  /** Called before each retry with the attempt number, error, and upcoming delay. */
15
22
  /**
@@ -32,27 +39,25 @@ declare const DEFAULT_RETRY_POLICY: Required<Omit<RetryPolicy, 'onRetry' | 'logg
32
39
  declare function backoffDelayMs(attempt: number, baseDelayMs: number, maxDelayMs?: number): number;
33
40
  /**
34
41
  * Execute `fn` with exponential backoff on retryable RouteDockErrors.
35
- * Honors `retryAfterMs` from facilitator 429/503 responses when present.
42
+ * Honors `retryAfterMs` from facilitator 429/503 responses when present, but
43
+ * never sleeps longer than `maxDelayMs`: a larger server hint is clamped.
36
44
  */
37
45
  declare function withRetry<T>(fn: () => Promise<T>, policy?: RetryPolicy): Promise<T>;
38
46
 
39
47
  /**
40
48
  * Enforce the per-endpoint `deprecated` and `sunset_at` metadata for the URL
41
- * being paid. `pay()` never learns the HTTP method, so a path shared by more
42
- * than one descriptor (e.g. GET and POST on `/infer`) is refused only when
43
- * every matching descriptor has sunset. Paths the manifest does not list keep
44
- * today's behavior. Query strings and hashes are ignored because only
45
- * `URL.pathname` is compared.
46
- */
47
- declare function assertEndpointActive(manifest: RouteDockManifest, url: string, logger?: RouteDockLogger): void;
48
- /**
49
- * Validate semantic constraints for manifest fields beyond JSON schema syntax.
50
- * Enforces:
51
- * - All keys in `latency_hints` must be a subset of declared `regions`.
52
- * - If `assets` is defined, it must be a non-empty array whose first entry
53
- * (`assets[0]`) matches the root-level `asset` and `asset_contract`.
49
+ * being paid.
50
+ *
51
+ * When the caller's HTTP method is known (`method`), only descriptors for that
52
+ * method can speak for the request, so a sunset GET `/infer` no longer blocks
53
+ * an active POST `/infer` (and vice versa). Without a method — `estimateCost()`
54
+ * before a request is chosen, or `openSession()` — a shared path is refused
55
+ * only when every matching descriptor has sunset. Paths the manifest does not
56
+ * list, and (method-known) method/path pairs it does not declare, keep today's
57
+ * behavior. Query strings and hashes are ignored because only `URL.pathname`
58
+ * is compared.
54
59
  */
55
- declare function assertManifestValid(manifest: RouteDockManifest, baseUrl?: string): void;
60
+ declare function assertEndpointActive(manifest: RouteDockManifest, url: string, logger?: RouteDockLogger, method?: string): void;
56
61
  /**
57
62
  * Override the manifest cache's max size (default 512). Affects the shared,
58
63
  * process-wide cache used by all RouteDockClient instances.
@@ -94,6 +99,24 @@ interface ModeSelectOptions {
94
99
  forceMode?: PaymentMode;
95
100
  /** Structured logger for mode selection events. Defaults to no-op. */
96
101
  logger?: RouteDockLogger;
102
+ /**
103
+ * HTTP request replayed to the provider. Use it for POST endpoints:
104
+ *
105
+ * ```ts
106
+ * await client.pay(url, {
107
+ * request: {
108
+ * method: 'POST',
109
+ * headers: { 'content-type': 'application/json' },
110
+ * body: JSON.stringify(input),
111
+ * },
112
+ * })
113
+ * ```
114
+ *
115
+ * The method/headers/body are sent on the unpaid probe, the paid request and
116
+ * every retry; SDK payment headers always override caller headers. The method
117
+ * also scopes the per-endpoint sunset check.
118
+ */
119
+ request?: PaymentRequest;
97
120
  }
98
121
  /**
99
122
  * Fetch, validate, and cache a RouteDock manifest from `baseUrl`.
@@ -104,6 +127,12 @@ interface ModeSelectOptions {
104
127
  * Callers that obtained a payee from a registry/pinned allowlist MUST pass it;
105
128
  * without it the self-signed signature alone proves nothing about who served
106
129
  * the manifest.
130
+ *
131
+ * Concurrent calls that miss the cache for the same `baseUrl` share one
132
+ * in-flight HTTP request instead of fanning out (the cache is process-wide,
133
+ * so this also holds across `RouteDockClient` instances). The first caller's
134
+ * `retryPolicy` and `manifestTimeoutMs` govern the shared request; the
135
+ * per-caller checks above still run for every caller after it settles.
107
136
  */
108
137
  declare function fetchManifest(baseUrl: string, retryPolicy?: RetryPolicy, manifestTimeoutMs?: number, expectedPayee?: string): Promise<RouteDockManifest>;
109
138
  /**
@@ -123,79 +152,45 @@ declare function selectMode(manifest: RouteDockManifest, options?: ModeSelectOpt
123
152
  */
124
153
  declare function rankProvidersByLatency(manifests: RouteDockManifest[], targetRegion: string): RouteDockManifest[];
125
154
 
126
- interface NulthPolicyState {
127
- dailyCapStroops: bigint;
128
- allowlistCommitment: string;
129
- allowedPayees: readonly string[];
130
- dailySpendStroops: bigint;
131
- dayBucket: number;
132
- witnessSecret: string;
133
- expiryLedger?: number;
134
- }
135
- interface PaymentAuthContext {
136
- authEntry: string;
137
- payee: string;
138
- amountStroops: bigint;
139
- assetContract: string;
140
- ledgerSequence: number;
155
+ type Network = 'testnet' | 'mainnet';
156
+ /** What an {@link X402PreSignHook} sees: the payment about to be signed. */
157
+ interface X402PreSignContext {
158
+ /** The resource being paid for. */
159
+ readonly url: string;
160
+ /** The signed manifest the challenge was already bound to. */
161
+ readonly manifest: RouteDockManifest;
162
+ /** The challenge entries that passed manifest binding — the signer picks from these. */
163
+ readonly accepts: readonly PaymentRequirements[];
141
164
  }
142
- interface NulthProof {
143
- version: 1;
144
- proof: string;
145
- publicInputs: {
146
- authDigest: string;
147
- allowlistCommitment: string;
148
- capCommitment: string;
149
- payeeHash: string;
150
- amountStroops: string;
151
- dayBucket: string;
152
- };
153
- }
154
- interface NulthAuthSignature {
155
- nulth: 'zk-v1';
156
- proof: NulthProof;
157
- }
158
- interface NulthClientConfig {
159
- nulthAccount: string;
160
- network: 'testnet' | 'mainnet';
161
- prover?: 'mock';
162
- policy: NulthPolicyState;
163
- verifierContract?: string;
164
- /** Log sink for the mock-prover warning. Defaults to a console-backed logger. */
165
- logger?: RouteDockLogger;
166
- }
167
- declare class NulthPolicyError extends Error {
168
- readonly code: 'payee_not_allowed' | 'daily_cap_exceeded' | 'session_expired' | 'auth_entry_mismatch';
169
- constructor(code: NulthPolicyError['code'], message: string);
170
- }
171
- interface NulthStellarSigner {
172
- address: string;
173
- signAuthEntry: SignAuthEntry;
174
- }
175
- interface NulthSignerConfig extends NulthClientConfig {
176
- paymentContext?: Omit<PaymentAuthContext, 'authEntry'>;
165
+ /** Verdict returned by an {@link X402PreSignHook}. */
166
+ type X402PreSignDecision = {
167
+ approve: true;
168
+ } | {
169
+ approve: false;
170
+ reason?: string;
171
+ };
172
+ /**
173
+ * Optional guard run once per `pay()`, after the 402 challenge has been bound to
174
+ * the manifest and immediately before anything is signed. Use it to plug in an
175
+ * external or custom audit. Rejecting — or throwing — aborts before signing
176
+ * (fail closed) with `RouteDockPolicyRejectError`.
177
+ */
178
+ type X402PreSignHook = (ctx: X402PreSignContext) => X402PreSignDecision | Promise<X402PreSignDecision>;
179
+ declare class X402Client {
180
+ private readonly network;
181
+ private readonly retryPolicy?;
182
+ private readonly preSignHook?;
183
+ private readonly httpClient;
184
+ private readonly signer;
185
+ private readonly logger;
186
+ constructor(secretKeyOrSigner: string | ClientStellarSigner, network: Network, retryPolicy?: RetryPolicy | undefined, preSignHook?: X402PreSignHook | undefined, logger?: RouteDockLogger);
187
+ /** Replace signer (e.g. swap to Nulth ZK account payer before pay) */
188
+ withSigner(signer: ClientStellarSigner): X402Client;
189
+ pay(url: string, manifest: RouteDockManifest, request?: PaymentRequest): Promise<PaymentResult>;
177
190
  }
191
+
192
+ /** Preserve RouteDock's logger default and configuration error contract. */
178
193
  declare function createNulthSigner(config: NulthSignerConfig): NulthStellarSigner;
179
- declare function setNulthPaymentContext(config: NulthSignerConfig, context: Omit<PaymentAuthContext, 'authEntry'>): void;
180
- declare function paymentContextFromManifest(manifest: {
181
- payee: string;
182
- asset_contract: string;
183
- pricing: {
184
- x402?: {
185
- amount: string;
186
- };
187
- 'mpp-charge'?: {
188
- amount: string;
189
- };
190
- };
191
- }, mode: 'x402' | 'mpp-charge', ledgerSequence: number): Omit<PaymentAuthContext, 'authEntry'>;
192
- declare function createPolicyState(input: {
193
- dailyCapUsdc: string;
194
- allowedPayees: readonly string[];
195
- witnessSecret: string;
196
- expiryLedger?: number;
197
- ledgerSequence?: number;
198
- }): NulthPolicyState;
199
194
  interface NulthVaultConfig {
200
195
  mode: 'nulth';
201
196
  nulthAccount: string;
@@ -213,56 +208,21 @@ declare function prepareNulthSigner(vault: NulthVaultConfig, manifest: RouteDock
213
208
  config: NulthSignerConfig;
214
209
  }>;
215
210
 
216
- declare function decodeAuthSignature(encoded: string): NulthAuthSignature;
217
-
218
- /**
219
- * Daily spend accumulator, keyed by an ISO date (YYYY-MM-DD).
220
- *
221
- * Totals are decimal strings of integer microUSDC (1 USDC = 10^7 units) so
222
- * the record stays JSON-serializable with no floating point precision loss.
223
- */
224
- interface DailySpend {
225
- date: string;
226
- /** Global accumulated spend for the day, in microUSDC (e.g. "5000000") */
227
- totalMicros: string;
228
- /** Per-endpoint accumulated spend for the day, keyed by origin URL, in microUSDC */
229
- endpoints: Record<string, string>;
230
- }
231
- /**
232
- * Durable backing store for the client's local daily spend cap.
233
- *
234
- * The spend cap is a financial safety control: if its accumulator lives only in
235
- * heap memory, any crash, OOM kill, or container restart silently resets it to
236
- * zero and the cap can be bypassed. Inject a persistent implementation
237
- * (Redis, SQL, Supabase, a file, …) so the counter survives restarts.
238
- */
239
- interface SpendStore {
240
- /** Return the persisted accumulator, or null if none has been written yet. */
241
- read(): Promise<DailySpend | null>;
242
- /** Persist the accumulator. */
243
- write(state: DailySpend): Promise<void>;
244
- }
245
- interface InMemorySpendStoreOptions {
246
- /** Log a startup warning about non-durability. Defaults to true. */
247
- warn?: boolean;
248
- /** Log sink for the non-durability warning. Defaults to a console-backed logger. */
249
- logger?: RouteDockLogger;
250
- }
251
- /**
252
- * Default, non-durable {@link SpendStore}. Holds the accumulator on the heap, so
253
- * it resets on every process restart. Logs a startup warning unless silenced.
254
- * Use this only for development; inject a persistent store in production.
255
- */
256
- declare class InMemorySpendStore implements SpendStore {
257
- private state;
258
- constructor(options?: InMemorySpendStoreOptions);
259
- read(): Promise<DailySpend | null>;
260
- write(state: DailySpend): Promise<void>;
261
- }
262
-
263
211
  interface SpendCap {
264
- /** Maximum total USDC spend per day (decimal string, e.g. "1.00") */
212
+ /**
213
+ * Maximum total spend per day (decimal string, e.g. "1.00"), denominated in
214
+ * `asset`.
215
+ *
216
+ * The accumulator is a single USDC number, so the cap only ever counts USDC.
217
+ * When a cap is configured, a payment whose selected asset (see
218
+ * `selectAsset`) is anything other than `asset` is rejected before any
219
+ * trustline check, network call or on-chain deposit with
220
+ * `RouteDockPolicyRejectError('local_cap_asset_mismatch')`. Amounts in other
221
+ * assets are never added to the USDC total, since 5 XLM is not 5 USDC.
222
+ * Manifests that only price in USDC behave exactly as before.
223
+ */
265
224
  daily: string;
225
+ /** The only asset this cap can account for. */
266
226
  asset: 'USDC';
267
227
  /**
268
228
  * Optional per-endpoint daily spend caps, keyed by endpoint origin URL
@@ -298,6 +258,11 @@ interface RouteDockClientConfig {
298
258
  commitmentSecret?: string | undefined;
299
259
  /** Retry policy for transient failures (network, facilitator 5xx). */
300
260
  retryPolicy?: RetryPolicy;
261
+ /**
262
+ * Optional guard run before an x402 payment is signed. Rejecting or throwing
263
+ * aborts the payment with `RouteDockPolicyRejectError`. x402 mode only.
264
+ */
265
+ preSignHook?: X402PreSignHook;
301
266
  /**
302
267
  * Durable backing store for the daily spend cap accumulator. Defaults to a
303
268
  * non-durable in-memory store that resets on restart (with a startup warning).
@@ -374,12 +339,24 @@ declare class RouteDockClient {
374
339
  */
375
340
  private _withSpendMutex;
376
341
  /**
377
- * Check that the payer has a trustline for the payment asset and surface
378
- * manifest metadata (regions, latency hints, capabilities). Safe to
379
- * call before committing to a payment — for approval gates and manual
380
- * trustline remediation.
342
+ * Check a provider before committing to a payment. Neither form submits a
343
+ * transaction or moves money.
344
+ *
345
+ * - `preflight(url)` fetches and validates the provider's manifest and
346
+ * returns the supported modes, the estimated cost per mode, and
347
+ * deprecation/sunset flags. It does not check the payer's trustline.
348
+ * Throws the same manifest errors as `pay()` / `estimateCost()` when the
349
+ * manifest is unreachable, invalid, unsupported by this SDK version, or
350
+ * sunset (including a sunset endpoint matching the URL path).
351
+ * - `preflight(manifest, mode?, endpoint?)` checks that the payer has a
352
+ * trustline for the payment asset and surfaces manifest metadata
353
+ * (regions, latency hints, capabilities). For approval gates and manual
354
+ * trustline remediation.
381
355
  */
356
+ preflight(url: string): Promise<UrlPreflightResult>;
382
357
  preflight(manifest: RouteDockManifest, mode?: PaymentMode, endpoint?: string): Promise<PreflightResult>;
358
+ /** Manifest-only preflight for a URL — no trustline check, no payment. */
359
+ private _preflightUrl;
383
360
  /**
384
361
  * Verify via Horizon that the payer account has a trustline for the
385
362
  * payment asset declared in the manifest. Throws RouteDockTrustlineError
@@ -415,6 +392,11 @@ declare class RouteDockClient {
415
392
  * disable via `options.maxDurationMs`.
416
393
  */
417
394
  openSession(url: string, options?: SessionOptions): Promise<SessionHandle>;
395
+ /**
396
+ * The local cap accumulates one USDC total. Refuse to count any other asset
397
+ * against it (or let it through uncounted) when a cap is configured.
398
+ */
399
+ private _assertCapAsset;
418
400
  /**
419
401
  * Record spend for an MPP session voucher. Called before each voucher
420
402
  * fetch in stream(). Enforces the same local daily and per-endpoint caps
@@ -466,4 +448,4 @@ declare class RouteDockClient {
466
448
  private _rollbackSpend;
467
449
  }
468
450
 
469
- export { selectMode as A, setNulthPaymentContext as B, usdcToMicros as C, DEFAULT_RETRY_POLICY as D, withRetry as E, InMemorySpendStore as I, type ModeSelectOptions as M, type NulthAuthSignature as N, type PaymentAuthContext as P, type RetryPolicy as R, type SpendCap as S, type VaultConfig as V, type DailySpend as a, type InMemorySpendStoreOptions as b, type NulthClientConfig as c, NulthPolicyError as d, type NulthPolicyState as e, type NulthProof as f, type NulthSignerConfig as g, type NulthStellarSigner as h, type NulthVaultConfig as i, RouteDockClient as j, type RouteDockClientConfig as k, type SpendStore as l, assertEndpointActive as m, assertManifestValid as n, assertNulthVaultManifest as o, backoffDelayMs as p, configureManifestCache as q, createNulthSigner as r, createPolicyState as s, decodeAuthSignature as t, fetchLedgerSequence as u, fetchManifest as v, invalidateManifest as w, paymentContextFromManifest as x, prepareNulthSigner as y, rankProvidersByLatency as z };
451
+ export { DEFAULT_RETRY_POLICY as D, type ModeSelectOptions as M, type NulthVaultConfig as N, type RetryPolicy as R, type SpendCap as S, type VaultConfig as V, X402Client as X, RouteDockClient as a, type RouteDockClientConfig as b, type X402PreSignContext as c, type X402PreSignDecision as d, type X402PreSignHook as e, assertEndpointActive as f, assertNulthVaultManifest as g, backoffDelayMs as h, configureManifestCache as i, createNulthSigner as j, fetchLedgerSequence as k, fetchManifest as l, invalidateManifest as m, prepareNulthSigner as p, rankProvidersByLatency as r, selectMode as s, usdcToMicros as u, withRetry as w };
@@ -1,7 +1,8 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { S as SeenTxStore, a as SettlementRecord } from './MppSessionHandler-Bq4S-JTE.js';
3
- import { h as RouteDockManifest, g as RouteDockLogger } from './logger-D8_Q2ytp.js';
2
+ import { S as SeenTxStore, a as SettlementRecord } from './MppSessionHandler-3QfCHI5x.js';
3
+ import { R as RouteDockManifest } from './types-DXaS5Wip.js';
4
4
  import { Keypair } from '@stellar/stellar-sdk';
5
+ import { b as RouteDockLogger } from './logger-DVa7VT4c.js';
5
6
 
6
7
  /**
7
8
  * Settlement idempotency backed by Postgres via Supabase.
@@ -95,7 +96,7 @@ type Network = 'testnet' | 'mainnet';
95
96
  type ChannelCloseFn = (args: {
96
97
  channel: string;
97
98
  amount: bigint;
98
- signature: Buffer;
99
+ signature: Uint8Array;
99
100
  feePayer: {
100
101
  envelopeSigner: Keypair;
101
102
  };
@@ -108,12 +109,18 @@ interface SessionReconcilerOptions {
108
109
  onRecovered?: (channelId: string, txHash: string, totalPaid: string) => Promise<void>;
109
110
  /** Injectable for testing. Defaults to @stellar/mpp/channel/server close() */
110
111
  channelClose?: ChannelCloseFn;
112
+ /** Failed close attempts before a row is moved to the terminal `failed` status. Default 5. */
113
+ maxReconcileAttempts?: number;
111
114
  }
112
115
  interface ReconciliationStats {
113
116
  orphanedCount: number;
114
117
  recoveredCount: number;
115
118
  skippedCount: number;
116
119
  failedCount: number;
120
+ /** Rows whose channel was already closed on-chain; marked `closed` without a new close. */
121
+ alreadyClosedCount: number;
122
+ /** Rows moved to the terminal `failed` status this run (attempt limit reached). */
123
+ terminalCount: number;
117
124
  errors: Array<{
118
125
  channelId: string;
119
126
  reason: string;
@@ -1,7 +1,8 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { S as SeenTxStore, a as SettlementRecord } from './MppSessionHandler-1EL8QBAn.cjs';
3
- import { h as RouteDockManifest, g as RouteDockLogger } from './logger-D8_Q2ytp.cjs';
2
+ import { S as SeenTxStore, a as SettlementRecord } from './MppSessionHandler-xu4Kxu6N.cjs';
3
+ import { R as RouteDockManifest } from './types-DXaS5Wip.cjs';
4
4
  import { Keypair } from '@stellar/stellar-sdk';
5
+ import { b as RouteDockLogger } from './logger-DVa7VT4c.cjs';
5
6
 
6
7
  /**
7
8
  * Settlement idempotency backed by Postgres via Supabase.
@@ -95,7 +96,7 @@ type Network = 'testnet' | 'mainnet';
95
96
  type ChannelCloseFn = (args: {
96
97
  channel: string;
97
98
  amount: bigint;
98
- signature: Buffer;
99
+ signature: Uint8Array;
99
100
  feePayer: {
100
101
  envelopeSigner: Keypair;
101
102
  };
@@ -108,12 +109,18 @@ interface SessionReconcilerOptions {
108
109
  onRecovered?: (channelId: string, txHash: string, totalPaid: string) => Promise<void>;
109
110
  /** Injectable for testing. Defaults to @stellar/mpp/channel/server close() */
110
111
  channelClose?: ChannelCloseFn;
112
+ /** Failed close attempts before a row is moved to the terminal `failed` status. Default 5. */
113
+ maxReconcileAttempts?: number;
111
114
  }
112
115
  interface ReconciliationStats {
113
116
  orphanedCount: number;
114
117
  recoveredCount: number;
115
118
  skippedCount: number;
116
119
  failedCount: number;
120
+ /** Rows whose channel was already closed on-chain; marked `closed` without a new close. */
121
+ alreadyClosedCount: number;
122
+ /** Rows moved to the terminal `failed` status this run (attempt limit reached). */
123
+ terminalCount: number;
117
124
  errors: Array<{
118
125
  channelId: string;
119
126
  reason: string;
@@ -1,8 +1,8 @@
1
- import { h as RouteDockManifest, P as PaymentMode, A as AssetConfig, p as SessionState } from './logger-D8_Q2ytp.js';
1
+ import { R as RouteDockManifest, P as PaymentMode, A as AssetConfig, p as SessionState } from './types-DXaS5Wip.cjs';
2
2
  import { SupabaseClient } from '@supabase/supabase-js';
3
3
 
4
4
  /** Stable error codes for programmatic handling. */
5
- type RouteDockErrorCode = 'MANIFEST' | 'MANIFEST_TIMEOUT' | 'MANIFEST_SUNSET' | 'NO_SUPPORTED_MODE' | 'FACILITATOR' | 'NETWORK' | 'SIGNATURE' | 'VOUCHER_MONOTONICITY' | 'POLICY_REJECT' | 'CHANNEL_STATE' | 'DISPUTE' | 'REFUND_WINDOW' | 'CLIENT_VERSION_TOO_OLD' | 'TRUSTLINE';
5
+ type RouteDockErrorCode = 'MANIFEST' | 'MANIFEST_TIMEOUT' | 'MANIFEST_SUNSET' | 'NO_SUPPORTED_MODE' | 'FACILITATOR' | 'NETWORK' | 'SIGNATURE' | 'VOUCHER_MONOTONICITY' | 'POLICY_REJECT' | 'PAYMENT_REJECTED' | 'CHANNEL_STATE' | 'DISPUTE' | 'REFUND_WINDOW' | 'CLIENT_VERSION_TOO_OLD' | 'TRUSTLINE';
6
6
  interface RouteDockErrorOptions {
7
7
  cause?: unknown;
8
8
  /** Milliseconds to wait before retry (e.g. from Retry-After header). */
@@ -68,6 +68,24 @@ declare class RouteDockPolicyRejectError extends RouteDockError {
68
68
  readonly reason: string;
69
69
  constructor(reason: string, options?: RouteDockErrorOptions);
70
70
  }
71
+ /**
72
+ * The provider refused a signed, paid request (HTTP 4xx such as 401/402).
73
+ * Not a manifest problem and not a local policy rejection: `status` is the HTTP
74
+ * status and `reason` is the provider's own explanation when it sent one
75
+ * (e.g. the facilitator's `invalidReason`). Not retryable — the same signed
76
+ * payment would be refused again.
77
+ */
78
+ declare class RouteDockPaymentRejectedError extends RouteDockError {
79
+ readonly status: number;
80
+ /** The provider's `reason` field, if its response body was JSON and had one. */
81
+ readonly reason?: string;
82
+ /** The provider's `error` field (short summary), if present. */
83
+ readonly providerError?: string;
84
+ constructor(message: string, status: number, details?: {
85
+ reason?: string;
86
+ providerError?: string;
87
+ }, options?: RouteDockErrorOptions);
88
+ }
71
89
  /** Channel lifecycle failure (open, simulate, close, missing settlement fields). */
72
90
  declare class RouteDockChannelStateError extends RouteDockError {
73
91
  constructor(message: string, options?: RouteDockErrorOptions);
@@ -88,6 +106,13 @@ declare const RouteDockSessionError: typeof RouteDockVoucherMonotonicityError;
88
106
  declare function parseRetryAfterMs(response: Response): number | undefined;
89
107
  /** Map an HTTP status to a typed, retryable-aware RouteDock error. */
90
108
  declare function httpStatusToError(message: string, status: number, response?: Response): RouteDockError;
109
+ /**
110
+ * Build a {@link RouteDockPaymentRejectedError} from a rejected paid response.
111
+ * Reads the JSON body best-effort (a missing, non-JSON or oversized body just
112
+ * means no `reason`; the provider is untrusted, so the read is bounded and the
113
+ * fields are length-capped), and never throws.
114
+ */
115
+ declare function paymentRejectedError(label: string, status: number, response: Response): Promise<RouteDockPaymentRejectedError>;
91
116
  /** Wrap a low-level fetch failure; preserves existing RouteDockErrors. */
92
117
  declare function wrapFetchError(err: unknown, context: string): RouteDockError;
93
118
  /**
@@ -140,4 +165,4 @@ declare class SupabaseSessionStore implements SessionStore {
140
165
  close(channelId: string): Promise<void>;
141
166
  }
142
167
 
143
- export { wrapFetchError as A, wrapMppError as B, RouteDockChannelStateError as R, type SessionStore as S, RouteDockClientVersionError as a, RouteDockDisputeError as b, RouteDockError as c, type RouteDockErrorCode as d, type RouteDockErrorOptions as e, RouteDockFacilitatorError as f, RouteDockManifestError as g, RouteDockManifestSunsetError as h, RouteDockManifestTimeoutError as i, RouteDockNetworkError as j, RouteDockNoSupportedModeError as k, RouteDockPolicyRejectError as l, RouteDockPolicyRejectedError as m, RouteDockRefundWindowError as n, RouteDockSessionError as o, RouteDockSignatureError as p, RouteDockTrustlineError as q, RouteDockVoucherMonotonicityError as r, SupabaseSessionStore as s, getEligibleAssets as t, httpStatusToError as u, isAssetEligible as v, normalizeManifestAssets as w, parseRetryAfterMs as x, resolveAssetContract as y, selectAsset as z };
168
+ export { resolveAssetContract as A, selectAsset as B, wrapFetchError as C, wrapMppError as D, RouteDockChannelStateError as R, type SessionStore as S, RouteDockClientVersionError as a, RouteDockDisputeError as b, RouteDockError as c, type RouteDockErrorCode as d, type RouteDockErrorOptions as e, RouteDockFacilitatorError as f, RouteDockManifestError as g, RouteDockManifestSunsetError as h, RouteDockManifestTimeoutError as i, RouteDockNetworkError as j, RouteDockNoSupportedModeError as k, RouteDockPaymentRejectedError as l, RouteDockPolicyRejectError as m, RouteDockPolicyRejectedError as n, RouteDockRefundWindowError as o, RouteDockSessionError as p, RouteDockSignatureError as q, RouteDockTrustlineError as r, RouteDockVoucherMonotonicityError as s, SupabaseSessionStore as t, getEligibleAssets as u, httpStatusToError as v, isAssetEligible as w, normalizeManifestAssets as x, parseRetryAfterMs as y, paymentRejectedError as z };
@@ -1,8 +1,8 @@
1
- import { h as RouteDockManifest, P as PaymentMode, A as AssetConfig, p as SessionState } from './logger-D8_Q2ytp.cjs';
1
+ import { R as RouteDockManifest, P as PaymentMode, A as AssetConfig, p as SessionState } from './types-DXaS5Wip.js';
2
2
  import { SupabaseClient } from '@supabase/supabase-js';
3
3
 
4
4
  /** Stable error codes for programmatic handling. */
5
- type RouteDockErrorCode = 'MANIFEST' | 'MANIFEST_TIMEOUT' | 'MANIFEST_SUNSET' | 'NO_SUPPORTED_MODE' | 'FACILITATOR' | 'NETWORK' | 'SIGNATURE' | 'VOUCHER_MONOTONICITY' | 'POLICY_REJECT' | 'CHANNEL_STATE' | 'DISPUTE' | 'REFUND_WINDOW' | 'CLIENT_VERSION_TOO_OLD' | 'TRUSTLINE';
5
+ type RouteDockErrorCode = 'MANIFEST' | 'MANIFEST_TIMEOUT' | 'MANIFEST_SUNSET' | 'NO_SUPPORTED_MODE' | 'FACILITATOR' | 'NETWORK' | 'SIGNATURE' | 'VOUCHER_MONOTONICITY' | 'POLICY_REJECT' | 'PAYMENT_REJECTED' | 'CHANNEL_STATE' | 'DISPUTE' | 'REFUND_WINDOW' | 'CLIENT_VERSION_TOO_OLD' | 'TRUSTLINE';
6
6
  interface RouteDockErrorOptions {
7
7
  cause?: unknown;
8
8
  /** Milliseconds to wait before retry (e.g. from Retry-After header). */
@@ -68,6 +68,24 @@ declare class RouteDockPolicyRejectError extends RouteDockError {
68
68
  readonly reason: string;
69
69
  constructor(reason: string, options?: RouteDockErrorOptions);
70
70
  }
71
+ /**
72
+ * The provider refused a signed, paid request (HTTP 4xx such as 401/402).
73
+ * Not a manifest problem and not a local policy rejection: `status` is the HTTP
74
+ * status and `reason` is the provider's own explanation when it sent one
75
+ * (e.g. the facilitator's `invalidReason`). Not retryable — the same signed
76
+ * payment would be refused again.
77
+ */
78
+ declare class RouteDockPaymentRejectedError extends RouteDockError {
79
+ readonly status: number;
80
+ /** The provider's `reason` field, if its response body was JSON and had one. */
81
+ readonly reason?: string;
82
+ /** The provider's `error` field (short summary), if present. */
83
+ readonly providerError?: string;
84
+ constructor(message: string, status: number, details?: {
85
+ reason?: string;
86
+ providerError?: string;
87
+ }, options?: RouteDockErrorOptions);
88
+ }
71
89
  /** Channel lifecycle failure (open, simulate, close, missing settlement fields). */
72
90
  declare class RouteDockChannelStateError extends RouteDockError {
73
91
  constructor(message: string, options?: RouteDockErrorOptions);
@@ -88,6 +106,13 @@ declare const RouteDockSessionError: typeof RouteDockVoucherMonotonicityError;
88
106
  declare function parseRetryAfterMs(response: Response): number | undefined;
89
107
  /** Map an HTTP status to a typed, retryable-aware RouteDock error. */
90
108
  declare function httpStatusToError(message: string, status: number, response?: Response): RouteDockError;
109
+ /**
110
+ * Build a {@link RouteDockPaymentRejectedError} from a rejected paid response.
111
+ * Reads the JSON body best-effort (a missing, non-JSON or oversized body just
112
+ * means no `reason`; the provider is untrusted, so the read is bounded and the
113
+ * fields are length-capped), and never throws.
114
+ */
115
+ declare function paymentRejectedError(label: string, status: number, response: Response): Promise<RouteDockPaymentRejectedError>;
91
116
  /** Wrap a low-level fetch failure; preserves existing RouteDockErrors. */
92
117
  declare function wrapFetchError(err: unknown, context: string): RouteDockError;
93
118
  /**
@@ -140,4 +165,4 @@ declare class SupabaseSessionStore implements SessionStore {
140
165
  close(channelId: string): Promise<void>;
141
166
  }
142
167
 
143
- export { wrapFetchError as A, wrapMppError as B, RouteDockChannelStateError as R, type SessionStore as S, RouteDockClientVersionError as a, RouteDockDisputeError as b, RouteDockError as c, type RouteDockErrorCode as d, type RouteDockErrorOptions as e, RouteDockFacilitatorError as f, RouteDockManifestError as g, RouteDockManifestSunsetError as h, RouteDockManifestTimeoutError as i, RouteDockNetworkError as j, RouteDockNoSupportedModeError as k, RouteDockPolicyRejectError as l, RouteDockPolicyRejectedError as m, RouteDockRefundWindowError as n, RouteDockSessionError as o, RouteDockSignatureError as p, RouteDockTrustlineError as q, RouteDockVoucherMonotonicityError as r, SupabaseSessionStore as s, getEligibleAssets as t, httpStatusToError as u, isAssetEligible as v, normalizeManifestAssets as w, parseRetryAfterMs as x, resolveAssetContract as y, selectAsset as z };
168
+ export { resolveAssetContract as A, selectAsset as B, wrapFetchError as C, wrapMppError as D, RouteDockChannelStateError as R, type SessionStore as S, RouteDockClientVersionError as a, RouteDockDisputeError as b, RouteDockError as c, type RouteDockErrorCode as d, type RouteDockErrorOptions as e, RouteDockFacilitatorError as f, RouteDockManifestError as g, RouteDockManifestSunsetError as h, RouteDockManifestTimeoutError as i, RouteDockNetworkError as j, RouteDockNoSupportedModeError as k, RouteDockPaymentRejectedError as l, RouteDockPolicyRejectError as m, RouteDockPolicyRejectedError as n, RouteDockRefundWindowError as o, RouteDockSessionError as p, RouteDockSignatureError as q, RouteDockTrustlineError as r, RouteDockVoucherMonotonicityError as s, SupabaseSessionStore as t, getEligibleAssets as u, httpStatusToError as v, isAssetEligible as w, normalizeManifestAssets as x, parseRetryAfterMs as y, paymentRejectedError as z };
@@ -0,0 +1,73 @@
1
+ import { b as RouteDockLogger } from './logger-DVa7VT4c.js';
2
+
3
+ /**
4
+ * Daily spend accumulator, keyed by an ISO date (YYYY-MM-DD).
5
+ *
6
+ * Totals are decimal strings of integer microUSDC (1 USDC = 10^7 units) so
7
+ * the record stays JSON-serializable with no floating point precision loss.
8
+ */
9
+ interface DailySpend {
10
+ date: string;
11
+ /** Global accumulated spend for the day, in microUSDC (e.g. "5000000") */
12
+ totalMicros: string;
13
+ /** Per-endpoint accumulated spend for the day, keyed by origin URL, in microUSDC */
14
+ endpoints: Record<string, string>;
15
+ }
16
+ /**
17
+ * Durable backing store for the client's local daily spend cap.
18
+ *
19
+ * The spend cap is a financial safety control: if its accumulator lives only in
20
+ * heap memory, any crash, OOM kill, or container restart silently resets it to
21
+ * zero and the cap can be bypassed. Inject a persistent implementation
22
+ * (Redis, SQL, Supabase, a file, …) so the counter survives restarts.
23
+ *
24
+ * ### Sharing a store between processes
25
+ * The client serializes its own read → check → write with an in-memory mutex,
26
+ * but that only protects one `RouteDockClient` instance. If more than one
27
+ * process (or client) can use the same backing store — the MCP server and the
28
+ * E2E agent share `~/.routedock/spend.json` by default — a plain `read()` /
29
+ * `write()` pair is a lost-update race: two writers both read total X, both
30
+ * pass the cap check, and both write X + amount, so a payment vanishes from the
31
+ * accumulator and the cap can be exceeded. A store that is shared across
32
+ * processes MUST implement {@link SpendStore.update}, which the client uses
33
+ * instead of `read()` + `write()` whenever it is present.
34
+ */
35
+ interface SpendStore {
36
+ /** Return the persisted accumulator, or null if none has been written yet. */
37
+ read(): Promise<DailySpend | null>;
38
+ /** Persist the accumulator. */
39
+ write(state: DailySpend): Promise<void>;
40
+ /**
41
+ * Atomically read-modify-write the accumulator. Optional, but required for
42
+ * stores shared across processes (see above).
43
+ *
44
+ * Implementations must guarantee that no other writer can change the state
45
+ * between the read that produces `prev` and the write of `fn`'s result:
46
+ * `fn` receives the current state (or `null`), and returns the new state to
47
+ * persist, or `undefined` to leave the state unchanged (no write). If `fn`
48
+ * throws, nothing is written and the error propagates. `fn` is synchronous
49
+ * in spirit — keep it fast and free of side effects, because an
50
+ * implementation backed by optimistic concurrency (compare-and-swap) may
51
+ * call it more than once. Resolves to the state after the update.
52
+ */
53
+ update?(fn: (prev: DailySpend | null) => DailySpend | undefined): Promise<DailySpend | null>;
54
+ }
55
+ interface InMemorySpendStoreOptions {
56
+ /** Log a startup warning about non-durability. Defaults to true. */
57
+ warn?: boolean;
58
+ /** Log sink for the non-durability warning. Defaults to a console-backed logger. */
59
+ logger?: RouteDockLogger;
60
+ }
61
+ /**
62
+ * Default, non-durable {@link SpendStore}. Holds the accumulator on the heap, so
63
+ * it resets on every process restart. Logs a startup warning unless silenced.
64
+ * Use this only for development; inject a persistent store in production.
65
+ */
66
+ declare class InMemorySpendStore implements SpendStore {
67
+ private state;
68
+ constructor(options?: InMemorySpendStoreOptions);
69
+ read(): Promise<DailySpend | null>;
70
+ write(state: DailySpend): Promise<void>;
71
+ }
72
+
73
+ export { type DailySpend as D, InMemorySpendStore as I, type SpendStore as S, type InMemorySpendStoreOptions as a };