@routedock/routedock 0.1.2 → 0.3.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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +532 -4
  3. package/dist/MppSessionHandler-1EL8QBAn.d.cts +215 -0
  4. package/dist/MppSessionHandler-Bq4S-JTE.d.ts +215 -0
  5. package/dist/ProviderRegistry-CA_TdREG.d.ts +142 -0
  6. package/dist/ProviderRegistry-D-0rqK0N.d.cts +142 -0
  7. package/dist/RouteDockClient-Bh86bVly.d.ts +469 -0
  8. package/dist/RouteDockClient-go9aopfF.d.cts +469 -0
  9. package/dist/SessionReconciler-Bw-PoBVl.d.ts +134 -0
  10. package/dist/SessionReconciler-Cz2nuOlJ.d.cts +134 -0
  11. package/dist/SessionStore-B4-Hi4H8.d.cts +143 -0
  12. package/dist/SessionStore-BaYiegP0.d.ts +143 -0
  13. package/dist/client.cjs +2746 -217
  14. package/dist/client.d.cts +51 -81
  15. package/dist/client.d.ts +51 -81
  16. package/dist/client.js +2690 -218
  17. package/dist/index.cjs +3634 -420
  18. package/dist/index.d.cts +12 -3
  19. package/dist/index.d.ts +12 -3
  20. package/dist/index.js +3569 -419
  21. package/dist/logger-D8_Q2ytp.d.cts +467 -0
  22. package/dist/logger-D8_Q2ytp.d.ts +467 -0
  23. package/dist/provider/fastify.cjs +1099 -0
  24. package/dist/provider/fastify.d.cts +40 -0
  25. package/dist/provider/fastify.d.ts +40 -0
  26. package/dist/provider/fastify.js +1097 -0
  27. package/dist/provider/hono.cjs +1152 -0
  28. package/dist/provider/hono.d.cts +101 -0
  29. package/dist/provider/hono.d.ts +101 -0
  30. package/dist/provider/hono.js +1142 -0
  31. package/dist/provider.cjs +1105 -147
  32. package/dist/provider.d.cts +38 -27
  33. package/dist/provider.d.ts +38 -27
  34. package/dist/provider.js +1079 -148
  35. package/dist/react.cjs +2931 -0
  36. package/dist/react.d.cts +107 -0
  37. package/dist/react.d.ts +107 -0
  38. package/dist/react.js +2924 -0
  39. package/dist/routedockMiddleware-57gUaEMf.d.ts +64 -0
  40. package/dist/routedockMiddleware-DlyLrxp7.d.cts +64 -0
  41. package/dist/schema.cjs +404 -0
  42. package/dist/schema.d.cts +3 -0
  43. package/dist/schema.d.ts +3 -0
  44. package/dist/schema.js +399 -0
  45. package/dist/sign-AKH67sft.d.cts +53 -0
  46. package/dist/sign-BEq-OsSq.d.ts +53 -0
  47. package/dist/testing.cjs +59 -0
  48. package/dist/testing.d.cts +114 -0
  49. package/dist/testing.d.ts +114 -0
  50. package/dist/testing.js +56 -0
  51. package/package.json +143 -23
  52. package/PUBLISH_CHECKLIST.md +0 -70
  53. package/dist/SessionStore-CCVO0PHM.d.cts +0 -152
  54. package/dist/SessionStore-CCVO0PHM.d.ts +0 -152
  55. package/dist/routedockMiddleware-B7WTB3LT.d.cts +0 -53
  56. package/dist/routedockMiddleware-DRgdrycG.d.ts +0 -53
  57. package/src/client/ModeRouter.ts +0 -104
  58. package/src/client/MppChargeClient.ts +0 -44
  59. package/src/client/MppSessionClient.ts +0 -144
  60. package/src/client/RouteDockClient.ts +0 -124
  61. package/src/client/index.ts +0 -8
  62. package/src/client/x402Client.ts +0 -84
  63. package/src/index.ts +0 -9
  64. package/src/provider/MppChargeHandler.ts +0 -90
  65. package/src/provider/MppSessionHandler.ts +0 -157
  66. package/src/provider/index.ts +0 -7
  67. package/src/provider/routedockMiddleware.ts +0 -154
  68. package/src/provider/x402Handler.ts +0 -164
  69. package/src/schemas/routedock.schema.json +0 -159
  70. package/src/store/SessionStore.ts +0 -90
  71. package/src/tests/smoke.test.ts +0 -228
  72. package/src/types.ts +0 -165
  73. package/tsconfig.json +0 -14
  74. package/tsup.config.ts +0 -15
@@ -0,0 +1,469 @@
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.cjs';
3
+ import { ClientStellarSigner } from '@x402/stellar';
4
+ import { SignAuthEntry } from '@stellar/stellar-sdk/contract';
5
+ import { usdcToStroops } from '@routedock/nulth-sdk';
6
+
7
+ interface RetryPolicy {
8
+ /** Total attempts including the first call. Default: 4 (3 retries). */
9
+ maxAttempts?: number;
10
+ /** Base delay for exponential backoff in ms. Default: 250. */
11
+ baseDelayMs?: number;
12
+ /** Maximum jittered backoff delay in ms. Default: 30000. */
13
+ maxDelayMs?: number;
14
+ /** Called before each retry with the attempt number, error, and upcoming delay. */
15
+ /**
16
+ * Observability hook fired before each retry. Awaited, so a returned promise
17
+ * is honoured; throwing or rejecting is caught and logged rather than
18
+ * aborting the retry loop.
19
+ */
20
+ onRetry?: (attempt: number, error: Error, nextDelayMs: number) => void | Promise<void>;
21
+ /**
22
+ * Log sink for internal diagnostics (an `onRetry` hook that throws).
23
+ * Defaults to a console-backed logger.
24
+ */
25
+ logger?: RouteDockLogger;
26
+ }
27
+ declare const DEFAULT_RETRY_POLICY: Required<Omit<RetryPolicy, 'onRetry' | 'logger'>>;
28
+ /**
29
+ * Full jitter exponential backoff delay.
30
+ * Delay range: 0..min(maxDelayMs, baseDelayMs * 2 ** attempt).
31
+ */
32
+ declare function backoffDelayMs(attempt: number, baseDelayMs: number, maxDelayMs?: number): number;
33
+ /**
34
+ * Execute `fn` with exponential backoff on retryable RouteDockErrors.
35
+ * Honors `retryAfterMs` from facilitator 429/503 responses when present.
36
+ */
37
+ declare function withRetry<T>(fn: () => Promise<T>, policy?: RetryPolicy): Promise<T>;
38
+
39
+ /**
40
+ * 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`.
54
+ */
55
+ declare function assertManifestValid(manifest: RouteDockManifest, baseUrl?: string): void;
56
+ /**
57
+ * Override the manifest cache's max size (default 512). Affects the shared,
58
+ * process-wide cache used by all RouteDockClient instances.
59
+ */
60
+ declare function configureManifestCache(maxSize: number): void;
61
+ /**
62
+ * Evict a provider's cached manifest immediately, forcing the next
63
+ * `fetchManifest(baseUrl)` to re-fetch and re-validate it. Use when a
64
+ * provider signals that their manifest changed (e.g. after a config update
65
+ * or a failed payment due to a mode change) so the next request does not
66
+ * keep routing on a stale cached copy for the remainder of the TTL.
67
+ *
68
+ * The cache is keyed by base URL as passed to `fetchManifest` — callers that
69
+ * pass a full URL (e.g. `RouteDockClient.invalidateManifest`) must normalize
70
+ * to the origin before calling this.
71
+ */
72
+ declare function invalidateManifest(baseUrl: string): void;
73
+
74
+ interface ModeSelectOptions {
75
+ /** Force mpp-session if the provider supports it */
76
+ sustained?: boolean;
77
+ session?: boolean;
78
+ /**
79
+ * Preferred streaming transport for sustained sessions. When 'websocket',
80
+ * the mpp-session-ws variant is preferred when the provider advertises it;
81
+ * when 'sse' (or omitted), the classic mpp-session mode is preferred.
82
+ * Either transport falls back to the other session variant when the
83
+ * preferred one is not advertised.
84
+ */
85
+ transport?: 'sse' | 'websocket';
86
+ /** Prefer the lowest-cost supported per-request mode when set to 'cost'. */
87
+ optimize?: 'cost';
88
+ /** Optional maximum acceptable per-request amount for cost-based selection. */
89
+ budget_per_request?: string;
90
+ /**
91
+ * Override mode selection and use this specific mode.
92
+ * Throws RouteDockNoSupportedModeError if the provider does not support it.
93
+ */
94
+ forceMode?: PaymentMode;
95
+ /** Structured logger for mode selection events. Defaults to no-op. */
96
+ logger?: RouteDockLogger;
97
+ }
98
+ /**
99
+ * Fetch, validate, and cache a RouteDock manifest from `baseUrl`.
100
+ *
101
+ * `expectedPayee`, when provided, is the out-of-band trust anchor for the
102
+ * manifest (e.g. the account this provider registered on-chain): the fetched
103
+ * manifest's `payee` must equal it or `RouteDockSignatureError` is thrown.
104
+ * Callers that obtained a payee from a registry/pinned allowlist MUST pass it;
105
+ * without it the self-signed signature alone proves nothing about who served
106
+ * the manifest.
107
+ */
108
+ declare function fetchManifest(baseUrl: string, retryPolicy?: RetryPolicy, manifestTimeoutMs?: number, expectedPayee?: string): Promise<RouteDockManifest>;
109
+ /**
110
+ * Deterministic mode selection per Section 6.3 of ROUTEDOCK_MASTER.md.
111
+ *
112
+ * Active modes are always considered before modes listed in `deprecated_modes`.
113
+ * Deprecated modes remain usable as a compatibility fallback and produce a
114
+ * logger warning when selected. An explicit forceMode remains authoritative,
115
+ * but also warns when the forced mode is deprecated.
116
+ */
117
+ declare function selectMode(manifest: RouteDockManifest, options?: ModeSelectOptions): PaymentMode;
118
+ /**
119
+ * Rank a list of provider manifests by estimated p50 round-trip latency for a given target region.
120
+ * Manifests declaring latency_hints for the target region are sorted ascending by latency (lowest first).
121
+ * Manifests that list the region in `regions` without an explicit latency hint come next.
122
+ * Manifests not advertising the target region come last.
123
+ */
124
+ declare function rankProvidersByLatency(manifests: RouteDockManifest[], targetRegion: string): RouteDockManifest[];
125
+
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;
141
+ }
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'>;
177
+ }
178
+ 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
+ interface NulthVaultConfig {
200
+ mode: 'nulth';
201
+ nulthAccount: string;
202
+ prover?: 'mock';
203
+ witnessSecret: string;
204
+ allowedPayees: readonly string[];
205
+ dailyCapUsdc: string;
206
+ expiryLedger?: number;
207
+ verifierContract?: string;
208
+ }
209
+ declare function assertNulthVaultManifest(manifest: RouteDockManifest, nulthAccount: string): void;
210
+ declare function fetchLedgerSequence(network: 'testnet' | 'mainnet'): Promise<number>;
211
+ declare function prepareNulthSigner(vault: NulthVaultConfig, manifest: RouteDockManifest, mode: PaymentMode, network: 'testnet' | 'mainnet', ledgerSequenceOverride?: number): Promise<{
212
+ signer: ClientStellarSigner;
213
+ config: NulthSignerConfig;
214
+ }>;
215
+
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
+ interface SpendCap {
264
+ /** Maximum total USDC spend per day (decimal string, e.g. "1.00") */
265
+ daily: string;
266
+ asset: 'USDC';
267
+ /**
268
+ * Optional per-endpoint daily spend caps, keyed by endpoint origin URL
269
+ * (e.g. "https://api.openai.com"). Checked before the global `daily` cap.
270
+ * An endpoint not listed here is only subject to the global cap.
271
+ * Both limits are enforced independently — hitting an endpoint cap does
272
+ * not prevent spend on other endpoints, but all spend still counts toward
273
+ * the global cap.
274
+ *
275
+ * Keys are normalized to `new URL(key).origin` when the client is
276
+ * constructed (lowercased host, no trailing slash, no default port), so
277
+ * "https://API.example.com/", "https://api.example.com" and
278
+ * "https://api.example.com:443" are all equivalent. A key that isn't a
279
+ * valid URL, that includes a path/query/hash, or that normalizes to the
280
+ * same origin as another key throws at construction time.
281
+ */
282
+ endpointCaps?: Record<string, string>;
283
+ }
284
+ type VaultConfig = NulthVaultConfig;
285
+ interface RouteDockClientConfig {
286
+ /** Stellar keypair or raw secret key (S...) — fee payer / fallback signer */
287
+ wallet: Keypair | string;
288
+ network: 'testnet' | 'mainnet';
289
+ /** Optional local daily spend cap — checked before every payment, nulth vault payments included */
290
+ spendCap?: SpendCap;
291
+ /**
292
+ * Ed25519 secret key (S...) for signing channel commitments. Required for mpp-session.
293
+ *
294
+ * WARNING: Do not log or serialize the config object — it contains this secret in plaintext.
295
+ * The RouteDockClient stores it outside the instance to prevent leakage via JSON.stringify
296
+ * or console.log, but the raw config object is not protected.
297
+ */
298
+ commitmentSecret?: string | undefined;
299
+ /** Retry policy for transient failures (network, facilitator 5xx). */
300
+ retryPolicy?: RetryPolicy;
301
+ /**
302
+ * Durable backing store for the daily spend cap accumulator. Defaults to a
303
+ * non-durable in-memory store that resets on restart (with a startup warning).
304
+ * Inject a persistent implementation for production safety.
305
+ */
306
+ spendStore?: SpendStore;
307
+ /** Structured logger for SDK events. Defaults to no-op (silent). */
308
+ logger?: RouteDockLogger;
309
+ /**
310
+ * Timeout in milliseconds for manifest fetches. A provider that accepts the TCP
311
+ * connection but never sends a response body will be aborted after this delay.
312
+ * Default: 5000 ms.
313
+ */
314
+ manifestTimeoutMs?: number;
315
+ /**
316
+ * Out-of-band trust anchor for manifests: the Stellar payee (G...) the client
317
+ * expects the provider to be (e.g. the account registered in the on-chain
318
+ * provider registry, or a pinned/allowlisted key). When set, every fetched
319
+ * manifest's `payee` must equal this value or payment is refused with
320
+ * `RouteDockSignatureError`. Without it, a self-signed manifest proves nothing
321
+ * about who served it, so set this whenever you obtained a provider address
322
+ * from a registry.
323
+ */
324
+ expectedPayee?: string;
325
+ /**
326
+ * Vault custody mode. When `nulth`, payments use a Nulth account as payer
327
+ * with off-chain ZK proofs attached as auth signatures.
328
+ */
329
+ vault?: VaultConfig;
330
+ }
331
+ /**
332
+ * @deprecated Use {@link usdcToStroops} — the same validated bigint converter,
333
+ * shared across the SDK and nulth-sdk. Kept as a thin alias so existing spend
334
+ * cap code keeps working during migration.
335
+ */
336
+ declare const usdcToMicros: typeof usdcToStroops;
337
+ declare class RouteDockClient {
338
+ /** Per-(account,asset) trustline existence cache keyed by `${network}:${pubkey}:${asset}` */
339
+ private static _trustlineCache;
340
+ private readonly keypair;
341
+ private readonly network;
342
+ private readonly spendCap;
343
+ private readonly retryPolicy;
344
+ private readonly logger;
345
+ private readonly manifestTimeoutMs;
346
+ private readonly expectedPayee;
347
+ private readonly vault;
348
+ /**
349
+ * Durable backing store for the local daily spend accumulator (keyed by
350
+ * YYYY-MM-DD). Totals are persisted as decimal strings of microUSDC
351
+ * (1 USDC = 10^7) so stores stay JSON-safe with no precision loss.
352
+ */
353
+ private readonly spendStore;
354
+ private x402;
355
+ private readonly charge;
356
+ private readonly session;
357
+ /**
358
+ * Promise-chain mutex for serializing read-modify-write on the spend
359
+ * accumulator. Each pay() call awaits the previous one, preventing
360
+ * concurrent read-modify-write races that could overrun the cap.
361
+ */
362
+ private _spendMutex;
363
+ /**
364
+ * Pending spend reservations keyed by reserveId. Used by _rollbackSpend
365
+ * to revert the accumulator when a payment fails after reservation.
366
+ */
367
+ private _pendingReserves;
368
+ constructor(config: RouteDockClientConfig);
369
+ /** Fetch manifest and select mode — shared by pay() and estimateCost(). */
370
+ private _resolveManifest;
371
+ /**
372
+ * Serialize async work through the per-instance spend mutex so concurrent
373
+ * pay() calls cannot interleave read-modify-write on the accumulator.
374
+ */
375
+ private _withSpendMutex;
376
+ /**
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.
381
+ */
382
+ preflight(manifest: RouteDockManifest, mode?: PaymentMode, endpoint?: string): Promise<PreflightResult>;
383
+ /**
384
+ * Verify via Horizon that the payer account has a trustline for the
385
+ * payment asset declared in the manifest. Throws RouteDockTrustlineError
386
+ * with an exact Stellar CLI change-trust remediation command when the
387
+ * trustline is missing. Results are cached per (account, asset) so the
388
+ * per-payment overhead is one RPC call amortized to zero after the first.
389
+ */
390
+ private _checkTrustline;
391
+ /**
392
+ * Pay for one request at `url`. Fetches manifest, selects payment mode,
393
+ * runs trustline preflight, reserves local spend cap BEFORE executing the
394
+ * payment, then commits the spend record on success. Rolls back the
395
+ * reservation if the on-chain payment fails, and also when the call
396
+ * completes without paying anything (an x402 endpoint answering a plain
397
+ * 200 with no challenge). Concurrent pay() calls are serialized through a
398
+ * per-instance mutex to prevent spend-cap overrun.
399
+ */
400
+ pay(url: string, options?: ModeSelectOptions): Promise<PaymentResult>;
401
+ /** Nulth ZK vault path — proof built off-chain, attached as auth signature */
402
+ private _payWithNulthVault;
403
+ /**
404
+ * Resolve manifest and compute the expected charge WITHOUT submitting any
405
+ * transaction. Safe to call before committing — for approval gates and
406
+ * budget-aware routing.
407
+ */
408
+ estimateCost(url: string, options?: ModeSelectOptions): Promise<EstimateCostResult>;
409
+ /**
410
+ * Open a sustained MPP session at `url`. Verifies mpp-session is supported
411
+ * before opening a channel. Returns a SessionHandle for streaming + closing.
412
+ *
413
+ * By default the session auto-closes after a wall-clock timeout (1h) so an
414
+ * orphaned channel cannot keep collateral locked on-chain — override or
415
+ * disable via `options.maxDurationMs`.
416
+ */
417
+ openSession(url: string, options?: SessionOptions): Promise<SessionHandle>;
418
+ /**
419
+ * Record spend for an MPP session voucher. Called before each voucher
420
+ * fetch in stream(). Enforces the same local daily and per-endpoint caps
421
+ * as discrete pay() calls. Returns a commit function to clean up the
422
+ * reservation after the voucher fetch succeeds.
423
+ */
424
+ private _recordSessionSpend;
425
+ /**
426
+ * Remove the commitment secret from memory and make it eligible for GC.
427
+ * Call when this client instance is no longer needed — particularly in
428
+ * long-lived processes or Worker threads.
429
+ */
430
+ dispose(): void;
431
+ /**
432
+ * Evict the cached manifest for `url` immediately, so the next pay() /
433
+ * estimateCost() / openSession() call re-fetches it instead of routing on
434
+ * the stale cached copy. Use when a provider signals their manifest
435
+ * changed (e.g. after a config update) and the change must take effect
436
+ * before the cache TTL expires.
437
+ */
438
+ invalidateManifest(url: string): void;
439
+ /**
440
+ * Reserve spend against local daily caps BEFORE dispatching payment.
441
+ * Returns a reserveId that must be passed to _commitSpend() on success
442
+ * or _rollbackSpend() on failure.
443
+ *
444
+ * The entire read-check-write is serialized through the per-instance
445
+ * mutex (_withSpendMutex) so concurrent pay() calls cannot interleave
446
+ * and overrun the cap.
447
+ *
448
+ * Enforcement order:
449
+ * 1. Per-endpoint cap (if `endpointCaps[endpointKey]` is set) — checked first.
450
+ * 2. Global daily cap — checked second.
451
+ *
452
+ * Both limits are independent: a payment is only reserved after **both** pass.
453
+ * All spend (regardless of endpoint) counts toward the global accumulator.
454
+ */
455
+ private _checkAndReserveSpend;
456
+ /**
457
+ * Commit a previously reserved spend after payment succeeds. For discrete
458
+ * pay() calls this is a no-op (the spend was already recorded by reserve).
459
+ * Reserved for future use if the committed amount differs from the reserved.
460
+ */
461
+ private _commitSpend;
462
+ /**
463
+ * Rollback a previously reserved spend when payment fails. Reverts the
464
+ * tentative accumulator write done by _checkAndReserveSpend.
465
+ */
466
+ private _rollbackSpend;
467
+ }
468
+
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 };
@@ -0,0 +1,134 @@
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';
4
+ import { Keypair } from '@stellar/stellar-sdk';
5
+
6
+ /**
7
+ * Settlement idempotency backed by Postgres via Supabase.
8
+ *
9
+ * The in-memory default {@link InMemorySeenTxStore} is per-handler and
10
+ * per-process. On serverless/edge runtimes (Cloudflare Workers, Deno Deploy,
11
+ * Lambda@Edge) each request may land in a fresh isolate with an empty cache.
12
+ * An agent retrying after a post-settlement timeout would miss the cache and
13
+ * settle a second time, charging twice on-chain.
14
+ *
15
+ * Postgres gives the read-your-writes consistency this needs; Workers KV
16
+ * would not, since a retry can outrun propagation and reintroduce the double
17
+ * settle.
18
+ *
19
+ * Requires the `settlements` table from migration `003_settlement_idempotency`
20
+ * and the `claim_settlement_replay` function from migration
21
+ * `006_settlement_replay_limit`:
22
+ *
23
+ * ```sql
24
+ * CREATE TABLE settlements (
25
+ * key TEXT PRIMARY KEY,
26
+ * tx_hash TEXT,
27
+ * headers JSONB,
28
+ * created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
29
+ * );
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { SupabaseSeenTxStore } from '@routedock/routedock/provider/hono'
35
+ * import { createClient } from '@supabase/supabase-js'
36
+ *
37
+ * const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!)
38
+ * const seenTxStore = new SupabaseSeenTxStore(supabase)
39
+ *
40
+ * routedockHono({ ..., seenTxStore })
41
+ * ```
42
+ */
43
+ declare class SupabaseSeenTxStore implements SeenTxStore {
44
+ private readonly supabase;
45
+ constructor(supabase: SupabaseClient);
46
+ get(key: string): Promise<SettlementRecord | undefined>;
47
+ set(key: string, record: SettlementRecord): Promise<void>;
48
+ /**
49
+ * Atomically increment the settlement's replay counter through the
50
+ * `claim_settlement_replay` Postgres function (migration 006). The UPDATE's
51
+ * row lock gives concurrent claims distinct counts. Returns `Infinity` when
52
+ * the key no longer exists, so the caller treats it as spent.
53
+ */
54
+ claimReplay(key: string): Promise<number>;
55
+ }
56
+
57
+ interface RegisterProviderOptions {
58
+ supabase: SupabaseClient;
59
+ manifest: RouteDockManifest & {
60
+ signature?: string;
61
+ signature_version?: string;
62
+ };
63
+ baseUrl: string;
64
+ /**
65
+ * Whether to flag the provider record as verified in Supabase.
66
+ * Defaults to true (since signature is verified before writing).
67
+ */
68
+ verified?: boolean;
69
+ }
70
+ /**
71
+ * Register a provider in the Supabase `providers` table.
72
+ *
73
+ * Verifies the manifest's Ed25519 signature before upserting into the
74
+ * database keyed on `base_url`.
75
+ *
76
+ * Throws `RouteDockSignatureError` if the manifest is unsigned or invalid,
77
+ * and `Error` if the database upsert fails.
78
+ */
79
+ declare function registerProvider(optionsOrSupabase: RegisterProviderOptions | SupabaseClient, manifestArg?: RouteDockManifest & {
80
+ signature?: string;
81
+ }, baseUrlArg?: string, verifiedArg?: boolean): Promise<void>;
82
+
83
+ /**
84
+ * Session Reconciler — Recovery mechanism for orphaned MPP sessions
85
+ *
86
+ * Scans Supabase for sessions marked as "closing" (client initiated close but
87
+ * provider crashed before broadcasting settlement). Recovers abandoned funds by
88
+ * broadcasting the channel close transaction with the latest signed commitment.
89
+ *
90
+ * Idempotent: Safe to run multiple times on the same session (settlement_tx_hash
91
+ * acts as deduplication key).
92
+ */
93
+
94
+ type Network = 'testnet' | 'mainnet';
95
+ type ChannelCloseFn = (args: {
96
+ channel: string;
97
+ amount: bigint;
98
+ signature: Buffer;
99
+ feePayer: {
100
+ envelopeSigner: Keypair;
101
+ };
102
+ network: 'stellar:testnet' | 'stellar:pubnet';
103
+ }) => Promise<string>;
104
+ interface SessionReconcilerOptions {
105
+ supabase: SupabaseClient;
106
+ network: Network;
107
+ payeeSecretKey: string;
108
+ onRecovered?: (channelId: string, txHash: string, totalPaid: string) => Promise<void>;
109
+ /** Injectable for testing. Defaults to @stellar/mpp/channel/server close() */
110
+ channelClose?: ChannelCloseFn;
111
+ }
112
+ interface ReconciliationStats {
113
+ orphanedCount: number;
114
+ recoveredCount: number;
115
+ skippedCount: number;
116
+ failedCount: number;
117
+ errors: Array<{
118
+ channelId: string;
119
+ reason: string;
120
+ }>;
121
+ }
122
+ declare function decimalToStroops(amountStr: string): bigint;
123
+ /**
124
+ * Scan for abandoned sessions and attempt recovery.
125
+ * Idempotent: sessions with settlement_tx_hash are skipped (already recovered).
126
+ */
127
+ declare function reconcileAbandonedSessions(opts: SessionReconcilerOptions): Promise<ReconciliationStats>;
128
+ /**
129
+ * Run reconciliation on provider startup.
130
+ * Logs results and recovers orphaned sessions before accepting new connections.
131
+ */
132
+ declare function runStartupReconciliation(opts: SessionReconcilerOptions, logger?: RouteDockLogger): Promise<void>;
133
+
134
+ export { type ReconciliationStats as R, type SessionReconcilerOptions as S, type RegisterProviderOptions as a, SupabaseSeenTxStore as b, registerProvider as c, decimalToStroops as d, runStartupReconciliation as e, reconcileAbandonedSessions as r };