@dvmkit/sdk 0.1.5-rc.9 → 0.2.1-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/{chunk-MLRCSJYX.js → chunk-2D75MNNG.js} +178 -550
- package/dist/{chunk-BIFLRKMO.js → chunk-2KGG5ORW.js} +1 -1
- package/dist/{chunk-27V2ILSR.js → chunk-4A2RAKCW.js} +2 -2
- package/dist/{chunk-5URG56JJ.js → chunk-A4CM2BD3.js} +18 -2
- package/dist/{chunk-L4OYF4DQ.js → chunk-AJNFXR7J.js} +2 -2
- package/dist/{chunk-S3XAHZQY.js → chunk-COYKJRMM.js} +24 -3
- package/dist/{chunk-UP2F5RRT.js → chunk-D7YQYLLM.js} +1 -4
- package/dist/{chunk-CEOAHV2I.js → chunk-FDKRXOZO.js} +0 -5
- package/dist/{chunk-TQWGQCNV.js → chunk-FPOLJRBF.js} +5 -5
- package/dist/{chunk-BTZY7VPH.js → chunk-GAIPXGM3.js} +1 -1
- package/dist/{chunk-SSSZUVWM.js → chunk-JAHRF5X3.js} +89 -39
- package/dist/{chunk-5PBOA25N.js → chunk-JNKQZVZX.js} +472 -617
- package/dist/{chunk-U6M3ATSG.js → chunk-MD3H3BF7.js} +40 -6
- package/dist/{chunk-CGKZDODG.js → chunk-MG67KXU7.js} +0 -5
- package/dist/{chunk-KSQEFVFJ.js → chunk-MMCE7XSR.js} +1 -1
- package/dist/{chunk-JZWELPFH.js → chunk-N5B66UOS.js} +4 -16
- package/dist/{chunk-BIP6G74V.js → chunk-O6D72KYA.js} +10 -10
- package/dist/{chunk-KXCKFIJI.js → chunk-QX52F6IB.js} +1 -1
- package/dist/{chunk-RW5LP57K.js → chunk-SDK6KDJN.js} +0 -1
- package/dist/{chunk-KVEHHC7W.js → chunk-U5VNEALI.js} +24 -13
- package/dist/{chunk-FROTD5XQ.js → chunk-V5ZRATJH.js} +2 -3
- package/dist/{chunk-EVBK675R.js → chunk-VA7GT6C6.js} +31 -37
- package/dist/{chunk-2UUXIIOC.js → chunk-VIVXB4CD.js} +3 -3
- package/dist/{chunk-6BQM7TOW.js → chunk-XMDQOXHO.js} +11 -38
- package/dist/{chunk-DMNLFNTW.js → chunk-XRLKGCAW.js} +2 -2
- package/dist/{chunk-M7LHFJ5K.js → chunk-XZ7LW4S5.js} +2 -3
- package/dist/{chunk-E4EVGPDX.js → chunk-YO56VCTQ.js} +0 -6
- package/dist/{credit-ledger-2DFQHNLB.js → credit-ledger-MHA652TV.js} +2 -2
- package/dist/{credit-menu-enwMbn55.d.ts → credit-menu-kDA9RVzD.d.ts} +524 -1077
- package/dist/{fx-D860pZvP.d.ts → fx-DhyT1Cjh.d.ts} +38 -82
- package/dist/index.d.ts +11 -26
- package/dist/index.js +2 -2
- package/dist/internal/caller.d.ts +644 -2140
- package/dist/internal/caller.js +32 -66
- package/dist/internal/server.d.ts +40 -90
- package/dist/internal/server.js +16 -16
- package/dist/{job-store-BUGqvCfL.d.ts → job-store-D3CcZp_g.d.ts} +73 -66
- package/dist/{lightning-backend-BozcevPZ.d.ts → lightning-backend-BhsyrBLa.d.ts} +19 -41
- package/dist/{memory-credit-ledger-MNUOTQO5.js → memory-credit-ledger-BYBATRN3.js} +3 -3
- package/dist/{mpp-secret-state-WNAQQ6K4.js → mpp-secret-state-B5GCILVH.js} +1 -1
- package/dist/{mpp-setup-4FJD6ZHV.js → mpp-setup-MPP3ST5X.js} +1 -1
- package/dist/{payout-reporter-RG6XNGPI.js → payout-reporter-KEMUXEUO.js} +2 -2
- package/dist/{postgres-consumed-credential-store-VHBT4KEA.js → postgres-consumed-credential-store-6N2V5MJN.js} +2 -2
- package/dist/postgres-job-store-MZZZMS2B.js +7 -0
- package/dist/postgres-kv-store-DXGTKXLU.js +7 -0
- package/dist/{postgres-replay-store-UJXRT6VO.js → postgres-replay-store-ZF4JNZOW.js} +2 -2
- package/dist/{pricing-4CEB34RM.js → pricing-MU5GNUJZ.js} +1 -1
- package/dist/{processed-payment-store-HAA4SFNK.js → processed-payment-store-GKMAWOM3.js} +2 -2
- package/dist/revenue-reporter-O75BGYFK.js +7 -0
- package/dist/server/index.d.ts +47 -159
- package/dist/server/index.js +67 -138
- package/dist/{ssrf-DbFkpDv0.d.ts → ssrf-dMooihtY.d.ts} +1 -2
- package/dist/{step-cache-5dljDqrQ.d.ts → step-cache-v2yhyhSZ.d.ts} +617 -1532
- package/dist/{tempo-charge-store-RIFTALZK.js → tempo-charge-store-SDTUNTKA.js} +2 -2
- package/dist/{tempo-lifecycle-DFIXQ54Q.js → tempo-lifecycle-G5SB4HFC.js} +5 -5
- package/dist/{tempo-session-store-2JNOKJGX.js → tempo-session-store-WF3VZXNF.js} +2 -2
- package/dist/{tempo-wallet-4QKSV65O.js → tempo-wallet-ZFNUDFVO.js} +2 -2
- package/dist/testing/index.d.ts +5 -15
- package/dist/testing/index.js +4 -11
- package/dist/{usd-DoRuAckA.d.ts → usd-Cu9G6xv2.d.ts} +14 -16
- package/dist/{wallet-CJC8lwxx.d.ts → wallet-Dwjs5n_M.d.ts} +1 -1
- package/dist/{x402-5H27DCBE.js → x402-CI6PSHNO.js} +5 -5
- package/package.json +3 -2
- package/dist/postgres-job-store-3RAXMNSY.js +0 -7
- package/dist/postgres-kv-store-JFBDP5IP.js +0 -7
- package/dist/revenue-reporter-ASZ7SHHH.js +0 -7
|
@@ -25,11 +25,8 @@ declare class InvalidCurrencyError extends Error {
|
|
|
25
25
|
constructor(raw: unknown);
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* "usd" (the bundled fx fetcher is USD/BTC); EUR/GBP/JPY land when a
|
|
31
|
-
* multi-currency fx fetcher does. The message names the supported set so
|
|
32
|
-
* operators see exactly what's missing.
|
|
28
|
+
* A valid currency code that the configured FX snapshot cannot price.
|
|
29
|
+
* The error message names the supported set so the builder can correct it.
|
|
33
30
|
*/
|
|
34
31
|
declare class UnsupportedCurrencyError extends Error {
|
|
35
32
|
readonly raw: string;
|
|
@@ -128,7 +125,7 @@ interface X402Config {
|
|
|
128
125
|
* When true (default), the SDK includes x402 `PaymentRequirements` on the
|
|
129
126
|
* 402 response body alongside MPP's `WWW-Authenticate: Payment` header so
|
|
130
127
|
* x402-speaking agents (Coinbase reference client, thirdweb-ecosystem)
|
|
131
|
-
* can pay. Flip to `false` for MPP-only DVMs
|
|
128
|
+
* can pay. Flip to `false` for MPP-only DVMs.
|
|
132
129
|
*/
|
|
133
130
|
dualServe?: boolean;
|
|
134
131
|
/** Human-readable description carried on the `PaymentRequirements`. */
|
|
@@ -156,7 +153,7 @@ interface X402Receipt {
|
|
|
156
153
|
verified: boolean;
|
|
157
154
|
amountMsats: number;
|
|
158
155
|
/**
|
|
159
|
-
* Native USDC amount in 6-decimal microunits
|
|
156
|
+
* Native USDC amount in 6-decimal microunits. Captured from the
|
|
160
157
|
* payment payload's `value` field so the platform revenue ledger records
|
|
161
158
|
* the rail-native value without re-deriving it from `amountMsats` + a
|
|
162
159
|
* fresh BTC rate (lossy and racy across the boundary).
|
|
@@ -166,27 +163,14 @@ interface X402Receipt {
|
|
|
166
163
|
txHash?: string;
|
|
167
164
|
/**
|
|
168
165
|
* Base64-encoded `SettleResponse` for the `X-PAYMENT-RESPONSE` header the
|
|
169
|
-
* server attaches to the 2xx response per x402 spec
|
|
166
|
+
* server attaches to the 2xx response per x402 spec.
|
|
170
167
|
*/
|
|
171
168
|
settleResponseHeader?: string;
|
|
172
169
|
/** When `verified === false`, a short stable code surfacing the reason. */
|
|
173
170
|
invalidReason?: string;
|
|
174
171
|
}
|
|
175
172
|
|
|
176
|
-
/**
|
|
177
|
-
* dvmkit's x402 wire-format implementation — the v1/v2 `PaymentRequirements` /
|
|
178
|
-
* `PaymentPayload` envelope shapes, payment-header encode/decode, and the
|
|
179
|
-
* facilitator verify/settle calls that a deployed DVM's 402 response and
|
|
180
|
-
* payment parsing are built from. A wire-format change lands here.
|
|
181
|
-
*
|
|
182
|
-
* **On a change here, or an x402 reference-client upgrade, run the conformance
|
|
183
|
-
* check** — `public compatibility guide`. Our own SDK round-trips
|
|
184
|
-
* against itself (the vitest tiers, `money-loop:x402`), so only a client
|
|
185
|
-
* that isn't dvmkit can catch a *mutual* misreading of the spec. The runbook
|
|
186
|
-
* names the out-of-tree v1 and v2 clients plus the funded Base Sepolia key
|
|
187
|
-
* (`public compatibility guide`); the runbook carries a run log of
|
|
188
|
-
* what it last ran against.
|
|
189
|
-
*/
|
|
173
|
+
/** Wire compatibility requires independent v1 and v2 reference-client checks. */
|
|
190
174
|
|
|
191
175
|
/** Current x402 protocol version preferred by dvmkit clients and servers. */
|
|
192
176
|
declare const X402_VERSION = 2;
|
|
@@ -197,13 +181,13 @@ type X402Version = typeof X402_V1_VERSION | typeof X402_VERSION;
|
|
|
197
181
|
/**
|
|
198
182
|
* Chain a caller's x402 wallet reads when it was connected without an explicit
|
|
199
183
|
* `--network`. Single source of truth for the default: the wallet loader, the
|
|
200
|
-
* connect output and the asset resolver all read it from here
|
|
184
|
+
* connect output and the asset resolver all read it from here.
|
|
201
185
|
*/
|
|
202
186
|
declare const X402_DEFAULT_NETWORK = "eip155:8453";
|
|
203
187
|
/**
|
|
204
188
|
* Scheme name of the per-call rail — one signed authorization settling one
|
|
205
189
|
* resource. Shared with the credit funding menu's `x402.schemes` block, which
|
|
206
|
-
* has to name the same string the requirement carries
|
|
190
|
+
* has to name the same string the requirement carries.
|
|
207
191
|
*/
|
|
208
192
|
declare const X402_EXACT_SCHEME = "exact";
|
|
209
193
|
/**
|
|
@@ -212,7 +196,7 @@ declare const X402_EXACT_SCHEME = "exact";
|
|
|
212
196
|
* the caller's requirement selection, and the credit menu's `x402.schemes`
|
|
213
197
|
* block all read it from here — the menu advertises exactly what the challenge
|
|
214
198
|
* will carry, so a rename cannot leave one of them advertising a flavour the
|
|
215
|
-
* others no longer speak
|
|
199
|
+
* others no longer speak.
|
|
216
200
|
*/
|
|
217
201
|
declare const X402_BATCH_SETTLEMENT_SCHEME = "batch-settlement";
|
|
218
202
|
|
|
@@ -344,11 +328,8 @@ interface BuildPaymentRequirementsOpts {
|
|
|
344
328
|
version?: X402Version;
|
|
345
329
|
}
|
|
346
330
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* The resource string is bound into the EIP-3009 `extra` field on signing in
|
|
350
|
-
* the reference clients, so it must match exactly between the 402 response
|
|
351
|
-
* and the retry payload — pass the request URL the client will retry against.
|
|
331
|
+
* V1 carries the resource URL in each requirement; v2 carries it in the outer
|
|
332
|
+
* response. This metadata is not part of the EIP-3009 authorization signature.
|
|
352
333
|
*/
|
|
353
334
|
declare function buildPaymentRequirements(opts: BuildPaymentRequirementsOpts & {
|
|
354
335
|
version: typeof X402_V1_VERSION;
|
|
@@ -364,13 +345,7 @@ declare function buildPaymentRequiredV2(opts: Omit<BuildPaymentRequirementsOpts,
|
|
|
364
345
|
}): PaymentRequiredV2;
|
|
365
346
|
/** Convert the server's legacy body requirements into the parallel v2 declaration. */
|
|
366
347
|
declare function paymentRequiredV2FromV1(requirements: PaymentRequirementsV1[], error?: string): PaymentRequiredV2;
|
|
367
|
-
/**
|
|
368
|
-
* Translate a CAIP-2 chain id (`eip155:8453`) to the x402-spec network slug
|
|
369
|
-
* (`base`). Operators configure DVMs in CAIP-2 because it's the lingua-franca
|
|
370
|
-
* across our other rails; the x402 facilitator API only accepts named slugs.
|
|
371
|
-
* Unknown CAIP-2 ids fall through to the original string so non-EVM rails
|
|
372
|
-
* (Solana mainnet, etc.) can also be plumbed once supported.
|
|
373
|
-
*/
|
|
348
|
+
/** Legacy v1 network slugs; unknown identifiers pass through unchanged. */
|
|
374
349
|
declare function caip2ToX402Network(network: string): string;
|
|
375
350
|
/** Translate a legacy x402 network slug back to its CAIP-2 chain id. */
|
|
376
351
|
declare function x402NetworkToCaip2(network: string): string;
|
|
@@ -378,12 +353,6 @@ declare function x402NetworkToCaip2(network: string): string;
|
|
|
378
353
|
declare function chainIdFromCaip2(network: string): number;
|
|
379
354
|
/** USDC contract address for a network slug (post-translation). */
|
|
380
355
|
declare function usdcContractFor(networkSlug: string): `0x${string}` | undefined;
|
|
381
|
-
/**
|
|
382
|
-
* USDC contract address for a CAIP-2 chain id (e.g. `eip155:8453`). Resolves
|
|
383
|
-
* via {@link caip2ToX402Network} so the one network table is the source of
|
|
384
|
-
* truth — keeps callers that hold the CAIP-2 form (CLI wallet) in lockstep
|
|
385
|
-
* with the SDK 402-builder which already speaks x402 slugs.
|
|
386
|
-
*/
|
|
387
356
|
declare function usdcContractByCaip2(caip2: string): `0x${string}` | undefined;
|
|
388
357
|
/** USDC EIP-712 domain `name` for a network slug. */
|
|
389
358
|
declare function usdcDomainNameFor(networkSlug: string): string | undefined;
|
|
@@ -410,29 +379,24 @@ declare function decodePaymentRequiredHeader(header: string): PaymentRequiredV2;
|
|
|
410
379
|
declare function exactEvmAuthorization(payload: PaymentPayload): ExactEvmPayloadAuthorization;
|
|
411
380
|
/**
|
|
412
381
|
* Encode a `SettleResponse` for the `X-PAYMENT-RESPONSE` header that the
|
|
413
|
-
* server emits on a 2xx after a successful settlement (
|
|
382
|
+
* server emits on a 2xx after a successful settlement (per the
|
|
414
383
|
* x402 spec).
|
|
415
384
|
*/
|
|
416
385
|
declare function encodeSettleResponseHeader(response: SettleResponse): string;
|
|
417
386
|
|
|
418
|
-
/** Identifies which side of the conversation sent a message. */
|
|
419
387
|
type MessageFrom = "requester" | "provider";
|
|
420
388
|
/** Single mppx-shaped MPP challenge carried in `payment-request` messages. */
|
|
421
389
|
type MppxChallenge = Challenge.Challenge;
|
|
422
390
|
/** Structured MPP credential (`{ challenge, payload, source? }`) carried in `payment` messages. */
|
|
423
391
|
type MppxCredential = Credential.Credential;
|
|
424
|
-
/** All supported message types in the conversation protocol. */
|
|
425
392
|
type MessageType = "text" | "prompt" | "response" | "artifact" | "payment-request" | "payment" | "approval" | "cancel" | "complete" | "working" | "progress";
|
|
426
|
-
/** Common fields shared by all message variants. */
|
|
427
393
|
interface MessageBase {
|
|
428
394
|
/** Monotonic sequence number, assigned by provider/server. */
|
|
429
395
|
seq: number;
|
|
430
|
-
/** Which side sent this message. */
|
|
431
396
|
from: MessageFrom;
|
|
432
397
|
/** Unix timestamp (seconds). */
|
|
433
398
|
timestamp: number;
|
|
434
399
|
}
|
|
435
|
-
/** Free-form text message content. */
|
|
436
400
|
interface TextContent {
|
|
437
401
|
text: string;
|
|
438
402
|
}
|
|
@@ -453,15 +417,12 @@ interface ResponseContent {
|
|
|
453
417
|
}
|
|
454
418
|
/** Job output — inline data or a URL reference. */
|
|
455
419
|
interface ArtifactContent {
|
|
456
|
-
/** Human-readable name for the artifact. */
|
|
457
420
|
name?: string;
|
|
458
|
-
/** MIME type of the artifact. */
|
|
459
421
|
mime_type?: string;
|
|
460
422
|
/** Inline data (string or base64 for binary). */
|
|
461
423
|
data?: string;
|
|
462
424
|
/** Encoding of inline data: "utf-8" for text, "base64" for binary. */
|
|
463
425
|
encoding?: "utf-8" | "base64";
|
|
464
|
-
/** URL to fetch the artifact from. */
|
|
465
426
|
url?: string;
|
|
466
427
|
/** Size in bytes (for URL-referenced artifacts). */
|
|
467
428
|
size_bytes?: number;
|
|
@@ -487,7 +448,7 @@ interface PaymentRequestContent {
|
|
|
487
448
|
mints?: string[];
|
|
488
449
|
min_locktime_seconds?: number;
|
|
489
450
|
/**
|
|
490
|
-
* NUT-11 P2PK lock pubkey the requester should lock cashu outputs to
|
|
451
|
+
* NUT-11 P2PK lock pubkey the requester should lock cashu outputs to.
|
|
491
452
|
* When absent, the requester falls back to `/v1/info`'s `cashu.lock_pubkeys[0]`.
|
|
492
453
|
*/
|
|
493
454
|
lock_pubkey?: string;
|
|
@@ -506,7 +467,7 @@ interface PaymentRequestContent {
|
|
|
506
467
|
* caller MUST use when signing `TransferWithAuthorization` for this
|
|
507
468
|
* payment-request. Binds the credential to this job so a payload signed
|
|
508
469
|
* for job A cannot satisfy job B within the pre-settlement window
|
|
509
|
-
*
|
|
470
|
+
* Optional on the wire for backwards-compat — pre-fix DVMs
|
|
510
471
|
* never set it; verify-side gate keys on `pendingX402Nonce !== undefined`.
|
|
511
472
|
*/
|
|
512
473
|
nonce?: string;
|
|
@@ -527,7 +488,7 @@ interface PaymentContent {
|
|
|
527
488
|
mint?: string;
|
|
528
489
|
amount_msats?: number;
|
|
529
490
|
/**
|
|
530
|
-
* UUIDv4 per-call identifier for the agent-wallet path
|
|
491
|
+
* UUIDv4 per-call identifier for the agent-wallet path.
|
|
531
492
|
* Mid-job equivalent of the upfront flow's `X-Cashu-Request-Id` header —
|
|
532
493
|
* threaded into the SDK accumulator's `(dvm_id, request_id)` replay key.
|
|
533
494
|
*/
|
|
@@ -545,7 +506,7 @@ interface PaymentContent {
|
|
|
545
506
|
*/
|
|
546
507
|
tempo_credential?: MppxCredential;
|
|
547
508
|
/**
|
|
548
|
-
* Dev-only: satisfy the ask with no proof at all
|
|
509
|
+
* Dev-only: satisfy the ask with no proof at all. The `/_dev`
|
|
549
510
|
* console has no wallet, so its auto-approve button sends this instead of a
|
|
550
511
|
* rail credential. Admitted only by a `devMode` server, and only on a
|
|
551
512
|
* message that carries none of the proofs above — money on the wire always
|
|
@@ -586,7 +547,6 @@ interface ProgressContent {
|
|
|
586
547
|
/** Optional human-readable hint about what's happening right now. */
|
|
587
548
|
hint?: string;
|
|
588
549
|
}
|
|
589
|
-
/** A single message in a job conversation — discriminated union on `type`. */
|
|
590
550
|
type Message = (MessageBase & {
|
|
591
551
|
type: "text";
|
|
592
552
|
content: TextContent;
|
|
@@ -621,37 +581,30 @@ type Message = (MessageBase & {
|
|
|
621
581
|
type: "progress";
|
|
622
582
|
content: ProgressContent;
|
|
623
583
|
});
|
|
624
|
-
/** Type guard: message is a prompt from the provider. */
|
|
625
584
|
declare function isPromptMessage(msg: Message): msg is MessageBase & {
|
|
626
585
|
type: "prompt";
|
|
627
586
|
content: PromptContent;
|
|
628
587
|
};
|
|
629
|
-
/** Type guard: message contains an artifact. */
|
|
630
588
|
declare function isArtifactMessage(msg: Message): msg is MessageBase & {
|
|
631
589
|
type: "artifact";
|
|
632
590
|
content: ArtifactContent;
|
|
633
591
|
};
|
|
634
|
-
/** Type guard: message is a payment request. */
|
|
635
592
|
declare function isPaymentRequestMessage(msg: Message): msg is MessageBase & {
|
|
636
593
|
type: "payment-request";
|
|
637
594
|
content: PaymentRequestContent;
|
|
638
595
|
};
|
|
639
|
-
/** Type guard: message is a text message. */
|
|
640
596
|
declare function isTextMessage(msg: Message): msg is MessageBase & {
|
|
641
597
|
type: "text";
|
|
642
598
|
content: TextContent;
|
|
643
599
|
};
|
|
644
|
-
/** Type guard: message signals completion. */
|
|
645
600
|
declare function isCompleteMessage(msg: Message): msg is MessageBase & {
|
|
646
601
|
type: "complete";
|
|
647
602
|
content: CompleteContent;
|
|
648
603
|
};
|
|
649
|
-
/** Type guard: message is a cancellation. */
|
|
650
604
|
declare function isCancelMessage(msg: Message): msg is MessageBase & {
|
|
651
605
|
type: "cancel";
|
|
652
606
|
content: CancelContent;
|
|
653
607
|
};
|
|
654
|
-
/** Type guard: message is a working signal. */
|
|
655
608
|
declare function isWorkingMessage(msg: Message): msg is MessageBase & {
|
|
656
609
|
type: "working";
|
|
657
610
|
content: WorkingContent;
|
|
@@ -697,7 +650,6 @@ declare const CLIENT_COMPATIBILITY_HEADERS: {
|
|
|
697
650
|
readonly protocolVersion: "DVM-Protocol-Version";
|
|
698
651
|
readonly capabilities: "DVM-Client-Capabilities";
|
|
699
652
|
};
|
|
700
|
-
/** A parsed SemVer value carried by `DVM-Client`. */
|
|
701
653
|
interface ClientSemVer {
|
|
702
654
|
readonly version: string;
|
|
703
655
|
readonly major: number;
|
|
@@ -705,7 +657,6 @@ interface ClientSemVer {
|
|
|
705
657
|
readonly patch: number;
|
|
706
658
|
readonly prerelease: readonly (string | number)[];
|
|
707
659
|
}
|
|
708
|
-
/** Validated compatibility metadata from one HTTP request. */
|
|
709
660
|
interface ClientCompatibility {
|
|
710
661
|
readonly clientVersion?: ClientSemVer;
|
|
711
662
|
readonly protocolVersion?: number;
|
|
@@ -721,16 +672,13 @@ interface ClientCompatibility {
|
|
|
721
672
|
readonly capabilities: boolean;
|
|
722
673
|
};
|
|
723
674
|
}
|
|
724
|
-
/** Compatibility requirement attached to one endpoint. */
|
|
725
675
|
interface ClientCompatibilityRequirement {
|
|
726
676
|
/** Additive capability tokens the caller must advertise. */
|
|
727
677
|
readonly requiredCapabilities?: readonly string[];
|
|
728
678
|
/** Lowest `dvm` SemVer accepted by this endpoint. */
|
|
729
679
|
readonly minimumClientVersion?: string;
|
|
730
680
|
}
|
|
731
|
-
/** Factory for compatibility middleware attached to one custom route. */
|
|
732
681
|
type ClientCompatibilityGate = (requirement: ClientCompatibilityRequirement) => MiddlewareHandler<ClientCompatibilityEnv>;
|
|
733
|
-
/** Hono variables installed by the SDK compatibility middleware. */
|
|
734
682
|
interface ClientCompatibilityEnv {
|
|
735
683
|
Variables: {
|
|
736
684
|
clientCompatibility?: ClientCompatibility;
|
|
@@ -748,62 +696,35 @@ declare function parseClientCapabilities(value: string): readonly string[] | und
|
|
|
748
696
|
declare function clientCompatibilityAttributes(compatibility: ClientCompatibility): Record<string, unknown>;
|
|
749
697
|
/** Parse request metadata and advertise the server protocol version on every response. */
|
|
750
698
|
declare function clientCompatibilityMiddleware(): MiddlewareHandler<ClientCompatibilityEnv>;
|
|
751
|
-
/** Build pre-handler enforcement middleware for one endpoint declaration. */
|
|
752
699
|
declare const requireClientCompatibility: ClientCompatibilityGate;
|
|
753
|
-
/** Render the normal structured HTTP 426 compatibility error. */
|
|
754
700
|
declare function clientUpgradeRequired(c: Context, compatibility: ClientCompatibility, requirement: ClientCompatibilityRequirement): Response;
|
|
755
701
|
|
|
756
|
-
/**
|
|
757
|
-
* Canonical JSON serialisation used for secp256k1+BIP-340 Schnorr request
|
|
758
|
-
* signing in cast (internal-review).
|
|
759
|
-
*
|
|
760
|
-
* Single source of truth for both the cast DVM's verifier (internal-review) and the
|
|
761
|
-
* client signing helper (internal-review). The same function on both sides guarantees
|
|
762
|
-
* that what the client signs is byte-identical to what the server hashes.
|
|
763
|
-
*
|
|
764
|
-
* Algorithm: recursively sort object keys lexicographically (UTF-16 code-unit
|
|
765
|
-
* order — JavaScript's default), then JSON.stringify with no whitespace.
|
|
766
|
-
* Arrays preserve order. Numbers, strings, booleans, null pass through as
|
|
767
|
-
* `JSON.stringify` formats them. Values of `undefined` or functions are
|
|
768
|
-
* forbidden (we throw rather than silently drop, since both sides must agree).
|
|
769
|
-
*/
|
|
770
|
-
/** Any JSON-serialisable value accepted by `canonicalize`. */
|
|
771
702
|
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
772
703
|
[key: string]: JsonValue;
|
|
773
704
|
};
|
|
774
705
|
/**
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
* `JSON.stringify`), so callers don't need to explicitly delete optional fields
|
|
779
|
-
* that weren't set. Throws `TypeError` for unsupported scalar types (functions,
|
|
780
|
-
* symbols, non-finite numbers). The caller is responsible for stripping fields
|
|
781
|
-
* that shouldn't appear in the signed payload (e.g. `signature` itself).
|
|
706
|
+
* Object keys sort recursively in UTF-16 code-unit order; arrays preserve order.
|
|
707
|
+
* Undefined object properties are omitted. Unsupported scalars, including
|
|
708
|
+
* non-finite numbers, throw TypeError. Callers strip unsigned fields themselves.
|
|
782
709
|
*/
|
|
783
710
|
declare function canonicalize(value: JsonValue): string;
|
|
784
711
|
/**
|
|
785
|
-
*
|
|
786
|
-
* signature over. Both the cast client signer (`signAddEpisode`) and the
|
|
787
|
-
* server verifier (`verifyCanonicalSignedRequest`) call this — sharing the function
|
|
788
|
-
* guarantees byte-identical input on both sides.
|
|
712
|
+
* UTF-8 encoding of the canonical JSON form.
|
|
789
713
|
*/
|
|
790
714
|
declare function canonicaliseForSigning(value: JsonValue): Uint8Array;
|
|
791
715
|
|
|
792
|
-
/** Terminal outcome a receipt attests. Mirrors the SDK's terminal job statuses. */
|
|
793
716
|
type ReceiptOutcome = "completed" | "failed" | "cancelled";
|
|
794
|
-
/** What the caller paid for the job, as persisted on the job record. */
|
|
795
717
|
interface ReceiptPayment {
|
|
796
718
|
/** Total credited millisats. `0` on free jobs — they still get receipts. */
|
|
797
719
|
msats: number;
|
|
798
720
|
/**
|
|
799
721
|
* Rail of the most recent successful credit; `null` when nothing was paid.
|
|
800
722
|
* `lightning` appears on a draw against a credit the caller funded by
|
|
801
|
-
* invoice
|
|
723
|
+
* invoice — it is a funding rail, never an attached-proof one.
|
|
802
724
|
*/
|
|
803
725
|
rail: "cashu" | "x402" | "tempo" | "lightning" | null;
|
|
804
726
|
/** Rail-native upfront amount (sats / USDC microunits), `null` when absent. */
|
|
805
727
|
native_amount: number | null;
|
|
806
|
-
/** Native asset tag paired with `native_amount`. */
|
|
807
728
|
native_asset: string | null;
|
|
808
729
|
/** Settlement reference: EVM tx hash (x402), mppx challenge id, or the Cashu request id. */
|
|
809
730
|
tx_hash: string | null;
|
|
@@ -811,8 +732,8 @@ interface ReceiptPayment {
|
|
|
811
732
|
mint: string | null;
|
|
812
733
|
}
|
|
813
734
|
/**
|
|
814
|
-
* Credit-draw countersignature block
|
|
815
|
-
*
|
|
735
|
+
* Credit-draw countersignature block. Present on every job whose payment funded
|
|
736
|
+
* or drew the credit
|
|
816
737
|
* ledger — including implicit N=1 per-job payments, which fund and draw a
|
|
817
738
|
* single-use credit in the same request. A caller holding a DVM-signed
|
|
818
739
|
* balance trajectory plus its own caller-signed draw intents can prove
|
|
@@ -820,23 +741,21 @@ interface ReceiptPayment {
|
|
|
820
741
|
* because the countersignature rides the receipt.
|
|
821
742
|
*
|
|
822
743
|
* Absence is signature-compatible: `canonicalize` omits absent keys, so a
|
|
823
|
-
* ledger-less (or free) job's receipt signs identically to a
|
|
744
|
+
* ledger-less (or free) job's receipt signs identically to a legacy
|
|
824
745
|
* receipt.
|
|
825
746
|
*/
|
|
826
747
|
interface ReceiptCredit {
|
|
827
|
-
/** Credit the draw was taken against. */
|
|
828
748
|
credit_id: string;
|
|
829
749
|
/** This draw's identifier — matches the caller-signed draw intent. */
|
|
830
750
|
draw_id: string;
|
|
831
751
|
/** Amount drawn, in the credit's denomination. */
|
|
832
752
|
amount: number;
|
|
833
|
-
/** Credit balance after this draw. */
|
|
834
753
|
balance_after: number;
|
|
835
754
|
/** Per-credit monotonic draw counter. */
|
|
836
755
|
ledger_seq: number;
|
|
837
756
|
}
|
|
838
757
|
/**
|
|
839
|
-
* A DVM-signed proof of what a job did and what it cost
|
|
758
|
+
* A DVM-signed proof of what a job did and what it cost.
|
|
840
759
|
*
|
|
841
760
|
* Signed once at the terminal transition by the per-DVM receipt key, whose
|
|
842
761
|
* authority chains to the builder identity key through the deploy-time
|
|
@@ -845,18 +764,22 @@ interface ReceiptCredit {
|
|
|
845
764
|
* and on every later re-read — the bytes are persisted, not recomputed.
|
|
846
765
|
*/
|
|
847
766
|
interface JobReceipt {
|
|
848
|
-
/** Receipt schema version. Currently always `1`. */
|
|
849
767
|
v: 1;
|
|
850
|
-
/** The job this receipt attests. */
|
|
851
768
|
job_id: string;
|
|
852
769
|
/** Immutable DVM identity bound into the builder attestation. */
|
|
853
770
|
dvm_id: string;
|
|
854
771
|
/** DVM slug, as bound in the builder attestation. */
|
|
855
772
|
dvm: string;
|
|
856
|
-
/** Capability the job dispatched to. */
|
|
857
773
|
capability: string;
|
|
858
|
-
/** Terminal outcome. */
|
|
859
774
|
outcome: ReceiptOutcome;
|
|
775
|
+
/** Signed Unix millisecond lifecycle times; absent on legacy receipts. */
|
|
776
|
+
timing?: {
|
|
777
|
+
accepted_at_ms: number;
|
|
778
|
+
terminal_at_ms: number;
|
|
779
|
+
caller_wait_ms: number;
|
|
780
|
+
};
|
|
781
|
+
/** SDK-derived attribution on failure/cancellation; absent on completion and legacy receipts. */
|
|
782
|
+
ended_by?: "caller" | "provider";
|
|
860
783
|
/** Terminal reason on `failed`/`cancelled` (e.g. `worker_died_mid_job`); `null` on success. */
|
|
861
784
|
reason: string | null;
|
|
862
785
|
/** Per-DVM monotonic terminal counter. Gaps expose receipt suppression. */
|
|
@@ -865,7 +788,6 @@ interface JobReceipt {
|
|
|
865
788
|
issued_at: number;
|
|
866
789
|
/** Caller's x-only pubkey when signed-request auth was used; `null` otherwise. */
|
|
867
790
|
requester_pubkey: string | null;
|
|
868
|
-
/** What the caller paid. */
|
|
869
791
|
paid: ReceiptPayment;
|
|
870
792
|
/**
|
|
871
793
|
* {@link computeResultHash} over the delivered result; `null` when nothing
|
|
@@ -878,7 +800,7 @@ interface JobReceipt {
|
|
|
878
800
|
/** BIP-340 Schnorr signature over the canonical receipt minus `signature` (128 hex chars). */
|
|
879
801
|
signature: string;
|
|
880
802
|
/**
|
|
881
|
-
* Credit-draw countersignature
|
|
803
|
+
* Credit-draw countersignature — present when the job's payment
|
|
882
804
|
* funded/drew the credit ledger. See {@link ReceiptCredit}. `balance_after`
|
|
883
805
|
* reflects the terminal resolution: the recorded draw trajectory on a
|
|
884
806
|
* settled (completed) job, the restored available balance on a released
|
|
@@ -886,12 +808,10 @@ interface JobReceipt {
|
|
|
886
808
|
*/
|
|
887
809
|
credit?: ReceiptCredit;
|
|
888
810
|
}
|
|
889
|
-
/** A receipt before signing — every field but the signature itself. */
|
|
890
811
|
type UnsignedJobReceipt = Omit<JobReceipt, "signature">;
|
|
891
|
-
/** Reclaim lifecycle event a {@link DrainReceipt} attests (internal-review, spec §5). */
|
|
892
812
|
type DrainReceiptEvent = "requested" | "parked" | "picked_up" | "sent" | "released";
|
|
893
813
|
/**
|
|
894
|
-
* A DVM-signed proof of one reclaim event
|
|
814
|
+
* A DVM-signed proof of one reclaim event. One receipt per
|
|
895
815
|
* lifecycle transition — `requested` (the balance debited into the drain
|
|
896
816
|
* liability), `parked` (Cashu notes locked to the caller's refund key,
|
|
897
817
|
* parked for pickup), `picked_up` (the caller collected them), `sent` (a
|
|
@@ -907,24 +827,20 @@ type DrainReceiptEvent = "requested" | "parked" | "picked_up" | "sent" | "releas
|
|
|
907
827
|
* same canonicalisation, so one attestation chain covers both.
|
|
908
828
|
*/
|
|
909
829
|
interface DrainReceipt {
|
|
910
|
-
/** Receipt schema version. Currently always `1`. */
|
|
911
830
|
v: 1;
|
|
912
831
|
/** Discriminator separating this from a `JobReceipt` in mixed stores. */
|
|
913
832
|
kind: "credit_drain";
|
|
914
|
-
/** Credit the drain was taken against. */
|
|
915
833
|
credit_id: string;
|
|
916
834
|
/** This drain's identifier — matches the caller-signed drain request. */
|
|
917
835
|
drain_id: string;
|
|
918
|
-
/** The lifecycle event this receipt attests. */
|
|
919
836
|
event: DrainReceiptEvent;
|
|
920
837
|
/** Payout rail: `cashu` | `x402` | `tempo`. */
|
|
921
838
|
method: string;
|
|
922
839
|
/** Amount reclaimed, micro-units of the credit's currency. */
|
|
923
840
|
amount_micro: number;
|
|
924
841
|
/**
|
|
925
|
-
* Credit balance
|
|
926
|
-
* pending job holds at request time
|
|
927
|
-
* Every event's receipt attests this same figure.
|
|
842
|
+
* Credit balance left after the drain debit, in micro-units. It includes
|
|
843
|
+
* pending job holds at request time and is fixed across this drain's event receipts.
|
|
928
844
|
*/
|
|
929
845
|
balance_after_micro: number;
|
|
930
846
|
/** Per-credit monotonic sequence the drain consumed, shared with draws. */
|
|
@@ -942,19 +858,16 @@ interface DrainReceipt {
|
|
|
942
858
|
/** BIP-340 Schnorr signature over the canonical receipt minus `signature`. */
|
|
943
859
|
signature: string;
|
|
944
860
|
}
|
|
945
|
-
/** A drain receipt before signing — every field but the signature itself. */
|
|
946
861
|
type UnsignedDrainReceipt = Omit<DrainReceipt, "signature">;
|
|
947
862
|
/**
|
|
948
|
-
* A DVM-signed proof that one payment funded a prepaid credit
|
|
863
|
+
* A DVM-signed proof that one payment funded a prepaid credit.
|
|
949
864
|
* The balance and sequence are the values fixed when the funding committed;
|
|
950
865
|
* retries return this original artifact even when the credit moved later.
|
|
951
866
|
*/
|
|
952
867
|
interface FundingReceipt {
|
|
953
|
-
/** Receipt schema version. Currently always `1`. */
|
|
954
868
|
v: 1;
|
|
955
869
|
/** Discriminator separating funding evidence from job and drain receipts. */
|
|
956
870
|
kind: "credit_funding";
|
|
957
|
-
/** Credit that received the value. */
|
|
958
871
|
credit_id: string;
|
|
959
872
|
/** Caller fund id, or the attached rail payment id for fund-and-draw. */
|
|
960
873
|
fund_id: string;
|
|
@@ -972,12 +885,10 @@ interface FundingReceipt {
|
|
|
972
885
|
dvm_id: string;
|
|
973
886
|
/** DVM slug, as bound in the builder attestation. */
|
|
974
887
|
dvm: string;
|
|
975
|
-
/** x-only secp256k1 pubkey that signed this receipt. */
|
|
976
888
|
receipt_pubkey: string;
|
|
977
889
|
/** BIP-340 Schnorr signature over the canonical receipt minus `signature`. */
|
|
978
890
|
signature: string;
|
|
979
891
|
}
|
|
980
|
-
/** A funding receipt before signing. */
|
|
981
892
|
type UnsignedFundingReceipt = Omit<FundingReceipt, "signature">;
|
|
982
893
|
/**
|
|
983
894
|
* Hash the delivered result so a receipt binds to *what* was returned, not
|
|
@@ -997,13 +908,6 @@ type UnsignedFundingReceipt = Omit<FundingReceipt, "signature">;
|
|
|
997
908
|
* issuer does the same (`resultHashFor` in `sdk/server/receipt-issuer.ts`).
|
|
998
909
|
*/
|
|
999
910
|
declare function computeResultHash(summary: string | undefined, artifactContents: JsonValue[]): string;
|
|
1000
|
-
/**
|
|
1001
|
-
* Sign an unsigned receipt with the DVM's receipt secret, returning the
|
|
1002
|
-
* complete receipt. Signs `canonicaliseForSigning(receipt)` — the same
|
|
1003
|
-
* sorted-key canonical JSON the caller-auth envelope and the builder
|
|
1004
|
-
* attestation use, so {@link verifyReceipt} is symmetric with
|
|
1005
|
-
* `verifyCanonicalSignedRequest`. There is no second canonicalisation.
|
|
1006
|
-
*/
|
|
1007
911
|
declare function signReceipt(unsigned: UnsignedJobReceipt, secretHex: string): JobReceipt;
|
|
1008
912
|
/**
|
|
1009
913
|
* Verify a receipt's self-consistency: the BIP-340 signature over the
|
|
@@ -1042,9 +946,7 @@ declare function isSignedJobReceipt(value: unknown): value is JobReceipt;
|
|
|
1042
946
|
declare function signDrainReceipt(unsigned: UnsignedDrainReceipt, secretHex: string): DrainReceipt;
|
|
1043
947
|
/** Sign an unsigned funding receipt using the common receipt canonicalisation. */
|
|
1044
948
|
declare function signFundingReceipt(unsigned: UnsignedFundingReceipt, secretHex: string): FundingReceipt;
|
|
1045
|
-
/** Structural guard for a funding receipt received over the wire or from disk. */
|
|
1046
949
|
declare function isFundingReceipt(value: unknown): value is FundingReceipt;
|
|
1047
|
-
/** Verify a funding receipt's inner BIP-340 signature. */
|
|
1048
950
|
declare function verifyFundingReceipt(receipt: FundingReceipt): boolean;
|
|
1049
951
|
/**
|
|
1050
952
|
* Structural guard on a drain receipt from outside this process — off a
|
|
@@ -1061,33 +963,16 @@ declare function verifyFundingReceipt(receipt: FundingReceipt): boolean;
|
|
|
1061
963
|
declare function isDrainReceipt(value: unknown): value is DrainReceipt;
|
|
1062
964
|
/**
|
|
1063
965
|
* Verify a drain receipt's self-consistency, mirroring {@link verifyReceipt}
|
|
1064
|
-
*
|
|
966
|
+
* the same provenance caveat applies: check `receipt_pubkey` against the
|
|
1065
967
|
* DVM's attested `receipt_pubkey` for the outer link.
|
|
1066
968
|
*/
|
|
1067
969
|
declare function verifyDrainReceipt(receipt: DrainReceipt): boolean;
|
|
1068
970
|
|
|
1069
971
|
/**
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
* reclaim time: fund 1,151 sats, draw 1.2% of the credit's value, reclaim
|
|
1075
|
-
* 98.8% of 1,151 sats. That obligation is only expressible if each funding
|
|
1076
|
-
* event's `{sats_funded, credited_micro}` pair survives — the ledger's pooled
|
|
1077
|
-
* `credits.msats_remaining` blends every funding into one ratio, which answers
|
|
1078
|
-
* a different question (what share of the credit's rail value does *this draw*
|
|
1079
|
-
* consume) and cannot say what a top-up taken at a different rate is worth.
|
|
1080
|
-
*
|
|
1081
|
-
* Everything here is pure integer arithmetic over that pair, shared by the
|
|
1082
|
-
* Postgres and in-memory ledgers so the two cannot drift — the arrangement
|
|
1083
|
-
* `allocateDrawValue` already holds for the pooled basis.
|
|
1084
|
-
*
|
|
1085
|
-
* Since internal-review these lots are also what a *settling* draw is priced at on
|
|
1086
|
-
* these rails, not only a reclaim: `inKindDrawMsats` turns the depletion a
|
|
1087
|
-
* settle already performs into the draw's booked rail value, so one credit has
|
|
1088
|
-
* one answer to "what were these sats" and the pooled remainder stops being a
|
|
1089
|
-
* second authority: settled draw sats plus reclaim sats must equal the sats
|
|
1090
|
-
* that credit's funding lots received.
|
|
972
|
+
* Cashu and Lightning credits owe unused sats back at each deposit's funding
|
|
973
|
+
* rate. Keep the sats/micro basis per funding lot: a pooled ratio loses the
|
|
974
|
+
* basis of top-ups made at different rates. Settled draws and reclaims share
|
|
975
|
+
* these lots so their total equals the sats funded.
|
|
1091
976
|
*/
|
|
1092
977
|
/**
|
|
1093
978
|
* The rails whose credits carry a Bitcoin deposit this ledger owes back in
|
|
@@ -1100,9 +985,7 @@ declare function verifyDrainReceipt(receipt: DrainReceipt): boolean;
|
|
|
1100
985
|
* {@link FundingMethod} must be a decision here rather than a default.
|
|
1101
986
|
*/
|
|
1102
987
|
declare const NON_CHANNEL_BITCOIN_RAILS: readonly ["cashu", "lightning"];
|
|
1103
|
-
/** A rail whose unused credit is a sats deposit the provider owes back. */
|
|
1104
988
|
type NonChannelBitcoinRail = (typeof NON_CHANNEL_BITCOIN_RAILS)[number];
|
|
1105
|
-
/** True when `rail` funds a credit with sats the provider holds and owes back. */
|
|
1106
989
|
declare function isNonChannelBitcoinRail(rail: string | null | undefined): rail is NonChannelBitcoinRail;
|
|
1107
990
|
/**
|
|
1108
991
|
* One funding event's deposit: the sats that arrived and the credit micro they
|
|
@@ -1110,7 +993,7 @@ declare function isNonChannelBitcoinRail(rail: string | null | undefined): rail
|
|
|
1110
993
|
*
|
|
1111
994
|
* `remainingMicro` is depleted FIFO by settled draws and by reclaims, so the
|
|
1112
995
|
* sum of a credit's lots tracks `credits.balance_micro` exactly — with one
|
|
1113
|
-
* deliberate exception: an expiry release
|
|
996
|
+
* deliberate exception: an expiry release zeroes the balance and
|
|
1114
997
|
* leaves the lots standing, because a revival restores that balance and its
|
|
1115
998
|
* in-kind basis has to still be there. A released credit is excluded from the
|
|
1116
999
|
* sweep floor by `BITCOIN_CREDIT_PREDICATE`'s own `balance_micro > 0`, so the
|
|
@@ -1124,24 +1007,19 @@ interface FundingLot {
|
|
|
1124
1007
|
satsFunded: number;
|
|
1125
1008
|
/** Credit micro this funding bought — the lot's denominator, never depleted. */
|
|
1126
1009
|
creditedMicro: number;
|
|
1127
|
-
/** Credit micro of this lot not yet drawn or reclaimed. */
|
|
1128
1010
|
remainingMicro: number;
|
|
1129
1011
|
/** Rail settlement reference, provenance only. */
|
|
1130
1012
|
fundingRef: string | null;
|
|
1131
1013
|
createdAt: number;
|
|
1132
1014
|
}
|
|
1133
|
-
/** One lot's share of a depletion, and what that share is worth in kind. */
|
|
1134
1015
|
interface LotDebit {
|
|
1135
1016
|
lotId: string;
|
|
1136
|
-
/** Credit micro taken out of this lot. */
|
|
1137
1017
|
micro: number;
|
|
1138
|
-
/**
|
|
1018
|
+
/** Decrease in this lot's floored sats obligation after taking the micro debit. */
|
|
1139
1019
|
sats: number;
|
|
1140
1020
|
}
|
|
1141
|
-
/** What a FIFO depletion took, and what it could not cover. */
|
|
1142
1021
|
interface LotDepletion {
|
|
1143
1022
|
debits: LotDebit[];
|
|
1144
|
-
/** In-kind value of everything taken — the reclaim obligation. */
|
|
1145
1023
|
satsOwed: number;
|
|
1146
1024
|
/** Micro the lots could not cover. Non-zero means the credit's lots are short. */
|
|
1147
1025
|
uncoveredMicro: number;
|
|
@@ -1149,32 +1027,15 @@ interface LotDepletion {
|
|
|
1149
1027
|
backedMicro: number;
|
|
1150
1028
|
}
|
|
1151
1029
|
/**
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* rate is reclaimed at *that* rate once the earlier deposit is spent. A
|
|
1157
|
-
* fraction of a satoshi cannot be handed back, so the conversion floors and
|
|
1158
|
-
* the dust stays with the provider rather than being rounded into a payout the
|
|
1159
|
-
* deposit does not cover.
|
|
1160
|
-
*
|
|
1161
|
-
* A slice is priced as the **decrement in the lot's own obligation** —
|
|
1162
|
-
* `lotSats(before) - lotSats(after)` — rather than by flooring the slice on
|
|
1163
|
-
* its own. The two agree whenever a lot is taken whole, which is every
|
|
1164
|
-
* ordinary reclaim; they diverge once a lot is drawn down in pieces, and there
|
|
1165
|
-
* the independent floor loses up to a satoshi *per piece*, permanently. That
|
|
1166
|
-
* matters since internal-review, where each settling draw is priced through here: the
|
|
1167
|
-
* slices have to telescope, or a lot spent over many jobs pays out less than
|
|
1168
|
-
* it took in and the residue is exactly the unattributable one this was meant
|
|
1169
|
-
* to remove. It also makes {@link lotOwedSats} exact rather than conservative
|
|
1170
|
-
* — the remaining obligation is precisely what future depletions will pay.
|
|
1171
|
-
*
|
|
1172
|
-
* `lots` must already be in FIFO order ({@link fifoOrder}).
|
|
1030
|
+
* Deplete lots in FIFO order, supplied by fifoOrder. Price each debit as
|
|
1031
|
+
* lotSats(before) - lotSats(after), not the independently floored slice: small
|
|
1032
|
+
* draws then telescope to the full deposit instead of losing up to one sat
|
|
1033
|
+
* per draw. Fractional remaining sats stay with the provider.
|
|
1173
1034
|
*/
|
|
1174
1035
|
declare function depleteLots(lots: FundingLot[], amountMicro: number): LotDepletion;
|
|
1175
1036
|
/**
|
|
1176
1037
|
* What a settling draw's fiat debit was worth in kind, in millisatoshis
|
|
1177
|
-
*
|
|
1038
|
+
* `null` where the lots cannot price it.
|
|
1178
1039
|
*
|
|
1179
1040
|
* This is the single sats authority for a non-channel Bitcoin credit. The
|
|
1180
1041
|
* settle already depletes the lots the draw consumed, so the figure costs no
|
|
@@ -1196,25 +1057,9 @@ declare function inKindDrawMsats(depletion: LotDepletion, amountMicro: number):
|
|
|
1196
1057
|
*/
|
|
1197
1058
|
declare function lotOwedSats(lots: FundingLot[]): number;
|
|
1198
1059
|
/**
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1202
|
-
* Delivering a refund costs money whichever way it is fulfilled: an
|
|
1203
|
-
* accumulator swap pays the mint's per-input fee, and a just-in-time mint pays
|
|
1204
|
-
* the mint's quote plus whatever the Lightning hop routes for. internal-review puts
|
|
1205
|
-
* that cost on the caller — but charging *actual* fees would mean re-publishing
|
|
1206
|
-
* a smaller figure after the reclaim was already promised, and internal-review fixed
|
|
1207
|
-
* `owed_sats` at request time precisely so it cannot move between the promise
|
|
1208
|
-
* and the payout. A flat reserve keeps both: the caller is quoted one final
|
|
1209
|
-
* number, net, and the provider spends the reserve on delivery.
|
|
1210
|
-
*
|
|
1211
|
-
* It is deliberately a constant rather than a policy knob. The figure only has
|
|
1212
|
-
* to cover a small mint fee and one Lightning hop — the live hub's last hop
|
|
1213
|
-
* measured 1.1 sats on a 2,000-sat payment (internal-review) — so there is headroom
|
|
1214
|
-
* without a number anyone has to tune, and a knob nobody sets is a knob that
|
|
1215
|
-
* drifts. What the reserve does not cover on some future expensive route is
|
|
1216
|
-
* absorbed, and what it over-covers stays with the provider as dust, the same
|
|
1217
|
-
* rule the per-lot flooring already follows.
|
|
1060
|
+
* Refund delivery uses a fixed eight-sat reserve so owed_sats cannot shrink
|
|
1061
|
+
* after it is promised. Extra delivery cost is absorbed by the provider;
|
|
1062
|
+
* unused reserve stays with the provider, like fractional-sat dust.
|
|
1218
1063
|
*/
|
|
1219
1064
|
declare const DRAIN_DELIVERY_RESERVE_SATS = 8;
|
|
1220
1065
|
/**
|
|
@@ -1237,59 +1082,50 @@ declare function netOwedSats(grossSats: number): number;
|
|
|
1237
1082
|
*/
|
|
1238
1083
|
declare function fifoOrder(left: FundingLot, right: FundingLot): number;
|
|
1239
1084
|
/**
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
* Both failure modes are real and neither is the caller's fault: a credit's
|
|
1244
|
-
* lots can be short (a coverage hole the boot backfill is meant to close), and
|
|
1245
|
-
* a credit funded before internal-review recorded a rail basis at all has lots whose
|
|
1246
|
-
* `satsFunded` is 0 — pricing *that* in kind would answer "we owe you nothing"
|
|
1247
|
-
* for a deposit we plainly hold. Mixed coverage is treated as unbacked too:
|
|
1248
|
-
* blending a lot rate with a live rate produces a figure neither basis
|
|
1249
|
-
* supports.
|
|
1085
|
+
* Missing lot coverage or a zero legacy sats basis requires pooled pricing.
|
|
1086
|
+
* Mixed backed/unbacked lots also fall back: combining lot and live rates
|
|
1087
|
+
* would claim a value neither basis supports.
|
|
1250
1088
|
*/
|
|
1251
1089
|
declare function isInKindDepletion(depletion: LotDepletion, amountMicro: number): boolean;
|
|
1252
1090
|
|
|
1253
1091
|
/** The rails a payout lands on. Lightning never pays out: credits fund straight into the receive wallet. */
|
|
1254
1092
|
type PayoutRail = "cashu" | "x402" | "tempo";
|
|
1255
1093
|
/**
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
* Tempo channel's scheduled / manual settlement to the recipient.
|
|
1094
|
+
* Transfers aggregate payments: a Cashu melt, x402 batch to pay_to, or Tempo recipient
|
|
1095
|
+
* settlement/close.
|
|
1259
1096
|
*/
|
|
1260
1097
|
type PayoutKind = "melt" | "batch" | "close" | "settle";
|
|
1261
|
-
/** Which repair verb completed a stuck movement. Only `tempo_close_reconciled` is produced here today. */
|
|
1262
1098
|
type PayoutRepairKind = "x402_settlement_reconciled" | "lightning_invoice_reconciled" | "tempo_close_reconciled";
|
|
1263
1099
|
/**
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
* on `(dvmId, payoutId)`, so the durable retry loop can redeliver freely; a
|
|
1267
|
-
* `repaired` redelivery of an id that already landed upgrades that row rather
|
|
1268
|
-
* than adding a second.
|
|
1100
|
+
* One landed movement into builder custody. Platform retries deduplicate by (dvmId,
|
|
1101
|
+
* payoutId); repaired redelivery upgrades that movement rather than adding another.
|
|
1269
1102
|
*/
|
|
1270
1103
|
interface PayoutReportPayload {
|
|
1271
|
-
/**
|
|
1104
|
+
/**
|
|
1105
|
+
* Platform record ID; must match the bearer token’s DVM.
|
|
1106
|
+
*/
|
|
1272
1107
|
dvmId: string;
|
|
1273
1108
|
/** Required. The DVM's own id for the movement — `cashu:melt:<quote>`, `x402:batch:<uuid>`, `tempo:<tx>`. */
|
|
1274
1109
|
payoutId: string;
|
|
1275
1110
|
rail: PayoutRail;
|
|
1276
1111
|
kind: PayoutKind;
|
|
1277
|
-
/**
|
|
1112
|
+
/**
|
|
1113
|
+
* Positive rail-native atomic units: sats or USDC micro.
|
|
1114
|
+
*/
|
|
1278
1115
|
nativeAmount: number;
|
|
1279
|
-
/** Required. `sats` for cashu, `usdc` for the stablecoin rails. */
|
|
1280
1116
|
nativeAsset: "sats" | "usdc";
|
|
1281
1117
|
/** Where it landed; `null` for a melt, whose Lightning destination the platform never records. */
|
|
1282
1118
|
recipient: string | null;
|
|
1283
|
-
/**
|
|
1119
|
+
/**
|
|
1120
|
+
* Descriptive mechanism references, never an arithmetic basis.
|
|
1121
|
+
*/
|
|
1284
1122
|
refs: Record<string, unknown>;
|
|
1285
1123
|
/** Required. When the money landed (epoch ms). */
|
|
1286
1124
|
landedAt: number;
|
|
1287
|
-
/** Set when a repair verb completed this movement. */
|
|
1288
1125
|
repaired?: boolean;
|
|
1289
1126
|
/** Required iff `repaired`. */
|
|
1290
1127
|
repairKind?: PayoutRepairKind;
|
|
1291
1128
|
}
|
|
1292
|
-
/** A stuck movement inside a pending snapshot. */
|
|
1293
1129
|
interface PayoutWedge {
|
|
1294
1130
|
id: string;
|
|
1295
1131
|
/**
|
|
@@ -1309,17 +1145,14 @@ interface PayoutWedge {
|
|
|
1309
1145
|
/** Rail-native amount held up, when known. */
|
|
1310
1146
|
native?: number;
|
|
1311
1147
|
}
|
|
1312
|
-
/** One pool inside a rail's pending figure — a mint, a batch, a channel. */
|
|
1313
1148
|
interface PayoutPool {
|
|
1314
1149
|
id: string;
|
|
1315
1150
|
native: number;
|
|
1316
1151
|
label?: string;
|
|
1317
1152
|
}
|
|
1318
1153
|
/**
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
* replaced on each report. Best-effort: a lost one is superseded by the next
|
|
1322
|
-
* tick, so it rides no durable queue.
|
|
1154
|
+
* A replaceable snapshot of one rail’s pending builder funds. Best-effort delivery uses no
|
|
1155
|
+
* durable queue; a later tick supersedes a lost report.
|
|
1323
1156
|
*/
|
|
1324
1157
|
interface PayoutPendingPayload {
|
|
1325
1158
|
dvmId: string;
|
|
@@ -1327,14 +1160,12 @@ interface PayoutPendingPayload {
|
|
|
1327
1160
|
/** Everything on this rail still moving toward the builder, in atomic units. */
|
|
1328
1161
|
poolNative: number;
|
|
1329
1162
|
nativeAsset: "sats" | "usdc";
|
|
1330
|
-
/** How many pools the figure is made of. */
|
|
1331
1163
|
items: number;
|
|
1332
1164
|
pools: PayoutPool[];
|
|
1333
1165
|
wedged: PayoutWedge[];
|
|
1334
1166
|
/** This DVM's clock when the snapshot was taken (epoch ms). */
|
|
1335
1167
|
snapshotAt: number;
|
|
1336
1168
|
}
|
|
1337
|
-
/** The delivery seam — implemented by `RevenueReporter`, stubbed in tests. */
|
|
1338
1169
|
interface PayoutTransport {
|
|
1339
1170
|
/**
|
|
1340
1171
|
* Queue a payout through the caller's open transaction, so the row and the
|
|
@@ -1351,19 +1182,13 @@ interface PayoutTransport {
|
|
|
1351
1182
|
drainPending?(): Promise<void>;
|
|
1352
1183
|
}
|
|
1353
1184
|
/**
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
* back with it — the drain report's own discipline (internal-review): a call the
|
|
1357
|
-
* caller has to repeat costs a round trip, where a landed movement with no
|
|
1358
|
-
* row understates paid-out forever. `committed` runs once that transaction
|
|
1359
|
-
* has committed, for the delivery and the snapshot that must not read
|
|
1360
|
-
* uncommitted state.
|
|
1185
|
+
* enqueue joins the money transaction and must throw on failure so its fact rolls back.
|
|
1186
|
+
* committed runs after commit, when delivery and snapshots can safely read the new state.
|
|
1361
1187
|
*/
|
|
1362
1188
|
interface TransactionalPayoutHook<E> {
|
|
1363
1189
|
enqueue(event: E, tx: RevenueReporterQuerier): Promise<void>;
|
|
1364
1190
|
committed(): void;
|
|
1365
1191
|
}
|
|
1366
|
-
/** What `/admin/cashu/mark-melted` knows once the rows are marked. */
|
|
1367
1192
|
interface CashuMeltCompleted {
|
|
1368
1193
|
rows: {
|
|
1369
1194
|
id: string;
|
|
@@ -1373,7 +1198,6 @@ interface CashuMeltCompleted {
|
|
|
1373
1198
|
meltQuoteId: string;
|
|
1374
1199
|
paymentPreimage: string;
|
|
1375
1200
|
}
|
|
1376
|
-
/** What mppx reports after a Tempo channel settlement or close confirmed on chain. */
|
|
1377
1201
|
interface TempoSessionSettled {
|
|
1378
1202
|
txHash: string;
|
|
1379
1203
|
channelId: string;
|
|
@@ -1383,27 +1207,23 @@ interface TempoSessionSettled {
|
|
|
1383
1207
|
/** Newly settled to the payee by this transaction, atomic units. */
|
|
1384
1208
|
delta: bigint;
|
|
1385
1209
|
}
|
|
1386
|
-
/** What `reconcile-tempo-drain` re-read from the close receipt it booked against. */
|
|
1387
1210
|
interface TempoCloseReconciled {
|
|
1388
1211
|
channelId: string;
|
|
1389
1212
|
txHash: string;
|
|
1390
1213
|
/** Captured by the payee — this DVM — per the receipt, atomic units as a decimal string. */
|
|
1391
1214
|
settledToPayee: string;
|
|
1392
1215
|
}
|
|
1393
|
-
/** The channel view the tracker sums claims over. */
|
|
1394
1216
|
interface X402TrackedChannel {
|
|
1395
1217
|
channelId: string;
|
|
1396
1218
|
chargedCumulativeAmount: string;
|
|
1397
1219
|
totalClaimed: string;
|
|
1398
1220
|
}
|
|
1399
|
-
/** A refund settlement wedged between chain and ledger (internal-review), for the wedge list. */
|
|
1400
1221
|
interface X402WedgedRefund {
|
|
1401
1222
|
settlementId: string;
|
|
1402
1223
|
/** Epoch ms the settlement was first prepared. */
|
|
1403
1224
|
createdAt: number;
|
|
1404
1225
|
native?: number;
|
|
1405
1226
|
}
|
|
1406
|
-
/** What the batch-settlement server hands the tracker once it knows its scope. */
|
|
1407
1227
|
interface X402PayoutContext {
|
|
1408
1228
|
/** `${network}|${payTo}|${token}` — the scope the settle-pending marker is keyed on. */
|
|
1409
1229
|
scope: string;
|
|
@@ -1416,7 +1236,6 @@ interface X402PayoutContext {
|
|
|
1416
1236
|
settleIntervalMs: number;
|
|
1417
1237
|
listWedgedRefunds?: () => Promise<X402WedgedRefund[]>;
|
|
1418
1238
|
}
|
|
1419
|
-
/** The two manager verbs the tracker wraps. */
|
|
1420
1239
|
interface X402TrackedManager {
|
|
1421
1240
|
claim(...args: never[]): Promise<{
|
|
1422
1241
|
vouchers: number;
|
|
@@ -1439,9 +1258,7 @@ interface X402PayoutObserver {
|
|
|
1439
1258
|
/** A settle attempt that failed outside the tracked manager. One call per attempt a builder would count as one. */
|
|
1440
1259
|
recordSettleFailure(error: unknown): Promise<void>;
|
|
1441
1260
|
}
|
|
1442
|
-
/** The Tempo readers the host attaches after mounting. */
|
|
1443
1261
|
interface TempoPayoutReader {
|
|
1444
|
-
/** The recipient address every Tempo payout names. */
|
|
1445
1262
|
recipient?: string;
|
|
1446
1263
|
/** CAIP-2 network for the refs, when known. */
|
|
1447
1264
|
network?: string;
|
|
@@ -1461,11 +1278,8 @@ interface TempoPayoutReader {
|
|
|
1461
1278
|
}) => Promise<CreditDrainRecord[]>;
|
|
1462
1279
|
}
|
|
1463
1280
|
/**
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
* whichever machine won the tick, and the settle that finally moves the money
|
|
1467
|
-
* may run on another: an in-process figure would report a partial amount
|
|
1468
|
-
* after any restart in between.
|
|
1281
|
+
* Claims accumulated since the last settle. The batch is durable across machines: claims and
|
|
1282
|
+
* eventual settlement can run on different hosts under the fleet lock.
|
|
1469
1283
|
*/
|
|
1470
1284
|
interface X402OpenBatch {
|
|
1471
1285
|
scope: string;
|
|
@@ -1479,55 +1293,39 @@ interface X402OpenBatch {
|
|
|
1479
1293
|
lastError: string | null;
|
|
1480
1294
|
nextRetryAt: number | null;
|
|
1481
1295
|
}
|
|
1482
|
-
/** Where the open batch lives — Postgres beside the settlement rows, or memory in dev. */
|
|
1483
1296
|
interface X402BatchStore {
|
|
1484
1297
|
get(scope: string): Promise<X402OpenBatch | undefined>;
|
|
1485
1298
|
put(batch: X402OpenBatch): Promise<void>;
|
|
1486
1299
|
delete(scope: string): Promise<void>;
|
|
1487
1300
|
/**
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
* a settle can never lose its payout, because a failed enqueue leaves the
|
|
1491
|
-
* batch open for the next settle to report in full. `false` when the batch
|
|
1492
|
-
* under `scope` is no longer `batchId` — a sibling settled it first — in
|
|
1493
|
-
* which case nothing is queued.
|
|
1301
|
+
* Atomically delete the matching batch and enqueue its payout. Failed enqueue preserves the
|
|
1302
|
+
* batch for retry; false means the scope no longer holds batchId and nothing was queued.
|
|
1494
1303
|
*/
|
|
1495
1304
|
settle(scope: string, batchId: string, enqueue: (tx?: RevenueReporterQuerier) => Promise<void>): Promise<boolean>;
|
|
1496
1305
|
}
|
|
1497
|
-
/** Construction options for {@link PayoutReporter}. */
|
|
1498
1306
|
interface PayoutReporterOpts {
|
|
1499
|
-
/** Platform DVM record ID, bound into every report. */
|
|
1500
1307
|
dvmId: string;
|
|
1501
1308
|
transport: PayoutTransport;
|
|
1502
1309
|
/**
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
* no cashu snapshot is taken.
|
|
1310
|
+
* Postgres holds durable x402 batches and supplies Cashu snapshots. Without it, the default
|
|
1311
|
+
* batch store is memory and Cashu snapshots are omitted.
|
|
1506
1312
|
*/
|
|
1507
1313
|
db?: Pool;
|
|
1508
1314
|
/**
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1511
|
-
* read would fail every tick and warn about a rail it does not carry.
|
|
1315
|
+
* Only accumulator hosts create wallet_accumulator; enabling this on another host makes
|
|
1316
|
+
* each snapshot query fail.
|
|
1512
1317
|
*/
|
|
1513
1318
|
cashu?: boolean;
|
|
1514
1319
|
/** Override the batch store (tests); defaults to Postgres on `db`, memory without. */
|
|
1515
1320
|
batchStore?: X402BatchStore;
|
|
1516
1321
|
/** Snapshot cadence, ms. Default 5 minutes. */
|
|
1517
1322
|
snapshotIntervalMs?: number;
|
|
1518
|
-
/** Override for the current time (tests). */
|
|
1519
1323
|
now?: () => number;
|
|
1520
1324
|
}
|
|
1521
1325
|
/**
|
|
1522
|
-
*
|
|
1523
|
-
*
|
|
1524
|
-
*
|
|
1525
|
-
* Two entry points are transactional: `cashuMelted` and `tempoCloseReconciled`
|
|
1526
|
-
* queue their row inside the caller's transaction and throw when they cannot,
|
|
1527
|
-
* so the melt or the reconciled close rolls back with its missing report rather
|
|
1528
|
-
* than landing without one. The x402 settle and the pending snapshots are
|
|
1529
|
-
* fail-safe instead: a report that cannot be built or queued there is logged and
|
|
1530
|
-
* picked up on the next tick, never thrown into the settle that produced it.
|
|
1326
|
+
* Cashu melts and repaired Tempo closes enqueue inside their caller’s transaction and
|
|
1327
|
+
* propagate failures. x402 reporting and pending snapshots instead log failures; failed x402
|
|
1328
|
+
* enqueue preserves the open batch for retry.
|
|
1531
1329
|
*/
|
|
1532
1330
|
declare class PayoutReporter implements PayoutTransport {
|
|
1533
1331
|
private readonly dvmId;
|
|
@@ -1544,7 +1342,6 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1544
1342
|
private timer;
|
|
1545
1343
|
private warmup;
|
|
1546
1344
|
constructor(opts: PayoutReporterOpts);
|
|
1547
|
-
/** Boot DDL for the durable batch store, when this reporter owns one. */
|
|
1548
1345
|
init(): Promise<void>;
|
|
1549
1346
|
/**
|
|
1550
1347
|
* Start the snapshot loop. Unref'd, so it never keeps a scale-to-zero
|
|
@@ -1558,23 +1355,13 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1558
1355
|
reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
|
|
1559
1356
|
/** Deliver what is queued now; a transport without the seam waits for its retry loop. */
|
|
1560
1357
|
flush(): Promise<void>;
|
|
1561
|
-
/** The hook `mark-melted` runs inside its transaction: the mark and its payout commit together. */
|
|
1562
1358
|
meltHook(): TransactionalPayoutHook<CashuMeltCompleted>;
|
|
1563
1359
|
/**
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
* belongs to one mint. The amount is the face value melted; the mint's fee
|
|
1568
|
-
* and the Lightning amount received are builder-machine facts this DVM does
|
|
1569
|
-
* not see.
|
|
1570
|
-
*
|
|
1571
|
-
* Queued through `tx` when the caller is inside the transaction that marks
|
|
1572
|
-
* the rows, and a failure is thrown rather than swallowed so that mark rolls
|
|
1573
|
-
* back with it — see {@link TransactionalPayoutHook}. Without `tx` the rows
|
|
1574
|
-
* are queued on their own and delivered at once.
|
|
1360
|
+
* Report melted face value per mint, not the Lightning amount received after fees. Hash the
|
|
1361
|
+
* payment preimage; do not publish it. With tx, propagate enqueue failures and defer
|
|
1362
|
+
* delivery until commit. Without tx, enqueue independently and start delivery.
|
|
1575
1363
|
*/
|
|
1576
1364
|
cashuMelted(event: CashuMeltCompleted, tx?: RevenueReporterQuerier): Promise<void>;
|
|
1577
|
-
/** The host attaches the session store and ledger readers once they exist. */
|
|
1578
1365
|
attachTempo(reader: TempoPayoutReader): void;
|
|
1579
1366
|
/**
|
|
1580
1367
|
* A Tempo channel settled or closed on chain. `delta` is what this
|
|
@@ -1582,29 +1369,18 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1582
1369
|
* (a close of a fully settled channel) is no payout.
|
|
1583
1370
|
*/
|
|
1584
1371
|
tempoSettled(event: TempoSessionSettled): Promise<void>;
|
|
1585
|
-
/** The hook `reconcile-tempo-drain` runs inside its transaction: the booking and its payout commit together. */
|
|
1586
1372
|
tempoRepairHook(): TransactionalPayoutHook<TempoCloseReconciled>;
|
|
1587
1373
|
/**
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
*
|
|
1591
|
-
* figure is the receipt's payee total for the channel.
|
|
1592
|
-
*
|
|
1593
|
-
* Queued through `tx` when the caller is inside the transaction that books
|
|
1594
|
-
* the drain, and thrown rather than swallowed so that booking rolls back
|
|
1595
|
-
* with it — see {@link TransactionalPayoutHook}.
|
|
1374
|
+
* Report the receipt’s payee total under the live close’s transaction ID, flagged repaired.
|
|
1375
|
+
* The platform upgrades a live row if one exists. Join tx and propagate enqueue failures;
|
|
1376
|
+
* this does not report the caller refund.
|
|
1596
1377
|
*/
|
|
1597
1378
|
tempoCloseReconciled(event: TempoCloseReconciled, tx?: RevenueReporterQuerier): Promise<void>;
|
|
1598
|
-
/** The observer the batch-settlement server attaches to and routes its manager through. */
|
|
1599
1379
|
x402Observer(): X402PayoutObserver;
|
|
1600
1380
|
/**
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
1603
|
-
*
|
|
1604
|
-
* fleet lock the before/after read is consistent. A channel the claim
|
|
1605
|
-
* removed on its way through — a refund's claim-then-delete (internal-review) —
|
|
1606
|
-
* moved whatever it still owed before it went, and that value reaches
|
|
1607
|
-
* `pay_to` in this batch too, so it counts at its pre-claim figure.
|
|
1381
|
+
* Read claim deltas from storage because upstream returns voucher counts. Under the fleet
|
|
1382
|
+
* lock, before/after reads are consistent. A deleted channel contributes its pre-claim
|
|
1383
|
+
* unclaimed value, including refund claim-then-delete.
|
|
1608
1384
|
*/
|
|
1609
1385
|
private trackX402Manager;
|
|
1610
1386
|
private recordX402Claim;
|
|
@@ -1627,7 +1403,7 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1627
1403
|
/**
|
|
1628
1404
|
* Tempo: each active channel's spent-but-unsettled balance — earned, not
|
|
1629
1405
|
* yet paid to the recipient — and the cooperative closes wedged between
|
|
1630
|
-
* chain and ledger
|
|
1406
|
+
* chain and ledger, aged past the same floor the repair queue
|
|
1631
1407
|
* uses so an in-flight close is not reported as stuck.
|
|
1632
1408
|
*/
|
|
1633
1409
|
snapshotTempo(): Promise<void>;
|
|
@@ -1636,65 +1412,57 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1636
1412
|
/**
|
|
1637
1413
|
* JSON wire shape POSTed to the platform's `/_internal/job-revenue` endpoint
|
|
1638
1414
|
* and stored in `pending_dvm_reports.payload` for durable retry.
|
|
1639
|
-
*
|
|
1640
|
-
* The platform-side consumer is `OnJobCompleted`, implemented by
|
|
1641
|
-
* `createRevenueCallback`. Keep the two shapes in sync: the SDK and platform
|
|
1642
|
-
* deploy in lockstep, and any
|
|
1643
|
-
* backwards-incompatible field change requires a coordinated release. No `version`
|
|
1644
|
-
* field is included pre-launch because there are no external container-runtime
|
|
1645
|
-
* consumers yet; add one once the first third-party builder ships a container-runtime
|
|
1646
|
-
* DVM (trigger condition: external builder onboarded via `dvmctl deploy --container`).
|
|
1647
1415
|
*/
|
|
1648
1416
|
interface RevenueReportPayload {
|
|
1649
|
-
/**
|
|
1417
|
+
/** Platform DVM record ID (`dvms.id`). Identifies which DVM earned the revenue. */
|
|
1650
1418
|
dvmId: string;
|
|
1651
|
-
/**
|
|
1419
|
+
/** Job identifier used by the platform to de-duplicate revenue rows and link to the job record. */
|
|
1652
1420
|
jobId: string;
|
|
1653
1421
|
/**
|
|
1654
|
-
*
|
|
1422
|
+
* Capability selected by dispatch for this job. Omitted for
|
|
1655
1423
|
* reports from older SDKs and legacy persisted jobs that predate capability
|
|
1656
1424
|
* dispatch; absence must not be rewritten as a capability name.
|
|
1657
1425
|
*/
|
|
1658
1426
|
capability?: string;
|
|
1659
|
-
/**
|
|
1427
|
+
/** Total amount paid by the caller in millisatoshis, across all credits that satisfied the job. */
|
|
1660
1428
|
paidMsats: number;
|
|
1661
|
-
/**
|
|
1429
|
+
/** Cashu mint URL from which the payment tokens were issued. Set only for the `cashu` rail; omitted for all other rails. */
|
|
1662
1430
|
paymentMint?: string;
|
|
1663
1431
|
/**
|
|
1664
|
-
*
|
|
1432
|
+
* Payment rail identifier (`"cashu"`, `"tempo"`, `"x402"`, `"stripe"`).
|
|
1665
1433
|
* Mirrors `PaymentMethod` on the platform; carried as a plain string over the wire
|
|
1666
1434
|
* so the SDK has no platform-type dependency.
|
|
1667
1435
|
*/
|
|
1668
1436
|
rail: string;
|
|
1669
1437
|
/**
|
|
1670
|
-
*
|
|
1438
|
+
* Settlement reference for the credit: EVM tx hash for `x402`, credential
|
|
1671
1439
|
* challenge ID for `tempo`, `X-Cashu-Request-Id` UUID for `cashu`. Required by the
|
|
1672
1440
|
* platform revenue ledger for `tempo`/`x402` rails (recordJobRevenue throws without it);
|
|
1673
1441
|
* nullable for `cashu`.
|
|
1674
1442
|
*/
|
|
1675
1443
|
paymentTxHash?: string;
|
|
1676
1444
|
/**
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
1445
|
+
* Rail-native payment amount in atomic units: USDC microunits for `tempo` and
|
|
1446
|
+
* `x402`, USD cents for `stripe`. Omitted for `cashu` (platform derives
|
|
1679
1447
|
* `paidMsats / 1000`). Must be set together with `nativeAsset`.
|
|
1680
1448
|
*/
|
|
1681
1449
|
nativeAmount?: number;
|
|
1682
1450
|
/**
|
|
1683
|
-
*
|
|
1451
|
+
* Asset tag paired with `nativeAmount`: `"sats"`, `"usdc"`, `"usdc.e"`, or
|
|
1684
1452
|
* `"usd-cents"`. Both fields must be present for the platform to write a non-cashu
|
|
1685
1453
|
* revenue row.
|
|
1686
1454
|
*/
|
|
1687
1455
|
nativeAsset?: string;
|
|
1688
1456
|
/**
|
|
1689
|
-
*
|
|
1690
|
-
* accumulator path
|
|
1457
|
+
* Cashu flow discriminator. Set to `"p2pk_accumulator"` when the cashu
|
|
1458
|
+
* accumulator path satisfied the job; omitted for legacy cashu. Written into
|
|
1691
1459
|
* `revenue_events.metadata.cashu_flow` by the platform so ops can split per-call cashu
|
|
1692
1460
|
* rows by source.
|
|
1693
1461
|
*/
|
|
1694
1462
|
cashuFlow?: string;
|
|
1695
1463
|
/**
|
|
1696
|
-
*
|
|
1697
|
-
* handler through `ctx.cost()`
|
|
1464
|
+
* What the job cost the **builder** to serve, as declared by the
|
|
1465
|
+
* handler through `ctx.cost()` — the other half of the
|
|
1698
1466
|
* margin the dashboard states. `amountMicro` is 1e-6 of `currency`, which
|
|
1699
1467
|
* is the currency the builder incurred the cost in and need not be the
|
|
1700
1468
|
* credit's.
|
|
@@ -1715,37 +1483,50 @@ interface RevenueReportPayload {
|
|
|
1715
1483
|
currency: string;
|
|
1716
1484
|
};
|
|
1717
1485
|
/**
|
|
1718
|
-
*
|
|
1486
|
+
* Credit this job's payment drew against. Present on
|
|
1719
1487
|
* every job a ledger-backed payment satisfied; its presence is what tells
|
|
1720
1488
|
* the platform the row is a **draw-keyed** revenue event rather than a
|
|
1721
1489
|
* pre-credits per-call one.
|
|
1722
1490
|
*/
|
|
1723
1491
|
creditId?: string;
|
|
1724
|
-
/**
|
|
1492
|
+
/** The draw this job settled — one draw, one revenue row. */
|
|
1725
1493
|
drawId?: string;
|
|
1726
|
-
/**
|
|
1494
|
+
/** The draw's fiat amount, 1e-6 of `creditCurrency`. The exact liability offset. */
|
|
1727
1495
|
drawAmountMicro?: number;
|
|
1728
|
-
/**
|
|
1496
|
+
/** Currency the credit is denominated in (lowercase ISO, e.g. `usd`). */
|
|
1729
1497
|
creditCurrency?: string;
|
|
1730
1498
|
/**
|
|
1731
|
-
*
|
|
1499
|
+
* Settlement instant (epoch ms) — draws are revenue *when they
|
|
1732
1500
|
* settle*, so this dates the row. Omitted for pre-credits rows, which the
|
|
1733
1501
|
* platform dates at record time as before.
|
|
1734
1502
|
*/
|
|
1735
1503
|
settledAt?: number;
|
|
1736
|
-
/**
|
|
1504
|
+
/** The funding's own rail reference, for reconciling a draw back to its deposit. */
|
|
1737
1505
|
fundingRef?: string;
|
|
1738
1506
|
/**
|
|
1739
|
-
*
|
|
1507
|
+
* Revenue class when this row is not ordinary service revenue.
|
|
1740
1508
|
* `short_pay_forfeit` marks money kept from an underpayment that bought no
|
|
1741
|
-
* job
|
|
1509
|
+
* job so analytics can exclude it.
|
|
1742
1510
|
*/
|
|
1743
1511
|
kind?: string;
|
|
1744
1512
|
}
|
|
1513
|
+
/** Privacy-preserving terminal evidence; contains no caller or payment references. */
|
|
1514
|
+
interface JobTerminalReportPayload {
|
|
1515
|
+
dvmId: string;
|
|
1516
|
+
jobId: string;
|
|
1517
|
+
capability: string;
|
|
1518
|
+
outcome: "completed" | "failed" | "cancelled";
|
|
1519
|
+
endedBy: "caller" | "provider" | null;
|
|
1520
|
+
paid: boolean;
|
|
1521
|
+
acceptedAtMs: number;
|
|
1522
|
+
terminalAtMs: number;
|
|
1523
|
+
callerWaitMs: number;
|
|
1524
|
+
receiptSeq: number | null;
|
|
1525
|
+
}
|
|
1745
1526
|
/**
|
|
1746
1527
|
* JSON wire shape POSTed to the platform's `/_internal/job-cost` endpoint
|
|
1747
1528
|
* when a terminal job incurred a declared builder cost but produced no
|
|
1748
|
-
* revenue report
|
|
1529
|
+
* revenue report.
|
|
1749
1530
|
*
|
|
1750
1531
|
* Unlike {@link RevenueReportPayload}, this event has no payment rail or
|
|
1751
1532
|
* revenue amount: a failed, cancelled, or free job is not a sale. The
|
|
@@ -1756,16 +1537,16 @@ interface RevenueReportPayload {
|
|
|
1756
1537
|
* delivery order.
|
|
1757
1538
|
*/
|
|
1758
1539
|
interface JobCostReportPayload {
|
|
1759
|
-
/**
|
|
1540
|
+
/** Platform DVM record ID (`dvms.id`); bound to the authenticating token. */
|
|
1760
1541
|
dvmId: string;
|
|
1761
|
-
/**
|
|
1542
|
+
/** Job identifier and the report's platform-side idempotency key. */
|
|
1762
1543
|
jobId: string;
|
|
1763
|
-
/**
|
|
1544
|
+
/** Capability selected by dispatch; absent only on legacy persisted jobs. */
|
|
1764
1545
|
capability?: string;
|
|
1765
|
-
/**
|
|
1546
|
+
/** Terminal outcome that explains why this event carries no revenue. */
|
|
1766
1547
|
outcome: "completed" | "failed" | "cancelled";
|
|
1767
1548
|
/**
|
|
1768
|
-
*
|
|
1549
|
+
* Number of declarations incorporated into `cost`; the
|
|
1769
1550
|
* platform keeps the payload with the highest revision for this job.
|
|
1770
1551
|
*/
|
|
1771
1552
|
costRevision: number;
|
|
@@ -1781,45 +1562,44 @@ interface JobCostReportPayload {
|
|
|
1781
1562
|
}
|
|
1782
1563
|
/**
|
|
1783
1564
|
* JSON wire shape POSTed to the platform's `/_internal/credit-deposit`
|
|
1784
|
-
* endpoint
|
|
1785
|
-
* drawn — reported separately and never summed into revenue (
|
|
1565
|
+
* endpoint. A funding event is a **deposit** — a liability until
|
|
1566
|
+
* drawn — reported separately and never summed into revenue (the deposit-accounting rule).
|
|
1786
1567
|
*
|
|
1787
1568
|
* Idempotent on `(dvmId, creditId, fundingId)`, so the shared retry loop can
|
|
1788
1569
|
* redeliver freely.
|
|
1789
1570
|
*/
|
|
1790
1571
|
interface CreditDepositPayload {
|
|
1791
|
-
/**
|
|
1572
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1792
1573
|
dvmId: string;
|
|
1793
|
-
/** Required. Credit the funding landed on. */
|
|
1794
1574
|
creditId: string;
|
|
1795
|
-
/**
|
|
1575
|
+
/** Canonical owner of the funded credit. */
|
|
1796
1576
|
callerPubkey: string;
|
|
1797
|
-
/**
|
|
1577
|
+
/** The rail's settlement reference — the deposit's idempotency key. */
|
|
1798
1578
|
fundingId: string;
|
|
1799
|
-
/**
|
|
1579
|
+
/** Payment rail (`"cashu"`, `"tempo"`, `"x402"`). */
|
|
1800
1580
|
rail: string;
|
|
1801
|
-
/**
|
|
1581
|
+
/** Rail value received, in millisatoshis. */
|
|
1802
1582
|
paidMsats: number;
|
|
1803
1583
|
/** Cashu only. Full received proof face before its receiver input fee. */
|
|
1804
1584
|
grossPaidMsats?: number;
|
|
1805
1585
|
/** Cashu only. Standalone NUT-02 fee reserved by the received proofs. */
|
|
1806
1586
|
cashuInputFeeMsats?: number;
|
|
1807
|
-
/**
|
|
1587
|
+
/** Rail-native atomic units; omitted for cashu (derived from `paidMsats`). */
|
|
1808
1588
|
nativeAmount?: number;
|
|
1809
|
-
/**
|
|
1589
|
+
/** Asset tag paired with `nativeAmount`. */
|
|
1810
1590
|
nativeAsset?: string;
|
|
1811
|
-
/**
|
|
1591
|
+
/** Cashu mint URL the funding proofs were issued from. */
|
|
1812
1592
|
paymentMint?: string;
|
|
1813
|
-
/**
|
|
1593
|
+
/** Fiat micro credited to the ledger — the liability this deposit creates. */
|
|
1814
1594
|
amountMicro: number;
|
|
1815
|
-
/**
|
|
1595
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1816
1596
|
creditCurrency: string;
|
|
1817
|
-
/**
|
|
1597
|
+
/** When the funding committed (epoch ms). */
|
|
1818
1598
|
fundedAt: number;
|
|
1819
|
-
/**
|
|
1599
|
+
/** Current credit expiry after this funding committed (epoch ms). */
|
|
1820
1600
|
expiryMs: number;
|
|
1821
1601
|
/**
|
|
1822
|
-
* Tempo session funding only
|
|
1602
|
+
* Tempo session funding only: the TIP-1034 channel this deposit
|
|
1823
1603
|
* arrived on, registered so the platform's close observer can route a
|
|
1824
1604
|
* `CloseRequested` event back to the DVM that owns the channel.
|
|
1825
1605
|
*
|
|
@@ -1841,14 +1621,14 @@ interface CreditDepositPayload {
|
|
|
1841
1621
|
payee?: string;
|
|
1842
1622
|
};
|
|
1843
1623
|
/**
|
|
1844
|
-
* Tempo session funding only
|
|
1624
|
+
* Tempo session funding only: which channel this deposit's voucher
|
|
1845
1625
|
* was accepted on, emitted **whenever the funding was session-based** —
|
|
1846
1626
|
* independently of whether {@link CreditDepositPayload.tempoChannel} could be
|
|
1847
1627
|
* read. That independence is the entire point: `rail: "tempo"` covers both
|
|
1848
1628
|
* `tempo/session` and the `tempo/charge` one-shot, and a charge legitimately
|
|
1849
1629
|
* carries no channel at all, so without this the platform cannot tell a
|
|
1850
1630
|
* routine charge deposit from a session whose chain binding was lost — the
|
|
1851
|
-
*
|
|
1631
|
+
* shape, where the close observer silently never watches the escrow.
|
|
1852
1632
|
*
|
|
1853
1633
|
* Carries only the channel id, not the chain binding: a DVM that could read
|
|
1854
1634
|
* the binding sends `tempoChannel` too, and one that could not still names
|
|
@@ -1859,7 +1639,7 @@ interface CreditDepositPayload {
|
|
|
1859
1639
|
channelId: string;
|
|
1860
1640
|
};
|
|
1861
1641
|
/**
|
|
1862
|
-
* Set by the blocked-invoice repair
|
|
1642
|
+
* Set by the blocked-invoice repair: this deposit is a paid
|
|
1863
1643
|
* Lightning invoice the builder's agent reconciled onto a credit. The money
|
|
1864
1644
|
* landed in the receive wallet at payment time and this deposit is the one
|
|
1865
1645
|
* row it gets, so the settlements table types it `repair` off this flag
|
|
@@ -1869,7 +1649,7 @@ interface CreditDepositPayload {
|
|
|
1869
1649
|
}
|
|
1870
1650
|
/**
|
|
1871
1651
|
* JSON wire shape POSTed to the platform's `/_internal/credit-draw-release`
|
|
1872
|
-
* endpoint
|
|
1652
|
+
* endpoint. A released hold leaves the credit balance intact, but
|
|
1873
1653
|
* marks that slice of the outstanding liability as failure churn rather than
|
|
1874
1654
|
* untouched prefunding.
|
|
1875
1655
|
*
|
|
@@ -1877,62 +1657,45 @@ interface CreditDepositPayload {
|
|
|
1877
1657
|
* redeliver freely.
|
|
1878
1658
|
*/
|
|
1879
1659
|
interface CreditDrawReleasePayload {
|
|
1880
|
-
/**
|
|
1660
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1881
1661
|
dvmId: string;
|
|
1882
|
-
/** Required. Credit whose hold was released. */
|
|
1883
1662
|
creditId: string;
|
|
1884
|
-
/**
|
|
1663
|
+
/** Released draw; the platform idempotency key with DVM and credit. */
|
|
1885
1664
|
drawId: string;
|
|
1886
|
-
/**
|
|
1665
|
+
/** Fiat micro restored to availability. */
|
|
1887
1666
|
amountMicro: number;
|
|
1888
|
-
/**
|
|
1667
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1889
1668
|
creditCurrency: string;
|
|
1890
|
-
/**
|
|
1669
|
+
/** Per-credit gap-free sequence assigned when the draw was placed. */
|
|
1891
1670
|
ledgerSeq: number;
|
|
1892
|
-
/**
|
|
1671
|
+
/** When the hold reached `released` (epoch ms). */
|
|
1893
1672
|
releasedAt: number;
|
|
1894
1673
|
}
|
|
1895
|
-
/** Repair verbs that can finish a caller-facing credit reclaim. */
|
|
1896
1674
|
type CreditDrainRepairKind = "x402_settlement_reconciled" | "tempo_close_reconciled";
|
|
1897
1675
|
/**
|
|
1898
|
-
*
|
|
1899
|
-
*
|
|
1900
|
-
* the caller
|
|
1901
|
-
* so outstanding liability nets out to `deposited − drawn − drained`; never
|
|
1902
|
-
* summed into revenue, and never a revenue row — no job was sold.
|
|
1903
|
-
*
|
|
1904
|
-
* Reported only on the drain's **terminal** transition (`picked_up` for cashu,
|
|
1905
|
-
* `sent` for the other rails), because that is where the money has verifiably
|
|
1906
|
-
* left. `requested` and `parked` have not paid the caller yet.
|
|
1907
|
-
*
|
|
1908
|
-
* Idempotent on `(dvmId, creditId, drainId)`, so the shared retry loop can
|
|
1909
|
-
* redeliver freely.
|
|
1910
|
-
*
|
|
1911
|
-
* Fiat micro only, with no `paidMsats` sibling: `credit_drains` holds no rail
|
|
1912
|
-
* value (the sats figure is derived at read time from a live rate), so there
|
|
1913
|
-
* would be nothing truthful to put in one — and the liability arithmetic this
|
|
1914
|
-
* feeds is exact fiat micro on all three of its terms.
|
|
1676
|
+
* Terminal picked_up/sent drains report reclaimed fiat micro-units, reducing
|
|
1677
|
+
* outstanding liability rather than adding revenue. Requested or parked drains
|
|
1678
|
+
* have not paid the caller. Idempotency key: (dvmId, creditId, drainId).
|
|
1915
1679
|
*/
|
|
1916
1680
|
interface CreditDrainPayload {
|
|
1917
|
-
/**
|
|
1681
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1918
1682
|
dvmId: string;
|
|
1919
|
-
/** Required. Credit the reclaim came off. */
|
|
1920
1683
|
creditId: string;
|
|
1921
|
-
/**
|
|
1684
|
+
/** Canonical owner of the reclaimed credit. */
|
|
1922
1685
|
callerPubkey: string;
|
|
1923
|
-
/**
|
|
1686
|
+
/** The drain's own idempotency key. */
|
|
1924
1687
|
drainId: string;
|
|
1925
|
-
/**
|
|
1688
|
+
/** Payout rail (`"cashu"`, `"x402"`, `"tempo"`). */
|
|
1926
1689
|
method: string;
|
|
1927
|
-
/**
|
|
1690
|
+
/** Fiat micro reclaimed — the liability this drain extinguishes. */
|
|
1928
1691
|
amountMicro: number;
|
|
1929
|
-
/**
|
|
1692
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1930
1693
|
creditCurrency: string;
|
|
1931
|
-
/**
|
|
1694
|
+
/** Terminal state: `"picked_up"` for cashu, `"sent"` for other rails. */
|
|
1932
1695
|
status: string;
|
|
1933
|
-
/**
|
|
1696
|
+
/** When the money left (epoch ms). */
|
|
1934
1697
|
drainedAt: number;
|
|
1935
|
-
/**
|
|
1698
|
+
/** Per-credit gap-free ledger sequence, for reconciling against the DVM's ledger. */
|
|
1936
1699
|
ledgerSeq?: number;
|
|
1937
1700
|
/** Set when an operator repair verb completed this reclaim. */
|
|
1938
1701
|
repaired?: boolean;
|
|
@@ -1957,7 +1720,7 @@ type CreditDepositEnqueue = (tx: RevenueReporterQuerier, payload: CreditDepositP
|
|
|
1957
1720
|
type CreditDrawReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload) => Promise<void>;
|
|
1958
1721
|
/**
|
|
1959
1722
|
* Transactional drain-outbox seam threaded through each terminal drain
|
|
1960
|
-
* transition
|
|
1723
|
+
* transition.
|
|
1961
1724
|
*
|
|
1962
1725
|
* Implemented by {@link RevenueReporter.enqueueCreditDrain}; callers must await
|
|
1963
1726
|
* it before committing the status CAS that moved the money.
|
|
@@ -1965,7 +1728,7 @@ type CreditDrawReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditDraw
|
|
|
1965
1728
|
type CreditDrainEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrainPayload) => Promise<void>;
|
|
1966
1729
|
/**
|
|
1967
1730
|
* JSON wire shape POSTed to the platform's `/_internal/credit-expiry-release`
|
|
1968
|
-
* endpoint
|
|
1731
|
+
* endpoint. A prepaid credit that reaches its TTL with an undrawn
|
|
1969
1732
|
* remainder RELEASES that remainder to the builder: on cashu, Lightning and
|
|
1970
1733
|
* x402 the funding value reached builder custody at funding time or on the
|
|
1971
1734
|
* settlement cycle, so expiry extinguishes the caller's remaining claim.
|
|
@@ -1981,39 +1744,37 @@ type CreditDrainEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrainPaylo
|
|
|
1981
1744
|
*
|
|
1982
1745
|
* - **The release**, written by the expiry sweep with `reversedAt` absent.
|
|
1983
1746
|
* - **The reversal**, written when a later funding revives the credit
|
|
1984
|
-
*
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
1747
|
+
* (`CreditLedger.fund` overwrites `expiry_ms`, so the recorded remainder is
|
|
1748
|
+
* no longer final). It repeats the whole payload rather than sending a
|
|
1749
|
+
* reference, because the retry loop can deliver the two out of order and the
|
|
1750
|
+
* platform's upsert must be able to land either one first.
|
|
1988
1751
|
*
|
|
1989
1752
|
* Idempotent on `(dvmId, creditId, releaseId)`, so the shared retry loop can
|
|
1990
1753
|
* redeliver freely.
|
|
1991
1754
|
*/
|
|
1992
1755
|
interface CreditExpiryReleasePayload {
|
|
1993
|
-
/**
|
|
1756
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1994
1757
|
dvmId: string;
|
|
1995
|
-
/** Required. Credit whose remainder expired. */
|
|
1996
1758
|
creditId: string;
|
|
1997
|
-
/**
|
|
1759
|
+
/** Per-expiry-episode key; the platform idempotency key with DVM and credit. */
|
|
1998
1760
|
releaseId: string;
|
|
1999
|
-
/** Required. Caller whose claim on the remainder ended. */
|
|
2000
1761
|
callerPubkey: string;
|
|
2001
1762
|
/** Funding rail. `null` on a credit funded before the basis became mandatory. */
|
|
2002
1763
|
rail: string | null;
|
|
2003
|
-
/**
|
|
1764
|
+
/** Fiat micro released — the liability this expiry extinguishes. */
|
|
2004
1765
|
amountMicro: number;
|
|
2005
|
-
/**
|
|
1766
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
2006
1767
|
creditCurrency: string;
|
|
2007
|
-
/**
|
|
1768
|
+
/** When the credit's TTL ran out (epoch ms) — when the liability ended. */
|
|
2008
1769
|
expiryMs: number;
|
|
2009
|
-
/**
|
|
1770
|
+
/** When the sweep observed the expiry (epoch ms). */
|
|
2010
1771
|
releasedAt: number;
|
|
2011
1772
|
/** Set only on the reversal report: when the revival funding landed (epoch ms). */
|
|
2012
1773
|
reversedAt?: number | null;
|
|
2013
1774
|
}
|
|
2014
1775
|
/**
|
|
2015
1776
|
* Transactional expiry-release outbox seam, threaded through the sweep's insert
|
|
2016
|
-
* and through the funding that reverses a release
|
|
1777
|
+
* and through the funding that reverses a release.
|
|
2017
1778
|
*
|
|
2018
1779
|
* Implemented by {@link RevenueReporter.enqueueCreditExpiryRelease}; callers
|
|
2019
1780
|
* must await it before committing the write that made the release (or its
|
|
@@ -2023,7 +1784,7 @@ interface CreditExpiryReleasePayload {
|
|
|
2023
1784
|
type CreditExpiryReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload) => Promise<void>;
|
|
2024
1785
|
/**
|
|
2025
1786
|
* JSON wire shape POSTed to the platform's `/_internal/paid-job-death` endpoint
|
|
2026
|
-
*
|
|
1787
|
+
* when the stale-job reaper force-fails a *paid* job — its pending
|
|
2027
1788
|
* credit draw is released rather than settled, so the caller's payment is back
|
|
2028
1789
|
* on their credit balance and reclaimable by drain. The platform resolves fiat
|
|
2029
1790
|
* + owner and sends an ambient notice via the shared alert sink. Unlike
|
|
@@ -2032,52 +1793,45 @@ type CreditExpiryReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditEx
|
|
|
2032
1793
|
* report degrades to log-only rather than blocking the sweep.
|
|
2033
1794
|
*/
|
|
2034
1795
|
interface PaidJobDeathPayload {
|
|
2035
|
-
/**
|
|
1796
|
+
/** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
|
|
2036
1797
|
dvmId: string;
|
|
2037
|
-
/** Required. Job identifier the reaper force-failed. */
|
|
2038
1798
|
jobId: string;
|
|
2039
|
-
/** Required. Capability the job dispatched to (e.g. `"add-episode"`). */
|
|
2040
1799
|
capability: string;
|
|
2041
|
-
/**
|
|
1800
|
+
/** Amount of the released credit draw in millisatoshis. */
|
|
2042
1801
|
paidMsats: number;
|
|
2043
|
-
/**
|
|
1802
|
+
/** Payment rail of the released draw (`"cashu"`, `"tempo"`, `"x402"`). */
|
|
2044
1803
|
rail: string;
|
|
2045
|
-
/**
|
|
1804
|
+
/** Reaper reason: dead worker or never-terminal idle job. */
|
|
2046
1805
|
reason: string;
|
|
2047
|
-
/**
|
|
1806
|
+
/** Rail-native released amount, paired with `nativeAsset`. */
|
|
2048
1807
|
nativeAmount?: number;
|
|
2049
|
-
/**
|
|
1808
|
+
/** Asset tag for `nativeAmount` (`"sats"` / `"usdc"` / …). */
|
|
2050
1809
|
nativeAsset?: string;
|
|
2051
|
-
/**
|
|
1810
|
+
/** Cashu mint URL the credit draw's tokens were issued from. */
|
|
2052
1811
|
paymentMint?: string;
|
|
2053
1812
|
}
|
|
2054
|
-
/** Distinct causes for a settled credit draw that could not book revenue. */
|
|
2055
1813
|
type RevenueSkippedNoRailReason = "credit_rail_null" | "credit_rail_unrecognized" | "job_rail_missing" | "ledger_not_configured";
|
|
2056
1814
|
/**
|
|
2057
1815
|
* JSON wire shape POSTed to the platform's `/_internal/revenue-skipped-no-rail`
|
|
2058
|
-
* endpoint
|
|
1816
|
+
* endpoint when a settled credit draw has no usable payment rail
|
|
2059
1817
|
* for its revenue event. This is an operator notice rather than a durable
|
|
2060
1818
|
* accounting report: the SDK's structured log remains the durable backstop,
|
|
2061
1819
|
* and a failed alert must never block terminal handling or reconciliation.
|
|
2062
1820
|
*/
|
|
2063
1821
|
interface RevenueSkippedNoRailPayload {
|
|
2064
|
-
/**
|
|
1822
|
+
/** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
|
|
2065
1823
|
dvmId: string;
|
|
2066
|
-
/** Required. Job whose settled draw could not be booked. */
|
|
2067
1824
|
jobId: string;
|
|
2068
|
-
/** Required. Credit carrying the settled draw. */
|
|
2069
1825
|
creditId: string;
|
|
2070
|
-
/** Required. Settled draw that has no revenue event. */
|
|
2071
1826
|
drawId: string;
|
|
2072
|
-
/**
|
|
1827
|
+
/** Caller payment captured for the job, in millisatoshis. */
|
|
2073
1828
|
paidMsats: number;
|
|
2074
|
-
/**
|
|
1829
|
+
/** Settled draw amount in 1e-6 of `creditCurrency`. */
|
|
2075
1830
|
drawAmountMicro?: number;
|
|
2076
|
-
/**
|
|
1831
|
+
/** Currency the draw amount is denominated in. */
|
|
2077
1832
|
creditCurrency?: string;
|
|
2078
1833
|
/** Credit funding rail as stored on the draw, or null for legacy rows. */
|
|
2079
1834
|
creditRail: string | null;
|
|
2080
|
-
/** Required classification of why the revenue rail was unavailable. */
|
|
2081
1835
|
reason: RevenueSkippedNoRailReason;
|
|
2082
1836
|
}
|
|
2083
1837
|
/** Gas-balance observation POSTed by a platform-hosted x402 self-relay. */
|
|
@@ -2147,11 +1901,13 @@ interface TempoSettlementFailurePayload {
|
|
|
2147
1901
|
drainId?: string;
|
|
2148
1902
|
}
|
|
2149
1903
|
/**
|
|
2150
|
-
* Durable revenue reporter for container-runtime DVMs
|
|
1904
|
+
* Durable revenue reporter for container-runtime DVMs.
|
|
2151
1905
|
*
|
|
2152
1906
|
* On job completion the SDK calls `report()`, which persists the payload to
|
|
2153
1907
|
* a local Postgres table and attempts an immediate POST to the platform.
|
|
2154
1908
|
* A background retry loop picks up un-acked rows with exponential backoff.
|
|
1909
|
+
* Gauge and health notifications are best-effort: failed delivery is logged
|
|
1910
|
+
* and dropped rather than queued for retry.
|
|
2155
1911
|
*/
|
|
2156
1912
|
declare class RevenueReporter {
|
|
2157
1913
|
private readonly db;
|
|
@@ -2164,16 +1920,17 @@ declare class RevenueReporter {
|
|
|
2164
1920
|
constructor(db: Pool, platformUrl: string, platformToken: string);
|
|
2165
1921
|
/** Create the pending-reports table and migrate the legacy SDK outbox (idempotent). */
|
|
2166
1922
|
init(): Promise<void>;
|
|
2167
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
2168
1923
|
private createTables;
|
|
2169
|
-
/** Copy rows from the old SDK-owned table without touching the platform's table of that name. */
|
|
2170
1924
|
private migrateLegacySdkOutbox;
|
|
2171
|
-
/** Refuse a pre-existing relation whose columns cannot safely serve as the SDK outbox. */
|
|
2172
1925
|
private assertSdkTableShape;
|
|
2173
1926
|
private assertSdkColumns;
|
|
2174
1927
|
private tableColumns;
|
|
2175
1928
|
/** Persist a revenue report and attempt immediate delivery. */
|
|
2176
1929
|
report(payload: RevenueReportPayload): Promise<void>;
|
|
1930
|
+
/** The permanent deduplication claim and outbox insert commit together.
|
|
1931
|
+
* The claim survives successful delivery, including for custom job stores.
|
|
1932
|
+
*/
|
|
1933
|
+
reportTerminal(payload: JobTerminalReportPayload): Promise<void>;
|
|
2177
1934
|
/** Persist a zero-revenue job cost and attempt immediate delivery. */
|
|
2178
1935
|
reportCost(payload: JobCostReportPayload): Promise<void>;
|
|
2179
1936
|
/**
|
|
@@ -2187,21 +1944,13 @@ declare class RevenueReporter {
|
|
|
2187
1944
|
/** Join a released draw to the transaction that made the hold terminal. */
|
|
2188
1945
|
enqueueCreditDrawRelease(tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload): Promise<void>;
|
|
2189
1946
|
/**
|
|
2190
|
-
*
|
|
2191
|
-
*
|
|
2192
|
-
* Same discipline as {@link enqueueDeposit} and the same reason: no network
|
|
2193
|
-
* I/O here, only the durable queue row, written through `tx` so the report
|
|
2194
|
-
* and the status CAS commit or roll back together. A drain whose row was
|
|
2195
|
-
* lost between the two would overstate outstanding liability forever —
|
|
2196
|
-
* the mirror image of a lost deposit.
|
|
2197
|
-
*
|
|
2198
|
-
* Named for the payload rather than the verb because `drain` already means
|
|
2199
|
-
* "flush the queue" in this class (see {@link drainPending}).
|
|
1947
|
+
* Await durable queue insertion through tx so the terminal drain and its
|
|
1948
|
+
* report commit or roll back together; no network I/O here.
|
|
2200
1949
|
*/
|
|
2201
1950
|
enqueueCreditDrain(tx: RevenueReporterQuerier, payload: CreditDrainPayload): Promise<void>;
|
|
2202
1951
|
/**
|
|
2203
1952
|
* Join a payout to the transaction that commits the movement it reports
|
|
2204
|
-
*
|
|
1953
|
+
* the batch close, the accumulator mark, the drain booking.
|
|
2205
1954
|
* No network I/O, only the durable queue row through `tx`, the discipline
|
|
2206
1955
|
* {@link enqueueCreditDrain} set and for the same reason: the money has
|
|
2207
1956
|
* already moved on a rail this ledger does not own, so the platform row is
|
|
@@ -2219,25 +1968,19 @@ declare class RevenueReporter {
|
|
|
2219
1968
|
*/
|
|
2220
1969
|
reportPayout(payload: PayoutReportPayload): Promise<void>;
|
|
2221
1970
|
/**
|
|
2222
|
-
* Report one rail's pending-pool snapshot
|
|
1971
|
+
* Report one rail's pending-pool snapshot. Best-effort and
|
|
2223
1972
|
* fire-and-forget, deliberately unlike {@link reportPayout}: a snapshot is
|
|
2224
1973
|
* replaced by the next tick, so queueing a stale one behind an outage would
|
|
2225
1974
|
* only deliver figures the platform already has newer ones for.
|
|
2226
1975
|
*/
|
|
2227
1976
|
reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
|
|
2228
1977
|
/**
|
|
2229
|
-
*
|
|
2230
|
-
*
|
|
2231
|
-
*
|
|
2232
|
-
* Same discipline as {@link enqueueCreditDrain}: no network I/O, only the
|
|
2233
|
-
* durable queue row written through `tx`. A release whose row was lost after
|
|
2234
|
-
* the sweep committed would understate committed value forever; a lost
|
|
2235
|
-
* reversal would overstate it, which is the worse direction — the builder
|
|
2236
|
-
* would see money as theirs that a revived credit can still buy work with.
|
|
1978
|
+
* Await durable queue insertion through tx so expiry release or reversal and
|
|
1979
|
+
* its report commit together. A missing reversal would overstate builder value.
|
|
2237
1980
|
*/
|
|
2238
1981
|
enqueueCreditExpiryRelease(tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload): Promise<void>;
|
|
2239
1982
|
/**
|
|
2240
|
-
* Report a reaper-force-failed paid job to the platform
|
|
1983
|
+
* Report a reaper-force-failed paid job to the platform so the
|
|
2241
1984
|
* operator receives an ambient notice that a DVM died or wedged mid-job.
|
|
2242
1985
|
* Best-effort and fire-and-forget: unlike {@link report} there is no local
|
|
2243
1986
|
* durable queue — a non-2xx or transport failure is structured-logged
|
|
@@ -2247,24 +1990,18 @@ declare class RevenueReporter {
|
|
|
2247
1990
|
*/
|
|
2248
1991
|
reportPaidJobDeath(payload: PaidJobDeathPayload): Promise<void>;
|
|
2249
1992
|
/**
|
|
2250
|
-
* Report a settled draw whose revenue event was skipped
|
|
1993
|
+
* Report a settled draw whose revenue event was skipped.
|
|
2251
1994
|
* Best-effort and fire-and-forget: the SDK's structured
|
|
2252
1995
|
* `revenue_skipped_no_rail` log is the durable backstop, so a failed notice
|
|
2253
1996
|
* must never wedge terminal handling or the orphan-draw reconciler.
|
|
2254
1997
|
*/
|
|
2255
1998
|
reportRevenueSkippedNoRail(payload: RevenueSkippedNoRailPayload): Promise<void>;
|
|
2256
|
-
/** Report the self-relay's Base gas gauge for platform-side paging and recovery. */
|
|
2257
1999
|
reportX402SelfRelayGasBalance(payload: X402SelfRelayGasBalancePayload): Promise<void>;
|
|
2258
|
-
/** Report redacted self-relay RPC health for platform-side paging and recovery. */
|
|
2259
2000
|
reportX402SelfRelayRpcHealth(payload: X402SelfRelayRpcHealthPayload): Promise<void>;
|
|
2260
|
-
/** Report the Tempo operator's fee-token gauge for paging and recovery. */
|
|
2261
2001
|
reportTempoSettlementBalance(payload: TempoSettlementBalancePayload): Promise<void>;
|
|
2262
|
-
/** Report a redacted operator fee rejection immediately. */
|
|
2263
2002
|
reportTempoSettlementFailure(payload: TempoSettlementFailurePayload): Promise<void>;
|
|
2264
2003
|
private postTempoSettlementReadiness;
|
|
2265
|
-
/** Start the background retry loop. */
|
|
2266
2004
|
startRetryLoop(): void;
|
|
2267
|
-
/** Stop the background retry loop. */
|
|
2268
2005
|
stop(): void;
|
|
2269
2006
|
/**
|
|
2270
2007
|
* Operator-facing queue snapshot: distinguishes the live retry queue from
|
|
@@ -2280,16 +2017,13 @@ declare class RevenueReporter {
|
|
|
2280
2017
|
/**
|
|
2281
2018
|
* Deliver one bounded batch from the durable queue.
|
|
2282
2019
|
*
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2020
|
+
* Processes due, non-stale rows; failures back off up to five minutes and
|
|
2021
|
+
* become terminal after 24 hours. Successful deliveries remove their rows.
|
|
2285
2022
|
*/
|
|
2286
2023
|
drainPending(): Promise<void>;
|
|
2287
|
-
/** Durably queue one report of either kind, then try it once immediately. */
|
|
2288
2024
|
private enqueue;
|
|
2289
|
-
/** Insert one pending report through either the pool or an open transaction. */
|
|
2290
2025
|
private insertPending;
|
|
2291
2026
|
private attemptDelivery;
|
|
2292
|
-
/** Mark a pending row terminal (stale) with a classification reason. */
|
|
2293
2027
|
private markStale;
|
|
2294
2028
|
}
|
|
2295
2029
|
|
|
@@ -2298,7 +2032,7 @@ type X402ChannelQuerier = Pick<Pool, "query">;
|
|
|
2298
2032
|
/**
|
|
2299
2033
|
* Durable phase of an irreversible x402 facilitator settlement.
|
|
2300
2034
|
*
|
|
2301
|
-
* `written_off` is the operator's terminal
|
|
2035
|
+
* `written_off` is the operator's terminal: a settlement whose
|
|
2302
2036
|
* ledger leg cannot be completed, reviewed and closed so it leaves the repair
|
|
2303
2037
|
* queue without pretending it booked. It is deliberately reversible — a later
|
|
2304
2038
|
* reconcile still accepts it, because it moved nothing.
|
|
@@ -2317,7 +2051,7 @@ interface X402SettlementIntent {
|
|
|
2317
2051
|
/**
|
|
2318
2052
|
* The channel as it stood when the settlement was handed to the facilitator —
|
|
2319
2053
|
* after the refund's mandatory on-chain refresh, which {@link channelBefore}
|
|
2320
|
-
* precedes
|
|
2054
|
+
* precedes.
|
|
2321
2055
|
*
|
|
2322
2056
|
* The facilitator sizes a cooperative refund from *live* state
|
|
2323
2057
|
* (`balance − chargedCumulativeAmount`) and signs it against the *live*
|
|
@@ -2363,7 +2097,7 @@ interface X402SettlementPage {
|
|
|
2363
2097
|
nextAfter?: X402SettlementCursor;
|
|
2364
2098
|
}
|
|
2365
2099
|
/**
|
|
2366
|
-
* Fleet-wide serialisation of the self-relay's chain submissions
|
|
2100
|
+
* Fleet-wide serialisation of the self-relay's chain submissions.
|
|
2367
2101
|
*
|
|
2368
2102
|
* Narrow on purpose, and implemented by {@link PostgresX402ChannelStorage}: the
|
|
2369
2103
|
* relay asks for one thing — exclusive use of its gas EOA for the length of one
|
|
@@ -2374,7 +2108,6 @@ interface X402SettlementPage {
|
|
|
2374
2108
|
interface X402RelaySubmissionLock {
|
|
2375
2109
|
withRelaySubmissionLock<T>(key: string, operation: () => Promise<T>): Promise<T>;
|
|
2376
2110
|
}
|
|
2377
|
-
/** Construction options for {@link PostgresX402ChannelStorage}. */
|
|
2378
2111
|
interface X402ChannelStorageOpts {
|
|
2379
2112
|
/**
|
|
2380
2113
|
* Override the relay submission lock's wait ceiling, in milliseconds.
|
|
@@ -2411,7 +2144,7 @@ interface X402RelayLockHolder {
|
|
|
2411
2144
|
* fault in the payment: a fleet-mate whose RPC is retrying holds the lock, and
|
|
2412
2145
|
* the same request works once it lets go. It refuses rather than proceeding —
|
|
2413
2146
|
* two machines submitting from one EOA collide on the nonce — and it refuses
|
|
2414
|
-
* rather than blocking, which is the
|
|
2147
|
+
* rather than blocking, which is the defect: the old acquire had no
|
|
2415
2148
|
* `lock_timeout`, so a wedged holder pinned a host-pool client and a channel
|
|
2416
2149
|
* advisory lock for as long as it took.
|
|
2417
2150
|
*/
|
|
@@ -2428,7 +2161,7 @@ interface X402SettlementWriteOff {
|
|
|
2428
2161
|
intent: X402SettlementIntent;
|
|
2429
2162
|
written: boolean;
|
|
2430
2163
|
}
|
|
2431
|
-
/** The refund standing between a channel and the credit it backs
|
|
2164
|
+
/** The refund standing between a channel and the credit it backs. */
|
|
2432
2165
|
interface X402UnresolvedRefund {
|
|
2433
2166
|
settlementId: string;
|
|
2434
2167
|
/** `${creditId}:${drainId}` — the drain this settlement belongs to. */
|
|
@@ -2436,7 +2169,7 @@ interface X402UnresolvedRefund {
|
|
|
2436
2169
|
status: X402SettlementStatus;
|
|
2437
2170
|
}
|
|
2438
2171
|
/**
|
|
2439
|
-
* The credit ledger's read into durable x402 settlement state
|
|
2172
|
+
* The credit ledger's read into durable x402 settlement state.
|
|
2440
2173
|
*
|
|
2441
2174
|
* Narrow on purpose: the ledger asks one question — is this channel's refund
|
|
2442
2175
|
* still unresolved — and must not acquire the ability to move a settlement.
|
|
@@ -2454,7 +2187,7 @@ interface X402RefundSettlementGate {
|
|
|
2454
2187
|
* transaction to the credit ledger: the fixed lock order is channel first,
|
|
2455
2188
|
* credit second, and no credit-ledger path calls back into this store.
|
|
2456
2189
|
*
|
|
2457
|
-
* Two pools, and the split is load-bearing
|
|
2190
|
+
* Two pools, and the split is load-bearing. The settlement markers
|
|
2458
2191
|
* must write *outside* the ledger transaction, which means a second connection
|
|
2459
2192
|
* taken while the transaction's client is still checked out — on the shared
|
|
2460
2193
|
* host pool that is a self-deadlock the moment concurrent deposits reach its
|
|
@@ -2475,7 +2208,6 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2475
2208
|
private readonly relayLockTimeoutMs;
|
|
2476
2209
|
private readonly terminalizeUnbackedCredit?;
|
|
2477
2210
|
constructor(pool: Pool, opts?: X402ChannelStorageOpts);
|
|
2478
|
-
/** Create the durable channel table under the SDK-wide DDL lock. */
|
|
2479
2211
|
init(): Promise<void>;
|
|
2480
2212
|
/**
|
|
2481
2213
|
* Read the fleet's durable "claimed funds are awaiting transfer" marker.
|
|
@@ -2487,7 +2219,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2487
2219
|
*
|
|
2488
2220
|
* Both accessors run on the settlement pool: every caller reads or writes the
|
|
2489
2221
|
* marker while {@link withFleetSettlementLock} holds a host-pool client, which
|
|
2490
|
-
* is the nesting the
|
|
2222
|
+
* is the nesting the split exists to keep off a non-terminal pool.
|
|
2491
2223
|
*/
|
|
2492
2224
|
isSettlePending(scope: string): Promise<boolean>;
|
|
2493
2225
|
/** Record whether a claim's funds still need a `settle(receiver, token)` transfer. */
|
|
@@ -2497,16 +2229,16 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2497
2229
|
*
|
|
2498
2230
|
* On the dedicated settlement pool like every other marker, and for a sharper
|
|
2499
2231
|
* reason than they had: this and {@link prepareSettlement} now run *inside*
|
|
2500
|
-
* the channel transaction
|
|
2232
|
+
* the channel transaction, which holds a host-pool client for its
|
|
2501
2233
|
* whole duration. Taking a second client from that same pool is the
|
|
2502
|
-
* self-deadlock
|
|
2234
|
+
* self-deadlock fixed — at pool max every client is a holder and a
|
|
2503
2235
|
* waiter — and moving these calls under the transaction is exactly what would
|
|
2504
2236
|
* reintroduce it.
|
|
2505
2237
|
*/
|
|
2506
2238
|
getSettlement(operation: "fund" | "refund", effectId: string): Promise<X402SettlementIntent | undefined>;
|
|
2507
2239
|
/**
|
|
2508
2240
|
* The refund on this channel that is neither finished nor abandoned, if there
|
|
2509
|
-
* is one — the credit ledger's spend gate
|
|
2241
|
+
* is one — the credit ledger's spend gate.
|
|
2510
2242
|
*
|
|
2511
2243
|
* `statuses` is the caller's, because the two gates disagree by design: a
|
|
2512
2244
|
* draw is blocked by {@link X402_SPEND_BLOCKING_SETTLEMENT_STATUSES}, a fresh
|
|
@@ -2514,12 +2246,12 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2514
2246
|
*
|
|
2515
2247
|
* On the dedicated settlement pool, and that is what makes the gate callable
|
|
2516
2248
|
* at all: a draw routinely runs inside an open channel transaction holding a
|
|
2517
|
-
* host-pool client, and taking a second one from that pool is the
|
|
2249
|
+
* host-pool client, and taking a second one from that pool is the
|
|
2518
2250
|
* self-deadlock. The oldest blocking row wins, so a channel that somehow
|
|
2519
2251
|
* accumulated two names the one an operator should reach for first.
|
|
2520
2252
|
*
|
|
2521
2253
|
* Projects three columns rather than decoding a whole {@link
|
|
2522
|
-
* X402SettlementIntent}: the decoder refuses a
|
|
2254
|
+
* X402SettlementIntent}: the decoder refuses a legacy row that carries
|
|
2523
2255
|
* no reconciliation snapshot, and a gate that throws on one would take every
|
|
2524
2256
|
* draw on that channel down with it — when the row is exactly the kind that
|
|
2525
2257
|
* ought to block them.
|
|
@@ -2527,7 +2259,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2527
2259
|
pendingRefundSettlement(channelId: string, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
|
|
2528
2260
|
/**
|
|
2529
2261
|
* Read one settlement by its own identifier — the handle an operator holds
|
|
2530
|
-
*
|
|
2262
|
+
* Same dedicated pool as {@link getSettlement}, for the same
|
|
2531
2263
|
* reason: the repair reads this while a channel transaction may be open.
|
|
2532
2264
|
*/
|
|
2533
2265
|
getSettlementById(settlementId: string): Promise<X402SettlementIntent | undefined>;
|
|
@@ -2535,7 +2267,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2535
2267
|
normalizeLegacyPaymentId(settlementId: string, operation: "fund" | "refund", legacyPaymentId: string, paymentId: string): Promise<X402SettlementIntent>;
|
|
2536
2268
|
/**
|
|
2537
2269
|
* The operator's repair queue: settlements stuck between "chain paid" and
|
|
2538
|
-
* "ledger booked", oldest write first
|
|
2270
|
+
* "ledger booked", oldest write first.
|
|
2539
2271
|
*
|
|
2540
2272
|
* Keyset-paginated on `(created_at, settlement_id)` for the reason the
|
|
2541
2273
|
* blocked-invoice queue is: a wedged row never self-clears, so a fixed first
|
|
@@ -2560,7 +2292,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2560
2292
|
after?: X402SettlementCursor;
|
|
2561
2293
|
}): Promise<X402SettlementPage>;
|
|
2562
2294
|
/**
|
|
2563
|
-
* Close a wedged settlement without booking anything
|
|
2295
|
+
* Close a wedged settlement without booking anything.
|
|
2564
2296
|
*
|
|
2565
2297
|
* A single-statement CAS on the wedged statuses, on the settlement pool —
|
|
2566
2298
|
* there is no ledger effect to be atomic with, which is the whole point:
|
|
@@ -2580,7 +2312,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2580
2312
|
* Mark that the next operation is the ambiguous external-call boundary.
|
|
2581
2313
|
*
|
|
2582
2314
|
* `channelAtSubmission` is the live channel the facilitator is about to size
|
|
2583
|
-
* and sign the settlement against
|
|
2315
|
+
* and sign the settlement against — bookmarked here, with the
|
|
2584
2316
|
* block height, because these two are the only description of that instant
|
|
2585
2317
|
* that survives the call.
|
|
2586
2318
|
*/
|
|
@@ -2602,12 +2334,11 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2602
2334
|
/**
|
|
2603
2335
|
* Mark the external settlement and ledger effect complete in the active transaction.
|
|
2604
2336
|
*
|
|
2605
|
-
* `resolvedAtMs` is set only by the operator repair
|
|
2337
|
+
* `resolvedAtMs` is set only by the operator repair, so an audit
|
|
2606
2338
|
* read can tell a settlement that completed on its own from one a human had
|
|
2607
2339
|
* to finish.
|
|
2608
2340
|
*/
|
|
2609
2341
|
completeSettlement(settlementId: string, resolvedAtMs?: number): Promise<void>;
|
|
2610
|
-
/** Load one channel snapshot. */
|
|
2611
2342
|
get(channelId: string): Promise<Channel | undefined>;
|
|
2612
2343
|
/** List all channel snapshots in stable channel-id order. */
|
|
2613
2344
|
list(): Promise<Channel[]>;
|
|
@@ -2629,7 +2360,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2629
2360
|
* this is how the settlement layer says "this exact decrease is the one the
|
|
2630
2361
|
* payer's verified refund voucher authorises". Outside this scope a decrease
|
|
2631
2362
|
* of `chargedCumulativeAmount` is refused flat, which is what keeps the
|
|
2632
|
-
* arbitrary-decrease guard meaningful
|
|
2363
|
+
* arbitrary-decrease guard meaningful.
|
|
2633
2364
|
*/
|
|
2634
2365
|
withSanctionedRefundBase<T>(base: string, operation: () => Promise<T>): Promise<T>;
|
|
2635
2366
|
withChannelTransaction<T>(channelId: string, operation: (tx: PoolClient) => Promise<T>): Promise<T>;
|
|
@@ -2639,7 +2370,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2639
2370
|
* The lock is session-scoped, so its client stays checked out for the whole
|
|
2640
2371
|
* operation — and `operation()` (upstream's claim/settle) re-enters the pool
|
|
2641
2372
|
* through {@link updateChannel}. Two bounds keep that from wedging the pool
|
|
2642
|
-
*
|
|
2373
|
+
* local callers are serialized in-process, so this holds at most
|
|
2643
2374
|
* one client per process no matter how many requests arrive, and a waiting
|
|
2644
2375
|
* acquire carries a `lock_timeout` so a wedged fleet-mate can't pin that
|
|
2645
2376
|
* client — and the caller's request — indefinitely. A timeout reports the
|
|
@@ -2652,7 +2383,7 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2652
2383
|
*/
|
|
2653
2384
|
withFleetSettlementLock(operation: () => Promise<unknown>, wait?: boolean): Promise<boolean>;
|
|
2654
2385
|
/**
|
|
2655
|
-
* Hold the fleet-wide self-relay submission lock for one chain submission
|
|
2386
|
+
* Hold the fleet-wide self-relay submission lock for one chain submission.
|
|
2656
2387
|
*
|
|
2657
2388
|
* The session lock keeps one per-DVM gas EOA to one in-flight submission
|
|
2658
2389
|
* across the whole fleet — two machines submitting at once collide on the
|
|
@@ -2683,7 +2414,6 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2683
2414
|
private updateInTransaction;
|
|
2684
2415
|
}
|
|
2685
2416
|
|
|
2686
|
-
/** Durable channel seam used to compose a settled draw with consumed Tempo value. */
|
|
2687
2417
|
interface TempoSessionSettlementStore {
|
|
2688
2418
|
withDrawPlacement<value>(channelId: string, operation: (tx: CreditLedgerQuerier, lifecycle: TempoSessionLifecycle$1) => Promise<value>, tx?: CreditLedgerQuerier): Promise<value>;
|
|
2689
2419
|
withTerminalConsumption<value>(channelId: string, operation: (tx: CreditLedgerQuerier, lifecycle: TempoSessionLifecycle$1) => Promise<{
|
|
@@ -2692,7 +2422,6 @@ interface TempoSessionSettlementStore {
|
|
|
2692
2422
|
consume: boolean;
|
|
2693
2423
|
}>): Promise<value>;
|
|
2694
2424
|
}
|
|
2695
|
-
/** Tempo channel state read while holding its durable row lock. */
|
|
2696
2425
|
interface TempoSessionLifecycle$1 {
|
|
2697
2426
|
finalized: boolean;
|
|
2698
2427
|
closeRequestedAt: bigint;
|
|
@@ -2708,7 +2437,6 @@ interface TempoCreditLossEvidence {
|
|
|
2708
2437
|
consumedNative: bigint;
|
|
2709
2438
|
observedAt?: number;
|
|
2710
2439
|
}
|
|
2711
|
-
/** Immutable audit record for one terminal, unbacked Tempo credit. */
|
|
2712
2440
|
interface TempoCreditLoss {
|
|
2713
2441
|
creditId: string;
|
|
2714
2442
|
channelId: string;
|
|
@@ -2728,7 +2456,6 @@ interface X402CreditLossEvidence {
|
|
|
2728
2456
|
totalClaimedNative: bigint;
|
|
2729
2457
|
observedAt?: number;
|
|
2730
2458
|
}
|
|
2731
|
-
/** Immutable audit record for one terminal, unbacked x402 credit. */
|
|
2732
2459
|
interface X402CreditLoss {
|
|
2733
2460
|
creditId: string;
|
|
2734
2461
|
channelId: string;
|
|
@@ -2741,300 +2468,115 @@ interface X402CreditLoss {
|
|
|
2741
2468
|
observedAt: number;
|
|
2742
2469
|
}
|
|
2743
2470
|
/**
|
|
2744
|
-
*
|
|
2745
|
-
*
|
|
2746
|
-
*
|
|
2747
|
-
* draws (`pending` at job-accept → `settled` on success / `released` on
|
|
2748
|
-
* failure), and a per-credit gap-free `ledger_seq` for the receipt evidence
|
|
2749
|
-
* chain (`ReceiptCredit` in `lib/receipt.ts`).
|
|
2750
|
-
*
|
|
2751
|
-
* Concurrency contract: **every write to `credit_draws` happens inside a
|
|
2752
|
-
* transaction that first takes `SELECT … FOR UPDATE` on the parent `credits`
|
|
2753
|
-
* row.** The pending-hold SUM that defines available balance is only stable
|
|
2754
|
-
* because of that lock — never add a `credit_draws` write path that skips it.
|
|
2755
|
-
* `ctx.store` / KV is prohibited for credit state: last-write-wins KV on a
|
|
2756
|
-
* multi-machine fleet loses money under concurrency (spec condition 2).
|
|
2471
|
+
* Every write to credit_draws takes SELECT … FOR UPDATE on its parent credit within the same
|
|
2472
|
+
* transaction. That lock keeps the pending-hold sum and available balance stable across
|
|
2473
|
+
* machines; KV storage cannot provide this money boundary.
|
|
2757
2474
|
*
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2760
|
-
*
|
|
2761
|
-
* column can be added by idempotent migration if per-mount ledgers are ever
|
|
2762
|
-
* needed.
|
|
2475
|
+
* The ledger is host-wide: credit_id and caller_pubkey identify rows, without a per-mount
|
|
2476
|
+
* dvm_id. Draws hold funds until settlement or release, and share a gap-free per-credit
|
|
2477
|
+
* ledger_seq with drains.
|
|
2763
2478
|
*/
|
|
2764
2479
|
declare class CreditLedger {
|
|
2765
2480
|
private readonly pool;
|
|
2766
2481
|
private readonly tempoSessionStore?;
|
|
2767
2482
|
/**
|
|
2768
2483
|
* Postgres-backed: balances survive a restart and are shared across the
|
|
2769
|
-
* fleet, so this DVM may advertise credit
|
|
2484
|
+
* fleet, so this DVM may advertise credit.
|
|
2770
2485
|
*/
|
|
2771
2486
|
readonly durable = true;
|
|
2772
2487
|
private x402Settlements?;
|
|
2773
2488
|
private expiryReleaseOutbox?;
|
|
2774
2489
|
constructor(pool: CreditLedgerPool, tempoSessionStore?: TempoSessionSettlementStore | undefined);
|
|
2775
2490
|
/**
|
|
2776
|
-
*
|
|
2777
|
-
*
|
|
2778
|
-
*
|
|
2779
|
-
* Late, because the settlement store is built with the batch-settlement
|
|
2780
|
-
* server, which is built *after* the ledger it reads earned draws from. An
|
|
2781
|
-
* unbound ledger simply does not gate — correct for a DVM with no x402
|
|
2782
|
-
* channel storage, where no settlement row can exist to wedge.
|
|
2491
|
+
* Bound after construction because the settlement store shares this ledger and is
|
|
2492
|
+
* initialized separately. Ledger effects and route preflights use the same refund gate.
|
|
2783
2493
|
*/
|
|
2784
2494
|
useX402SettlementGate(gate: X402RefundSettlementGate): void;
|
|
2785
2495
|
/**
|
|
2786
|
-
*
|
|
2787
|
-
*
|
|
2788
|
-
*
|
|
2789
|
-
* Held on the ledger rather than threaded through {@link fund} for the same
|
|
2790
|
-
* reason as the gate above: the revival half fires from *every* funding path
|
|
2791
|
-
* — Cashu commit, x402 exact and channel, Tempo, the Lightning invoice
|
|
2792
|
-
* settle, the implicit N=1 per-call payment — and a seam each of those has to
|
|
2793
|
-
* remember to pass is a seam one of them will eventually forget.
|
|
2794
|
-
*
|
|
2795
|
-
* Unbound (a `dvmctl dev` server, a self-hosted builder, a test host) the
|
|
2796
|
-
* sweep and the reversal still run and still write the ledger's own rows;
|
|
2797
|
-
* only the platform report is skipped. Reporting is bookkeeping and must
|
|
2798
|
-
* never gate the ledger.
|
|
2496
|
+
* Expiry release and revival reports join the ledger transaction. A caller-owned funding
|
|
2497
|
+
* transaction must not commit a reversal independently of its outbox row. Without this
|
|
2498
|
+
* wiring, ledger transitions still run.
|
|
2799
2499
|
*/
|
|
2800
2500
|
useCreditExpiryReleaseOutbox(outbox: CreditExpiryReleaseOutbox): void;
|
|
2801
|
-
/** Create the `credits` / `credit_draws` tables if absent. Call once at SDK boot. */
|
|
2802
2501
|
init(): Promise<void>;
|
|
2803
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
2804
2502
|
private createTables;
|
|
2805
2503
|
/**
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
*
|
|
2810
|
-
*
|
|
2811
|
-
*
|
|
2812
|
-
* The lot is derived from `credits.msats_remaining`, not replayed out of
|
|
2813
|
-
* `credit_fundings`. That is not a shortcut: `credit_fundings` records only
|
|
2814
|
-
* `{amount_micro, rail}` — no instrument amount at all — and is not written
|
|
2815
|
-
* on every funding path, whereas `msats_remaining` **is** the credit's
|
|
2816
|
-
* current unspent rail value, maintained pro rata on every draw since
|
|
2817
|
-
* internal-review. One synthetic lot pairing it with `balance_micro` therefore
|
|
2818
|
-
* reproduces today's in-kind position exactly, and FIFO over a single lot is
|
|
2819
|
-
* trivially correct. Historic per-funding rates are unrecoverable and would
|
|
2820
|
-
* change nothing: only the remaining position is owed.
|
|
2821
|
-
*
|
|
2822
|
-
* The semantics change is deliberately retroactive (internal-review) — every
|
|
2823
|
-
* current holder is first-party, so there is one regime and no legacy
|
|
2824
|
-
* branch.
|
|
2504
|
+
* Backfill an open non-channel Bitcoin credit only when it has no funding lot. Pair
|
|
2505
|
+
* balance_micro with current msats_remaining; credit_fundings lacks historical instrument
|
|
2506
|
+
* amounts and does not cover every funding path. Historical per-funding rates cannot be
|
|
2507
|
+
* recovered. Existing lots are left untouched, so this does not repair partial coverage
|
|
2508
|
+
* gaps.
|
|
2825
2509
|
*/
|
|
2826
2510
|
private backfillFundingLots;
|
|
2827
2511
|
/**
|
|
2828
|
-
*
|
|
2829
|
-
*
|
|
2830
|
-
*
|
|
2831
|
-
*
|
|
2832
|
-
* and `currency` must match or the fund is refused with a typed error —
|
|
2833
|
-
* without that check a top-up against someone else's `credit_id` would
|
|
2834
|
-
* silently merge two callers' money.
|
|
2512
|
+
* Funding adds balance and overwrites expiry, reviving an expired credit. It is additive,
|
|
2513
|
+
* not idempotent: the caller’s rail marker or funding record must prevent replay. Owner and
|
|
2514
|
+
* currency must match; rail and reusable-channel bindings cannot be mixed with other
|
|
2515
|
+
* instruments. A legacy NULL rail adopts its first typed top-up.
|
|
2835
2516
|
*
|
|
2836
|
-
*
|
|
2837
|
-
*
|
|
2838
|
-
*
|
|
2517
|
+
* With tx, all writes join the caller’s transaction without transaction control. Otherwise
|
|
2518
|
+
* the ledger owns a transaction covering balance, funding lot, reversal of standing expiry
|
|
2519
|
+
* releases and their reports. A partial revival would leave restored balance with a release
|
|
2520
|
+
* still counted.
|
|
2839
2521
|
*
|
|
2840
|
-
*
|
|
2841
|
-
*
|
|
2842
|
-
*
|
|
2843
|
-
* last leg restores balance and queues a report. A crash between the upsert
|
|
2844
|
-
* and the reversal would leave a revived credit whose release still stands,
|
|
2845
|
-
* which the sweep's own exclusion then makes permanent: `balance_micro > 0`
|
|
2846
|
-
* but a standing release means it is neither drainable nor re-releasable.
|
|
2847
|
-
*
|
|
2848
|
-
* `basis` records the rail value behind the fiat (internal-review) so each draw can
|
|
2849
|
-
* be allocated its share of the rail-native amount actually received. A
|
|
2850
|
-
* top-up on a different rail than the credit's is refused with
|
|
2851
|
-
* `rail_mismatch`: sats and USDC microunits aren't summable, so a blended
|
|
2852
|
-
* credit would have no coherent native basis to allocate from.
|
|
2853
|
-
*
|
|
2854
|
-
* It is **required** (internal-review), in the type and again at runtime via
|
|
2855
|
-
* {@link assertFundingBasis}. A basis-less fund wrote `rail = NULL`, and a
|
|
2856
|
-
* draw against such a credit settles — a real debit — while
|
|
2857
|
-
* `railAsFundingMethod` correctly declines to guess a rail, so `bookRevenue`
|
|
2858
|
-
* books nothing and outstanding liability (`deposits − draw revenue`) stays
|
|
2859
|
-
* overstated by the drawn amount forever. `credits.rail` stays nullable
|
|
2860
|
-
* because rows written before this change still exist in live databases:
|
|
2861
|
-
* **write is closed, read is not.** A legacy row adopts a rail from its first
|
|
2862
|
-
* typed top-up via the `COALESCE` below.
|
|
2863
|
-
*
|
|
2864
|
-
* The rail-native **instrument** is pinned at first funding too, and both
|
|
2865
|
-
* channel guards are symmetric for the same reason (internal-review). A reusable
|
|
2866
|
-
* channel — Tempo session or x402 batch-settlement — may only ever top up
|
|
2867
|
-
* the credit it opened, and a credit opened by a one-shot (a Tempo charge, an
|
|
2868
|
-
* x402 exact authorization) may never adopt one. The x402 half used to admit
|
|
2869
|
-
* `credits.x402_channel_id IS NULL`, so a channel deposit bound itself to a
|
|
2870
|
-
* credit already holding exact-funded value: `earnedNativeForX402Channel`
|
|
2871
|
-
* then read that credit's whole settled-draw history as the channel's
|
|
2872
|
-
* earnings, and the first claim tick swept a deposit nobody had consumed,
|
|
2873
|
-
* while the unspent balance lost every exit (the drain's refund base goes
|
|
2874
|
-
* below `totalClaimed`, and a channel-bound credit refuses every non-x402
|
|
2875
|
-
* drain). `credits_x402_channel_uidx` does not cover this: it guards
|
|
2876
|
-
* one-credit-per-channel, not one-source-per-credit.
|
|
2877
|
-
*
|
|
2878
|
-
* An x402 channel deposit is additionally refused `settlement_pending` while
|
|
2879
|
-
* that channel carries an unresolved refund (internal-review) — see the gate read
|
|
2880
|
-
* below. This method is the authority on that rule, as it is on the binding
|
|
2881
|
-
* rules above; every door preflights it where a refusal is still free.
|
|
2522
|
+
* An incoming x402 channel with an unresolved refund is refused before the upsert. That
|
|
2523
|
+
* preflight is not atomic with a concurrent cooperative drain; see the incoming-channel
|
|
2524
|
+
* gate below.
|
|
2882
2525
|
*/
|
|
2883
2526
|
fund(args: CreditFundArgs): Promise<CreditSnapshot>;
|
|
2884
|
-
/**
|
|
2885
|
-
* The funding itself, on whichever handle {@link fund} chose. Every refusal
|
|
2886
|
-
* it raises is the caller's to see unchanged; a self-opened transaction rolls
|
|
2887
|
-
* back around it.
|
|
2888
|
-
*/
|
|
2889
2527
|
private fundOn;
|
|
2890
2528
|
/**
|
|
2891
|
-
*
|
|
2892
|
-
*
|
|
2893
|
-
* release
|
|
2529
|
+
* Funding reverses standing expiry releases, restores their released micro and queues
|
|
2530
|
+
* reversal reports on the funding transaction. Do this even when the new expiry is already
|
|
2531
|
+
* past: the next sweep must release the enlarged remainder as a new episode.
|
|
2894
2532
|
*
|
|
2895
|
-
*
|
|
2896
|
-
*
|
|
2897
|
-
* overwrites `expiry_ms` (spec §5 carry-forward: the credit is a rolling
|
|
2898
|
-
* buffer), so the moment money arrives the recorded remainder is no longer
|
|
2899
|
-
* final: the release stops counting and its micro come back.
|
|
2900
|
-
*
|
|
2901
|
-
* Deliberately unconditional on the incoming expiry rather than gated on
|
|
2902
|
-
* `expiryMs > nowMs`. A release exists only for a credit that had already
|
|
2903
|
-
* lapsed, so any funding that reaches one is money the release did not
|
|
2904
|
-
* account for — and a funding that arrives with an already-past expiry leaves
|
|
2905
|
-
* a credit the very next sweep re-releases at its new, larger remainder. One
|
|
2906
|
-
* rule, self-healing in both directions, and it never needs to read the
|
|
2907
|
-
* pre-funding expiry the upsert has already overwritten.
|
|
2908
|
-
*
|
|
2909
|
-
* Runs on the funding's own handle — which {@link fund} now guarantees is a
|
|
2910
|
-
* transaction — so the reversal, the restored balance and the report commit
|
|
2911
|
-
* with the money or not at all. A reversal that committed without its report
|
|
2912
|
-
* would be unrecoverable: `WHERE reversed_at IS NULL` means no later pass
|
|
2913
|
-
* re-derives it.
|
|
2914
|
-
*
|
|
2915
|
-
* @returns the credit row as the restore left it, or undefined when there was
|
|
2916
|
-
* nothing to reverse. The caller reads its snapshot off this rather than off
|
|
2917
|
-
* the funding upsert's `RETURNING`, which predates the restore.
|
|
2533
|
+
* Returns the row after restoration, or undefined when no release stood. The upsert’s
|
|
2534
|
+
* RETURNING row predates the restoration and is not the final balance.
|
|
2918
2535
|
*/
|
|
2919
2536
|
private reverseExpiryReleases;
|
|
2920
2537
|
/**
|
|
2921
|
-
* Release
|
|
2922
|
-
*
|
|
2923
|
-
*
|
|
2924
|
-
* **What is released.** The credit's whole `balance_micro`. Expiry ends
|
|
2925
|
-
* spending but never ownership of the record, so the balance stays readable;
|
|
2926
|
-
* what ends is the caller's claim on it, and on the prepaid rails the value
|
|
2927
|
-
* behind it is already in builder custody.
|
|
2928
|
-
*
|
|
2929
|
-
* **Tempo is excluded**, at both of its markers (`tempo_channel_id` and a
|
|
2930
|
-
* `tempo` rail). A Tempo credit is channel-backed and its undrawn value
|
|
2931
|
-
* returns to the *caller* at channel exit, so releasing it would book the
|
|
2932
|
-
* builder money the chain is about to hand back.
|
|
2538
|
+
* Release expired undrawn balances, retaining readable credit records. Tempo rail or
|
|
2539
|
+
* channel-backed credits are excluded because their collateral returns to the caller
|
|
2540
|
+
* through the channel lifecycle.
|
|
2933
2541
|
*
|
|
2934
|
-
*
|
|
2935
|
-
*
|
|
2936
|
-
*
|
|
2937
|
-
*
|
|
2938
|
-
*
|
|
2939
|
-
* holds do resolve — the orphan-draw watchdog (internal-review) is what guarantees
|
|
2940
|
-
* a stranded one still reaches a terminal state.
|
|
2941
|
-
*
|
|
2942
|
-
* **Idempotent** two ways. `release_id` is derived from the expiry instant,
|
|
2943
|
-
* so a re-sweep after a crash between the insert and its report collides on
|
|
2944
|
-
* the same primary key instead of writing a second row; and the candidate
|
|
2945
|
-
* scan excludes any credit that already has an un-reversed release.
|
|
2946
|
-
*
|
|
2947
|
-
* Each candidate is re-checked under the credit row lock every
|
|
2948
|
-
* `credit_draws` write is taken under, so a draw racing the sweep either
|
|
2949
|
-
* loses the race (its credit is already released and it would have been
|
|
2950
|
-
* refused `credit_expired` anyway) or wins it and leaves a pending hold the
|
|
2951
|
-
* locked re-check sees.
|
|
2542
|
+
* Pending holds prevent release: jobs accepted before expiry may still settle or release.
|
|
2543
|
+
* Candidates are rechecked under the same parent-credit lock used by draw writes. The
|
|
2544
|
+
* balance debit, episode record and report commit together; standing releases exclude
|
|
2545
|
+
* repeat work. A later revival may reuse the expiry instant but receives a new release
|
|
2546
|
+
* sequence.
|
|
2952
2547
|
*/
|
|
2953
2548
|
sweepExpiredCredits(args?: {
|
|
2954
2549
|
nowMs?: number;
|
|
2955
|
-
/** Credits released per pass. Bounds the work a long-idle DVM does at boot. */
|
|
2956
2550
|
limit?: number;
|
|
2957
2551
|
}): Promise<{
|
|
2958
2552
|
released: CreditExpiryRelease[];
|
|
2959
2553
|
}>;
|
|
2960
2554
|
/**
|
|
2961
|
-
*
|
|
2962
|
-
*
|
|
2963
|
-
*
|
|
2964
|
-
* against, or already released between the scan and the lock.
|
|
2965
|
-
*
|
|
2966
|
-
* **Zeroing the balance is what makes the release real**, and it is the whole
|
|
2967
|
-
* reason this runs under the lock rather than as a bare INSERT. The row alone
|
|
2968
|
-
* records that the caller's claim ended; it does not *end* it. `requestDrain`
|
|
2969
|
-
* has no expiry check by design (expiry gates new draws, not reclaims), so a
|
|
2970
|
-
* credit whose remainder had been released and reported was still fully
|
|
2971
|
-
* drainable: the DVM would pay out money the platform had already booked as
|
|
2972
|
-
* the builder's, and `deposited - drawn - drained - released` would go
|
|
2973
|
-
* negative on the same micro. With the balance at zero, `requestDrain`'s
|
|
2974
|
-
* existing `availableMicro <= 0` guard refuses with `nothing_to_drain` and no
|
|
2975
|
-
* new check is needed anywhere. {@link reverseExpiryReleases} puts the
|
|
2976
|
-
* balance back when a funding revives the credit.
|
|
2555
|
+
* Recheck eligibility under the credit lock and zero the released balance in the same
|
|
2556
|
+
* transaction as its episode/report. Otherwise requestDrain, which intentionally ignores
|
|
2557
|
+
* expiry, could reclaim value already released to the builder.
|
|
2977
2558
|
*
|
|
2978
|
-
*
|
|
2979
|
-
*
|
|
2980
|
-
*
|
|
2981
|
-
* The zeroed balance also takes the credit out of `BITCOIN_CREDIT_PREDICATE`
|
|
2982
|
-
* (`balance_micro > 0`), so the in-kind sweep floor stops counting sats the
|
|
2983
|
-
* builder no longer owes back — and counts them again after a revival, with
|
|
2984
|
-
* the credit's funding lots untouched throughout.
|
|
2559
|
+
* Keep status active so future funding can revive it. Funding lots remain intact: balance
|
|
2560
|
+
* zero excludes this credit from Bitcoin liability, and reversal restores that liability.
|
|
2985
2561
|
*/
|
|
2986
2562
|
private releaseExpiredCreditLocked;
|
|
2987
2563
|
/**
|
|
2988
|
-
* Record
|
|
2989
|
-
*
|
|
2990
|
-
*
|
|
2991
|
-
* whatever transaction the rail opened — `withCommitTx` for Cashu,
|
|
2992
|
-
* `ProcessedPaymentStore.withTx` for x402/Tempo, `settleInvoice`'s own
|
|
2993
|
-
* `BEGIN` for Lightning. The funding-atomicity invariant therefore extends
|
|
2994
|
-
* to it structurally rather than by every call site remembering: a lot
|
|
2995
|
-
* cannot exist without its ledger credit, or the credit without its lot.
|
|
2996
|
-
*
|
|
2997
|
-
* Only a **channel-backed** credit writes no lot: it is in-kind already —
|
|
2998
|
-
* the escrow itself retains the unused value — so a lot on one would be
|
|
2999
|
-
* counted as Bitcoin deposit liability the hub does not hold.
|
|
2564
|
+
* Record non-channel Bitcoin funding lots on the funding transaction. An implicit payment
|
|
2565
|
+
* also needs a lot: its draw is initially only a hold, and failure releases the whole
|
|
2566
|
+
* balance for reclaim. Channel collateral is excluded from Bitcoin lots.
|
|
3000
2567
|
*
|
|
3001
|
-
*
|
|
3002
|
-
*
|
|
3003
|
-
* this transaction, so it looks like a lot born depleted — but the draw is a
|
|
3004
|
-
* *pending hold*, not a debit, and only a `completed` job settles it
|
|
3005
|
-
* (`JobManager.settlesDraw`). A failed, cancelled or stale-swept job
|
|
3006
|
-
* releases the hold and the whole funded amount becomes reclaimable balance,
|
|
3007
|
-
* so suppressing the lot here would leave routine traffic reclaiming off a
|
|
3008
|
-
* live rate and missing from the sweep floor. One small insert on the hot
|
|
3009
|
-
* path buys the invariant the liability read depends on: open lots cover
|
|
3010
|
-
* `balance_micro` for every Bitcoin credit, always.
|
|
3011
|
-
*
|
|
3012
|
-
* A zero-sats basis still writes a lot. It is honest — the credit exists and
|
|
3013
|
-
* we know its rail — and it is what makes "covered but unbacked" a
|
|
3014
|
-
* distinguishable state from "no lot at all" for the reclaim and the
|
|
3015
|
-
* liability rollup, instead of both reading as a gap.
|
|
2568
|
+
* A zero-sats basis still records a lot, distinguishing covered but basisless credit from a
|
|
2569
|
+
* missing-lot coverage gap.
|
|
3016
2570
|
*/
|
|
3017
2571
|
private recordFundingLot;
|
|
3018
2572
|
/**
|
|
3019
|
-
* Place a
|
|
3020
|
-
*
|
|
3021
|
-
*
|
|
3022
|
-
* `replayed: true` and places no second hold — this is how a caller recovers
|
|
3023
|
-
* a lost response (spec condition 1). A replay whose `amountMicro` or
|
|
3024
|
-
* `jobId` differs from the recorded draw is not a replay but a money bug in
|
|
3025
|
-
* the caller, refused with `draw_conflict`.
|
|
3026
|
-
*
|
|
3027
|
-
* The replay lookup runs **before** the expiry check, deliberately: a draw
|
|
3028
|
-
* placed pre-expiry must stay recoverable after the credit expires, or the
|
|
3029
|
-
* DVM holds a debit the caller can never reconcile. Fresh draws on an
|
|
3030
|
-
* expired credit are refused with `credit_expired`; the balance stays
|
|
3031
|
-
* intact and readable (spec §5: expiry ends spending, never ownership).
|
|
2573
|
+
* Place a pending hold with the next shared per-credit sequence. A matching (creditId,
|
|
2574
|
+
* drawId) replay returns its recorded tuple without another hold; conflicting amount or
|
|
2575
|
+
* jobId is draw_conflict.
|
|
3032
2576
|
*
|
|
3033
|
-
*
|
|
3034
|
-
*
|
|
3035
|
-
*
|
|
3036
|
-
* transaction control on `tx`; the `SELECT … FOR UPDATE` row lock is still
|
|
3037
|
-
* taken on the caller's transaction, so the locking invariant holds.
|
|
2577
|
+
* Replay precedes expiry and settlement gates so a lost response stays recoverable. Fresh
|
|
2578
|
+
* draws require spendable credit. With tx, take the parent-credit lock on the caller’s
|
|
2579
|
+
* transaction without issuing transaction control.
|
|
3038
2580
|
*/
|
|
3039
2581
|
draw(args: {
|
|
3040
2582
|
creditId: string;
|
|
@@ -3047,63 +2589,23 @@ declare class CreditLedger {
|
|
|
3047
2589
|
nowMs?: number;
|
|
3048
2590
|
}): Promise<DrawResult>;
|
|
3049
2591
|
/**
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
* `undefined` whenever there is nothing to ask — an unbound credit, or a DVM
|
|
3054
|
-
* with no durable settlement store, where a settlement row cannot exist.
|
|
3055
|
-
*
|
|
3056
|
-
* Public since internal-review, so `/v1/credit`'s pre-payment preflight can ask the
|
|
3057
|
-
* same question `fund` will ask from inside the rail transaction — one read,
|
|
3058
|
-
* one answer, rather than a second copy of the rule in the routes.
|
|
2592
|
+
* Return the unresolved refund that blocks an effect on this channel, or undefined. Route
|
|
2593
|
+
* preflights use the same rule before moving money; draw and drain also read it under the
|
|
2594
|
+
* credit lock.
|
|
3059
2595
|
*/
|
|
3060
2596
|
blockingX402Refund(channelId: string | null | undefined, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
|
|
3061
2597
|
/**
|
|
3062
|
-
* Grow
|
|
3063
|
-
*
|
|
3064
|
-
*
|
|
3065
|
-
* so the receipt's `ReceiptCredit` block countersigns the job's full cost
|
|
3066
|
-
* and `revenue_events` still books exactly one row per job.
|
|
3067
|
-
*
|
|
3068
|
-
* `draw()` cannot express this: a same-`draw_id` call with a different
|
|
3069
|
-
* amount is `draw_conflict` by design (a replayed draw must never re-price).
|
|
2598
|
+
* Grow an accepted job’s pending hold while preserving its original ledger_seq. The balance
|
|
2599
|
+
* tuple is updated at that sequence, so interleaved draws need not form a monotonic balance
|
|
2600
|
+
* trajectory. No expiry check applies to an already accepted job.
|
|
3070
2601
|
*
|
|
3071
|
-
*
|
|
3072
|
-
*
|
|
3073
|
-
* **every** pending hold, this draw's own included. That is deliberately the
|
|
3074
|
-
* conservative reading (a fresh draw of `addAmountMicro` would see exactly
|
|
3075
|
-
* the same figure), and it is what keeps the total held after growth inside
|
|
3076
|
-
* the credit's balance.
|
|
2602
|
+
* Available balance excludes every pending hold, including this one. An earmark allocates
|
|
2603
|
+
* the incoming funding’s rail value; absent one, growth uses the pool.
|
|
3077
2604
|
*
|
|
3078
|
-
*
|
|
3079
|
-
*
|
|
3080
|
-
*
|
|
3081
|
-
*
|
|
3082
|
-
*
|
|
3083
|
-
* **`ledger_seq` is not re-taken.** The per-credit sequence is gap-free
|
|
3084
|
-
* (`credit_draws_seq_uidx`), so moving this draw forward would strand its
|
|
3085
|
-
* original number. The consequence is deliberate and worth knowing: the
|
|
3086
|
-
* recorded `balance_after_micro` is the post-growth figure stamped at the
|
|
3087
|
-
* *original* seq, so a credit with interleaved sibling activity has a
|
|
3088
|
-
* trajectory that is truthful per draw rather than monotonic across seqs.
|
|
3089
|
-
*
|
|
3090
|
-
* **No expiry check**, matching `settle`/`release`: the job was accepted
|
|
3091
|
-
* pre-expiry and is still running. Refusing here would strand the handler
|
|
3092
|
-
* mid-flight over a clock the caller can't influence.
|
|
3093
|
-
*
|
|
3094
|
-
* **Not idempotent by itself.** Growth carries no key of its own — it
|
|
3095
|
-
* inherits the rail's (the accumulator's `(dvm_id, request_id)` UNIQUE rolls
|
|
3096
|
-
* the whole transaction back on replay; x402/mpp collide on the
|
|
3097
|
-
* `processed_payments` marker). Never call it outside a rail commit.
|
|
3098
|
-
*
|
|
3099
|
-
* **`capMicro` bounds cumulative growth at what the job cumulatively asked
|
|
3100
|
-
* (internal-review)**, and it is the only ceiling that can refuse a top-up: the
|
|
3101
|
-
* available-balance check cannot, because the `fund` a moment earlier in
|
|
3102
|
-
* this same transaction raised the balance by exactly the amount being
|
|
3103
|
-
* drawn. Excess is **granted partially or not at all rather than thrown** —
|
|
3104
|
-
* this runs inside the rail commit, so a throw would roll back cashu proofs
|
|
3105
|
-
* the caller already sent. The refused remainder stays funded balance the
|
|
3106
|
-
* caller owns and can drain.
|
|
2605
|
+
* Growth has no independent replay key: compose it with the rail’s replay protection in one
|
|
2606
|
+
* transaction. capMicro bounds cumulative growth at the job’s cumulative ask. Excess
|
|
2607
|
+
* remains caller-owned funded balance; partial or zero growth returns that excess rather
|
|
2608
|
+
* than throwing and rolling back accepted proofs.
|
|
3107
2609
|
*/
|
|
3108
2610
|
growDraw(args: {
|
|
3109
2611
|
creditId: string;
|
|
@@ -3111,7 +2613,7 @@ declare class CreditLedger {
|
|
|
3111
2613
|
addAmountMicro: number;
|
|
3112
2614
|
/**
|
|
3113
2615
|
* Rail value that funded this growth, earmarked to the increment instead
|
|
3114
|
-
* of allocated pro-rata
|
|
2616
|
+
* of allocated pro-rata. Pass it whenever `addAmountMicro` is
|
|
3115
2617
|
* money that landed in this same transaction; omit it when the growth
|
|
3116
2618
|
* spends balance the credit already held.
|
|
3117
2619
|
*/
|
|
@@ -3121,7 +2623,7 @@ declare class CreditLedger {
|
|
|
3121
2623
|
};
|
|
3122
2624
|
/**
|
|
3123
2625
|
* Ceiling on this draw's **cumulative** growth, in fiat micro of the
|
|
3124
|
-
* credit's currency
|
|
2626
|
+
* credit's currency — the job's total mid-job ask. Growth is
|
|
3125
2627
|
* clipped to `capMicro - grown_micro`; a second payment for an ask the
|
|
3126
2628
|
* first already satisfied gets zero. Omit it only when the caller genuinely
|
|
3127
2629
|
* has no ask total to bound against; growth is then unbounded, as before.
|
|
@@ -3155,7 +2657,7 @@ declare class CreditLedger {
|
|
|
3155
2657
|
}): Promise<DrawResolution>;
|
|
3156
2658
|
/**
|
|
3157
2659
|
* Release a pending draw: the hold evaporates, the balance is untouched —
|
|
3158
|
-
* this is how "no debit on job failure" is mechanically real (
|
|
2660
|
+
* this is how "no debit on job failure" is mechanically real (the credit lifecycle contract).
|
|
3159
2661
|
* Idempotent: releasing a released draw returns the original tuple. A
|
|
3160
2662
|
* settled draw cannot be released (`invalid_draw_state`) — un-settling
|
|
3161
2663
|
* booked revenue is a reconciliation problem, not a ledger verb.
|
|
@@ -3167,18 +2669,9 @@ declare class CreditLedger {
|
|
|
3167
2669
|
nowMs?: number;
|
|
3168
2670
|
}): Promise<DrawResolution>;
|
|
3169
2671
|
/**
|
|
3170
|
-
* Record
|
|
3171
|
-
*
|
|
3172
|
-
*
|
|
3173
|
-
* loses with a typed `funding_replayed` rather than crediting twice.
|
|
3174
|
-
*
|
|
3175
|
-
* Call it inside the same `tx` as {@link fund} — the rail commit, the
|
|
3176
|
-
* funding record, and the balance increment must land together or not at
|
|
3177
|
-
* all (spec §2 condition 3). Like `fund`, this issues no transaction
|
|
3178
|
-
* control on `tx`; it is a single statement.
|
|
3179
|
-
*
|
|
3180
|
-
* This does **not** move money on its own. `fund` still does the crediting;
|
|
3181
|
-
* this row is what lets a retry be answered with the original outcome.
|
|
2672
|
+
* Record the fund-only replay key without moving money. A duplicate (credit_id, fund_id)
|
|
2673
|
+
* raises funding_replayed. Compose this insert, fund and rail acceptance on the same
|
|
2674
|
+
* caller-owned transaction; a retry reads the recorded funding outcome.
|
|
3182
2675
|
*/
|
|
3183
2676
|
recordFunding(args: {
|
|
3184
2677
|
creditId: string;
|
|
@@ -3216,7 +2709,7 @@ declare class CreditLedger {
|
|
|
3216
2709
|
receipt: FundingReceipt;
|
|
3217
2710
|
}): Promise<FundingReceipt>;
|
|
3218
2711
|
/**
|
|
3219
|
-
* Record the bolt11 issued for a `(creditId, fundId)` top-up
|
|
2712
|
+
* Record the bolt11 issued for a `(creditId, fundId)` top-up, or
|
|
3220
2713
|
* return the one already issued for it.
|
|
3221
2714
|
*
|
|
3222
2715
|
* **Returning the existing row is the point.** A caller re-polling an unpaid
|
|
@@ -3237,7 +2730,6 @@ declare class CreditLedger {
|
|
|
3237
2730
|
expiresAt: number;
|
|
3238
2731
|
nowMs?: number;
|
|
3239
2732
|
}): Promise<CreditInvoiceRecord>;
|
|
3240
|
-
/** Read one invoice (no lock). */
|
|
3241
2733
|
getInvoice(args: {
|
|
3242
2734
|
creditId: string;
|
|
3243
2735
|
fundId: string;
|
|
@@ -3253,35 +2745,22 @@ declare class CreditLedger {
|
|
|
3253
2745
|
limit: number;
|
|
3254
2746
|
}): Promise<CreditInvoiceRecord[]>;
|
|
3255
2747
|
/**
|
|
3256
|
-
*
|
|
3257
|
-
*
|
|
3258
|
-
*
|
|
3259
|
-
*
|
|
3260
|
-
* One transaction covers the funding record, the balance, and the invoice's
|
|
3261
|
-
* own status flip, so the three can never disagree. A crash anywhere inside
|
|
3262
|
-
* leaves the invoice `pending` and the balance untouched: the next
|
|
3263
|
-
* `lookup_invoice` observes the same settled payment and credits it, once.
|
|
3264
|
-
* A replayed check after the commit sees `settled` and moves nothing.
|
|
3265
|
-
*
|
|
3266
|
-
* The lock is on the **invoice** row, not the credit row: the first
|
|
3267
|
-
* Lightning top-up is what opens the credit, so there is frequently no
|
|
3268
|
-
* `credits` row to take `FOR UPDATE` yet. That does not weaken the ledger's
|
|
3269
|
-
* concurrency contract — no `credit_draws` write happens here, and `fund`'s
|
|
3270
|
-
* upsert is a single statement — while still serialising two settlement
|
|
3271
|
-
* checks racing on the same invoice.
|
|
2748
|
+
* Serialize settlement on the invoice row, which exists before the first credit does.
|
|
2749
|
+
* Funding record, balance, invoice status and optional deposit outbox commit together. An
|
|
2750
|
+
* interrupted transaction leaves the prior invoice status intact for retry; a settled replay
|
|
2751
|
+
* moves no money.
|
|
3272
2752
|
*/
|
|
3273
2753
|
settleInvoice(args: {
|
|
3274
2754
|
creditId: string;
|
|
3275
2755
|
fundId: string;
|
|
3276
2756
|
expiryMs: number;
|
|
3277
2757
|
/**
|
|
3278
|
-
*
|
|
3279
|
-
* by internal-review — it is what `fund` writes to `credits.rail`.
|
|
2758
|
+
* Funding rail value pinned when this invoice was issued.
|
|
3280
2759
|
*/
|
|
3281
2760
|
basis: CreditFundingBasis;
|
|
3282
2761
|
/**
|
|
3283
2762
|
* Reporter-owned deposit outbox write. Runs on the invoice transaction
|
|
3284
|
-
* after the ledger/status writes and before COMMIT
|
|
2763
|
+
* after the ledger/status writes and before COMMIT.
|
|
3285
2764
|
*/
|
|
3286
2765
|
depositOutbox?: {
|
|
3287
2766
|
enqueue: CreditDepositEnqueue;
|
|
@@ -3300,39 +2779,20 @@ declare class CreditLedger {
|
|
|
3300
2779
|
fundId: string;
|
|
3301
2780
|
}): Promise<CreditInvoiceRecord | undefined>;
|
|
3302
2781
|
/**
|
|
3303
|
-
*
|
|
3304
|
-
*
|
|
3305
|
-
*
|
|
3306
|
-
* expired invoice was never paid and owes nobody anything, whereas a blocked
|
|
3307
|
-
* one has sats sitting in the builder's wallet with no ledger row to put them
|
|
3308
|
-
* on. Only an operator can resolve that, so the row records `reason` and
|
|
3309
|
-
* stops being swept — leaving it `pending` would spend one of the sweep's few
|
|
3310
|
-
* slots re-deriving the same verdict on every request the caller makes, and
|
|
3311
|
-
* three of them would wedge that caller's sweep so a genuinely payable
|
|
3312
|
-
* invoice behind them never credits.
|
|
3313
|
-
*
|
|
3314
|
-
* A CAS on `pending`, like the expiry retirement: a settlement observed
|
|
3315
|
-
* concurrently must win.
|
|
2782
|
+
* Mark a paid invoice blocked when its payment cannot be credited. This removes it from the
|
|
2783
|
+
* bounded pending sweep and leaves its reason for operator repair. The pending-only CAS
|
|
2784
|
+
* cannot overwrite a committed settlement.
|
|
3316
2785
|
*/
|
|
3317
2786
|
markInvoiceBlocked(args: {
|
|
3318
2787
|
creditId: string;
|
|
3319
2788
|
fundId: string;
|
|
3320
2789
|
reason: string;
|
|
3321
2790
|
}): Promise<CreditInvoiceRecord | undefined>;
|
|
3322
|
-
/** Read one invoice by the wallet's own identifier for the payment (no lock). */
|
|
3323
2791
|
getInvoiceByPaymentHash(paymentHash: string): Promise<CreditInvoiceRecord | undefined>;
|
|
3324
2792
|
/**
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
3327
|
-
*
|
|
3328
|
-
* self-clear, so a row the operator declines to act on sits at the head of
|
|
3329
|
-
* the age ordering forever; a fixed first page would starve everything
|
|
3330
|
-
* behind it on every run, which is the internal-review shape one page up.
|
|
3331
|
-
*
|
|
3332
|
-
* `includeResolved` widens to the operator-resolved statuses so a run can be
|
|
3333
|
-
* audited after the fact — the acceptance criterion this verb exists for.
|
|
3334
|
-
* Host-wide, like `listPendingDrains`: the ledger has no `dvm_id`, so on a
|
|
3335
|
-
* multi-mount host one builder's admin credential reads every mount's rows.
|
|
2793
|
+
* Host-wide operator queue, keyset-paginated so an untouched blocked row does not starve
|
|
2794
|
+
* later rows. includeResolved also exposes reconciled and written-off history; the ledger
|
|
2795
|
+
* has no per-mount dvm_id.
|
|
3336
2796
|
*/
|
|
3337
2797
|
listBlockedInvoices(args: {
|
|
3338
2798
|
limit: number;
|
|
@@ -3340,44 +2800,22 @@ declare class CreditLedger {
|
|
|
3340
2800
|
includeResolved?: boolean;
|
|
3341
2801
|
}): Promise<CreditInvoiceRecord[]>;
|
|
3342
2802
|
/**
|
|
3343
|
-
* Apply a blocked invoice
|
|
3344
|
-
*
|
|
3345
|
-
*
|
|
3346
|
-
*
|
|
3347
|
-
* same money doing the same thing a different way: one transaction covering
|
|
3348
|
-
* the funding record, the balance, the invoice's status flip and the deposit
|
|
3349
|
-
* outbox row, so the four can never disagree. Three deliberate differences:
|
|
2803
|
+
* Apply a blocked or written-off invoice to an operator-named credit. Lock the invoice by
|
|
2804
|
+
* its unique payment_hash before funding the credit, matching settlement’s
|
|
2805
|
+
* invoice-to-credit lock order. Funding, balance, resolution and optional outbox share one
|
|
2806
|
+
* transaction.
|
|
3350
2807
|
*
|
|
3351
|
-
*
|
|
3352
|
-
*
|
|
3353
|
-
*
|
|
3354
|
-
*
|
|
3355
|
-
* the same order `settleInvoice` takes, which is what keeps a reconcile and
|
|
3356
|
-
* a concurrent settlement check from deadlocking against each other.
|
|
3357
|
-
* - **The funding lands at `(targetCreditId, paymentHash)`, not the invoice's
|
|
3358
|
-
* own `(credit_id, fund_id)`.** That key is frequently the reason the row
|
|
3359
|
-
* is blocked at all — `funding_replayed_on_cashu` means something else
|
|
3360
|
-
* already holds it — and the operator's most natural target is that very
|
|
3361
|
-
* credit. The payment hash cannot collide with it, and it makes all three
|
|
3362
|
-
* references to this payment agree: `basis.fundingRef`, the deposit's
|
|
3363
|
-
* `funding_id`, and the funding row's `fund_id`.
|
|
3364
|
-
* - **`written_off` is an accepted input status.** A write-off unwound
|
|
3365
|
-
* nothing, so an operator who closed a row by mistake must not need raw SQL
|
|
3366
|
-
* against a money table to reopen it.
|
|
3367
|
-
*
|
|
3368
|
-
* The invoice row's status — never the funding row — is the idempotency
|
|
3369
|
-
* source of truth. `fund_id` is caller-chosen and the payment hash is
|
|
3370
|
-
* disclosed to them, so a caller *can* squat `(theirCredit, thatHash)` with a
|
|
3371
|
-
* top-up on another rail; when they have, `recordFunding` throws
|
|
3372
|
-
* `funding_replayed` and that surfaces as its own loud failure rather than
|
|
3373
|
-
* being absorbed as "already reconciled".
|
|
2808
|
+
* The repair funding key is (targetCreditId, paymentHash), distinct from the original
|
|
2809
|
+
* caller-chosen fundId. Invoice status determines replay. A caller can still occupy the
|
|
2810
|
+
* repair key with another payment; funding_replayed must remain a refusal, not be absorbed
|
|
2811
|
+
* as already reconciled. Preserve blocked_reason and the wallet’s settlement instant.
|
|
3374
2812
|
*/
|
|
3375
2813
|
reconcileBlockedInvoice(args: {
|
|
3376
2814
|
paymentHash: string;
|
|
3377
2815
|
/** Credit the money lands on. The caller identity comes off the invoice row. */
|
|
3378
2816
|
targetCreditId: string;
|
|
3379
2817
|
expiryMs: number;
|
|
3380
|
-
/** Rail-value basis, rebuilt from the invoice row by the caller
|
|
2818
|
+
/** Rail-value basis, rebuilt from the invoice row by the caller. */
|
|
3381
2819
|
basis: CreditFundingBasis;
|
|
3382
2820
|
/** The wallet's settlement instant, re-observed by the operator's verb. */
|
|
3383
2821
|
settledAtMs?: number;
|
|
@@ -3388,17 +2826,9 @@ declare class CreditLedger {
|
|
|
3388
2826
|
nowMs?: number;
|
|
3389
2827
|
}): Promise<InvoiceReconciliation>;
|
|
3390
2828
|
/**
|
|
3391
|
-
*
|
|
3392
|
-
*
|
|
3393
|
-
*
|
|
3394
|
-
* A CAS on `blocked`, and idempotent: re-running returns the recorded row
|
|
3395
|
-
* rather than overwriting the first note. It cannot capture a `reconciled`
|
|
3396
|
-
* row, because that one moved money.
|
|
3397
|
-
*
|
|
3398
|
-
* `written` reports whether *this* call was the transition. The caller has
|
|
3399
|
-
* no other way to tell — the row reads identically either way — and the
|
|
3400
|
-
* write-off's audit log is the only record of the amount, so a re-emitted
|
|
3401
|
-
* one reads as a second decision about the same sats.
|
|
2829
|
+
* Write off an invoice without crediting or moving money. Only the first transition records
|
|
2830
|
+
* the note; its written bit controls the audit event. An operator can later reconcile a
|
|
2831
|
+
* written-off invoice, because write-off did not return its payment.
|
|
3402
2832
|
*/
|
|
3403
2833
|
writeOffBlockedInvoice(args: {
|
|
3404
2834
|
paymentHash: string;
|
|
@@ -3413,38 +2843,24 @@ declare class CreditLedger {
|
|
|
3413
2843
|
getForCaller(callerPubkey: string, opts?: {
|
|
3414
2844
|
nowMs?: number;
|
|
3415
2845
|
}): Promise<CreditSnapshot[]>;
|
|
3416
|
-
/** Read one draw (no lock), with its credit's rail basis joined in. */
|
|
3417
2846
|
getDraw(args: {
|
|
3418
2847
|
creditId: string;
|
|
3419
2848
|
drawId: string;
|
|
3420
2849
|
}): Promise<DrawRecord | undefined>;
|
|
3421
2850
|
/** Read the sole draw bound to a pre-allocated job id; ambiguity fails closed. */
|
|
3422
2851
|
getDrawByJobId(jobId: string): Promise<DrawRecord | undefined>;
|
|
3423
|
-
/** Read every draw linked to a job, including already-resolved rows. */
|
|
3424
2852
|
listDrawsByJobId(jobId: string): Promise<DrawRecord[]>;
|
|
3425
2853
|
/**
|
|
3426
2854
|
* All pending holds on a credit, in `ledger_seq` order (no lock). Read
|
|
3427
|
-
* surface for the
|
|
2855
|
+
* surface for the sweeper wiring — a worker that dies mid-job
|
|
3428
2856
|
* leaves its hold `pending` until something releases it.
|
|
3429
2857
|
*/
|
|
3430
2858
|
listPendingDraws(creditId: string): Promise<DrawRecord[]>;
|
|
3431
2859
|
/**
|
|
3432
|
-
*
|
|
3433
|
-
*
|
|
3434
|
-
*
|
|
3435
|
-
*
|
|
3436
|
-
* resolve — the only release path keys off the `credit_id`/`draw_id` written
|
|
3437
|
-
* on the job row.
|
|
3438
|
-
*
|
|
3439
|
-
* Host-wide like {@link listPendingDrains}, not per-credit: the sweeper has
|
|
3440
|
-
* no candidate credit to start from. Age-bounded so it never sees a draw
|
|
3441
|
-
* whose job row is merely still in flight.
|
|
3442
|
-
*
|
|
3443
|
-
* `limit` is required and `after` keyset-paginates: an aged hold whose job
|
|
3444
|
-
* row *does* exist is legitimately un-sweepable and stays `pending`
|
|
3445
|
-
* indefinitely, so a caller that re-issues the same first page would never
|
|
3446
|
-
* see past a `limit`-sized wall of them. Pass the last row's
|
|
3447
|
-
* {@link StalePendingDrawCursor} to advance.
|
|
2860
|
+
* Page host-wide pending holds older than createdBeforeMs by (created_at, credit_id,
|
|
2861
|
+
* ledger_seq). Use the same compound key for ordering and continuation, preserving
|
|
2862
|
+
* same-millisecond rows. Terminal resolution still rechecks state under the parent-credit
|
|
2863
|
+
* lock.
|
|
3448
2864
|
*/
|
|
3449
2865
|
listStalePendingDraws(args: {
|
|
3450
2866
|
createdBeforeMs: number;
|
|
@@ -3452,38 +2868,18 @@ declare class CreditLedger {
|
|
|
3452
2868
|
after?: StalePendingDrawCursor;
|
|
3453
2869
|
}): Promise<DrawRecord[]>;
|
|
3454
2870
|
/**
|
|
3455
|
-
*
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3458
|
-
* ceiling (internal-review): a channel is claimable up to what its credit's draws
|
|
3459
|
-
* have earned, never up to the deposit that funded it.
|
|
3460
|
-
*
|
|
3461
|
-
* `undefined` when no credit is bound to the channel, which the claim job
|
|
3462
|
-
* treats as "cannot be resolved" and claims nothing for — distinct from a
|
|
3463
|
-
* resolved credit that has earned `0`.
|
|
3464
|
-
*
|
|
3465
|
-
* Deliberately a sum over settled draws rather than `funded − remaining`:
|
|
3466
|
-
* a drain lowers `native_remaining` without earning anything, so the
|
|
3467
|
-
* subtraction would read the caller's own reclaim as revenue. Pending holds
|
|
3468
|
-
* are excluded for the mirror reason — a hold is work in flight, and
|
|
3469
|
-
* claiming against it would take money a release still owes back.
|
|
2871
|
+
* Sum settled draws’ native value for the credit bound to this x402 channel.
|
|
2872
|
+
* Pending/released holds and drains earn nothing. Return undefined for an unbound channel,
|
|
2873
|
+
* distinct from earned zero, so settlement cannot guess a claim limit.
|
|
3470
2874
|
*/
|
|
3471
2875
|
earnedNativeForX402Channel(channelId: string): Promise<number | undefined>;
|
|
3472
2876
|
/**
|
|
3473
|
-
*
|
|
3474
|
-
*
|
|
3475
|
-
*
|
|
3476
|
-
* The operator repair's entry point: a wedged settlement row carries the
|
|
3477
|
-
* channel and an `effect_id`, and this is what turns them back into the
|
|
3478
|
-
* credit id, the caller pubkey, and the drain id the effect encodes. Deriving
|
|
3479
|
-
* the credit id by splitting `effect_id` instead would be wrong — credit ids
|
|
3480
|
-
* carry their own colons (`imp:`, `fnd:` and the rail segment inside them).
|
|
2877
|
+
* Resolve the unique credit bound to the channel. The ID is opaque and can contain colons;
|
|
2878
|
+
* do not reconstruct it by splitting settlement effect IDs.
|
|
3481
2879
|
*/
|
|
3482
2880
|
getCreditByX402Channel(channelId: string): Promise<CreditSnapshot | undefined>;
|
|
3483
|
-
/** Retire every active credit backed by a chain-proven lost Tempo channel. */
|
|
3484
2881
|
terminalizeTempoCredits(evidence: TempoCreditLossEvidence, tx?: CreditLedgerQuerier): Promise<TempoCreditLoss[]>;
|
|
3485
2882
|
private terminalizeTempoCreditsLocked;
|
|
3486
|
-
/** List terminal Tempo credit losses, newest observation first. */
|
|
3487
2883
|
listTempoCreditLosses(args?: {
|
|
3488
2884
|
limit?: number;
|
|
3489
2885
|
}): Promise<TempoCreditLoss[]>;
|
|
@@ -3498,36 +2894,21 @@ declare class CreditLedger {
|
|
|
3498
2894
|
*/
|
|
3499
2895
|
terminalizeX402Credit(evidence: X402CreditLossEvidence, tx?: CreditLedgerQuerier): Promise<X402CreditLoss[]>;
|
|
3500
2896
|
private terminalizeX402CreditLocked;
|
|
3501
|
-
/** List terminal x402 credit losses, newest observation first. */
|
|
3502
2897
|
listX402CreditLosses(args?: {
|
|
3503
2898
|
limit?: number;
|
|
3504
2899
|
}): Promise<X402CreditLoss[]>;
|
|
3505
2900
|
/**
|
|
3506
|
-
*
|
|
3507
|
-
*
|
|
3508
|
-
*
|
|
3509
|
-
*
|
|
3510
|
-
* is decremented in the same transaction, and the drain takes the next
|
|
3511
|
-
* gap-free `ledger_seq` — so the balance zeroes exactly once however many
|
|
3512
|
-
* machines race the request.
|
|
2901
|
+
* Move all available balance to a drain liability under the credit lock; pending holds stay
|
|
2902
|
+
* reserved. Expiry alone does not prevent reclaim before a sweep releases the balance.
|
|
2903
|
+
* Repeating (creditId, drainId) returns the recorded liability; owner, method and payout
|
|
2904
|
+
* conflicts refuse.
|
|
3513
2905
|
*
|
|
3514
|
-
*
|
|
3515
|
-
*
|
|
3516
|
-
*
|
|
3517
|
-
* silently honouring a changed payout would ship the caller's money to a
|
|
3518
|
-
* destination they never signed alongside this `drain_id`.
|
|
3519
|
-
*
|
|
3520
|
-
* Pending holds are NOT drained — in-flight jobs keep their money until
|
|
3521
|
-
* they settle or release. A release after a drain restores available
|
|
3522
|
-
* balance the caller reclaims with a **new** `drain_id`; drains are ledger
|
|
3523
|
-
* arithmetic, not a terminal credit state, so `status` stays `active`.
|
|
3524
|
-
*
|
|
3525
|
-
* Deliberately **no expiry check**: draining works before and after
|
|
3526
|
-
* expiry. The one thing expiry gates is new draws.
|
|
2906
|
+
* An unresolved channel refund blocks fresh drain IDs but admits its own effect ID for
|
|
2907
|
+
* repair. Caller-supplied payout validation belongs to the route. With tx, join the channel
|
|
2908
|
+
* transaction without controlling it.
|
|
3527
2909
|
*/
|
|
3528
2910
|
requestDrain(args: {
|
|
3529
2911
|
creditId: string;
|
|
3530
|
-
/** Client-generated drain id from the signed request body. */
|
|
3531
2912
|
drainId: string;
|
|
3532
2913
|
/** Verified caller pubkey — a drain may only be requested by the owner. */
|
|
3533
2914
|
callerPubkey: string;
|
|
@@ -3540,37 +2921,22 @@ declare class CreditLedger {
|
|
|
3540
2921
|
tx?: CreditLedgerQuerier;
|
|
3541
2922
|
nowMs?: number;
|
|
3542
2923
|
}): Promise<DrainRequestResult>;
|
|
3543
|
-
/** Read one drain (no lock), joined with its credit for currency + owner. */
|
|
3544
2924
|
getDrain(args: {
|
|
3545
2925
|
creditId: string;
|
|
3546
2926
|
drainId: string;
|
|
3547
2927
|
}): Promise<CreditDrainRecord | undefined>;
|
|
3548
2928
|
/**
|
|
3549
|
-
* One credit's undepleted funding lots, oldest first
|
|
2929
|
+
* One credit's undepleted funding lots, oldest first — the
|
|
3550
2930
|
* public read behind the reclaim's own arithmetic, for anything that needs
|
|
3551
2931
|
* to show its work.
|
|
3552
2932
|
*/
|
|
3553
2933
|
listFundingLots(creditId: string): Promise<FundingLot[]>;
|
|
3554
2934
|
/**
|
|
3555
|
-
*
|
|
3556
|
-
*
|
|
3557
|
-
*
|
|
3558
|
-
*
|
|
3559
|
-
*
|
|
3560
|
-
* exchange-rate lookup anywhere: the obligation is in kind, so a rate has
|
|
3561
|
-
* nothing to say about it. Pending job holds are deliberately **not**
|
|
3562
|
-
* subtracted — a hold either settles into revenue or releases back to the
|
|
3563
|
-
* caller, and until it does, treating it as already earned would understate
|
|
3564
|
-
* the floor. The figure is conservative by exactly that amount.
|
|
3565
|
-
*
|
|
3566
|
-
* The two coverage holes ride along rather than being folded in silently, so
|
|
3567
|
-
* a gap reads as a gap: `uncovered` is a credit whose lots fall short of its
|
|
3568
|
-
* balance, `unbacked` is one holding any open micro with no sats basis. A
|
|
3569
|
-
* reclaim on either prices the **whole** balance off a live rate — the
|
|
3570
|
-
* depletion refuses a mixture rather than blending two bases — so both are
|
|
3571
|
-
* reported as the whole balance, and neither contributes to `depositSats`.
|
|
3572
|
-
* A figure that counted the backed half of a mixed credit would be a floor
|
|
3573
|
-
* the payout never matches.
|
|
2935
|
+
* Host-wide in-kind Bitcoin deposit liability, excluding channel collateral. Credits with
|
|
2936
|
+
* missing coverage or any basisless lot are reported as gaps and use the live-rate fallback
|
|
2937
|
+
* rather than a guessed sats obligation. Group fiat micro by currency; never sum
|
|
2938
|
+
* denominations.
|
|
2939
|
+
* Pending job holds remain liabilities until settled because they may release to the caller.
|
|
3574
2940
|
*/
|
|
3575
2941
|
bitcoinDepositLiability(): Promise<BitcoinDepositLiability>;
|
|
3576
2942
|
/**
|
|
@@ -3583,46 +2949,21 @@ declare class CreditLedger {
|
|
|
3583
2949
|
*/
|
|
3584
2950
|
listPendingDrains(): Promise<CreditDrainRecord[]>;
|
|
3585
2951
|
/**
|
|
3586
|
-
*
|
|
3587
|
-
*
|
|
3588
|
-
*
|
|
3589
|
-
*
|
|
3590
|
-
* never self-clears, so a caller that re-issues the same first page would
|
|
3591
|
-
* never see past a `limit`-sized wall of them. The keyset is
|
|
3592
|
-
* `(created_at, credit_id, drain_id)` — `created_at` is immutable, so no
|
|
3593
|
-
* concurrent write can push an unexamined row past the cursor. It is a
|
|
3594
|
-
* BIGINT of epoch milliseconds rather than a `TIMESTAMPTZ`, so the
|
|
3595
|
-
* microsecond-vs-millisecond cursor trap that bit `listSettlements` cannot
|
|
3596
|
-
* arise here: the value round-trips as the integer it already is.
|
|
3597
|
-
*
|
|
3598
|
-
* `createdBeforeMs` is the staleness floor. A healthy cooperative close
|
|
3599
|
-
* moves the drain to `sent` inside the request that opened it, so anything
|
|
3600
|
-
* younger is in flight rather than stuck.
|
|
3601
|
-
*
|
|
3602
|
-
* The method filter is `'tempo'`, and it is the one line here worth a second
|
|
3603
|
-
* look: `init()` migrates the pre-internal-review `'mpp'` spelling away and
|
|
3604
|
-
* `DrainMethod` no longer carries it, so a query naming it matches nothing a
|
|
3605
|
-
* DVM has ever written. It named it anyway until internal-review's rename sweep and
|
|
3606
|
-
* internal-review's live rung caught it independently — the whole operator queue
|
|
3607
|
-
* read empty in production while every route test stayed green, because
|
|
3608
|
-
* those run on `MemoryCreditLedger`, which filters on the typed value. The
|
|
3609
|
-
* two implementations of this one predicate must be read together.
|
|
3610
|
-
*
|
|
3611
|
-
* A written-off row is excluded unless `includeResolved`: the operator has
|
|
3612
|
-
* already decided about it, and leaving it on the live queue would re-serve
|
|
3613
|
-
* a closed decision on every walk.
|
|
2952
|
+
* Page pending Tempo-channel drains by (created_at, credit_id, drain_id), optionally
|
|
2953
|
+
* age-bounded, excluding written-off rows. Accept the legacy mpp stored method as well as
|
|
2954
|
+
* tempo so pre-migration drains remain repairable. Use the compound key for both ordering
|
|
2955
|
+
* and continuation.
|
|
3614
2956
|
*/
|
|
3615
2957
|
listChannelDrains(args: {
|
|
3616
2958
|
limit: number;
|
|
3617
2959
|
after?: ChannelDrainCursor;
|
|
3618
2960
|
createdBeforeMs: number;
|
|
3619
|
-
/** Which channel binding to walk. Today only Tempo has a repair. */
|
|
3620
2961
|
rail: "tempo";
|
|
3621
2962
|
includeResolved?: boolean;
|
|
3622
2963
|
}): Promise<CreditDrainRecord[]>;
|
|
3623
2964
|
/**
|
|
3624
2965
|
* Record that an operator reviewed a floor-refused channel drain and is not
|
|
3625
|
-
* booking it
|
|
2966
|
+
* booking it. Books nothing anywhere — no status change, no
|
|
3626
2967
|
* balance movement, no platform report.
|
|
3627
2968
|
*
|
|
3628
2969
|
* A single-statement CAS on `written_off_at IS NULL`, so the first note on
|
|
@@ -3680,7 +3021,7 @@ declare class CreditLedger {
|
|
|
3680
3021
|
creditId: string;
|
|
3681
3022
|
drainId: string;
|
|
3682
3023
|
token: string;
|
|
3683
|
-
/** Cost evidence for a just-in-time mint
|
|
3024
|
+
/** Cost evidence for a just-in-time mint; absent on an accumulator park. */
|
|
3684
3025
|
fulfilment?: DrainFulfilment;
|
|
3685
3026
|
tx?: CreditLedgerQuerier;
|
|
3686
3027
|
nowMs?: number;
|
|
@@ -3691,7 +3032,7 @@ declare class CreditLedger {
|
|
|
3691
3032
|
* loss, so the caller's re-poll keeps returning the same token either way.
|
|
3692
3033
|
*
|
|
3693
3034
|
* Returns `replayed` because this is a **terminal** transition and the money
|
|
3694
|
-
* has left at exactly one of these calls
|
|
3035
|
+
* has left at exactly one of these calls: the platform drain
|
|
3695
3036
|
* report must be emitted by that caller and no other. Pass `tx` to write the
|
|
3696
3037
|
* report through the same transaction as the CAS.
|
|
3697
3038
|
*/
|
|
@@ -3722,11 +3063,9 @@ declare class CreditLedger {
|
|
|
3722
3063
|
nowMs?: number;
|
|
3723
3064
|
}): Promise<DrainTransitionResult>;
|
|
3724
3065
|
/**
|
|
3725
|
-
* Append
|
|
3726
|
-
*
|
|
3727
|
-
*
|
|
3728
|
-
* transition is the source of truth; the receipt is the proof the caller
|
|
3729
|
-
* can carry away).
|
|
3066
|
+
* Append an opaque supplied receipt observation to the drain’s JSONB evidence array.
|
|
3067
|
+
* Issuance and authentication belong to callers/consumers; this store does not verify
|
|
3068
|
+
* signatures. Preserve append order on replay/poll reads.
|
|
3730
3069
|
*/
|
|
3731
3070
|
appendDrainReceipt(args: {
|
|
3732
3071
|
creditId: string;
|
|
@@ -3735,35 +3074,17 @@ declare class CreditLedger {
|
|
|
3735
3074
|
tx?: CreditLedgerQuerier;
|
|
3736
3075
|
}): Promise<void>;
|
|
3737
3076
|
private readDrain;
|
|
3738
|
-
/** Re-read the credit join fields for a freshly-updated drain row. */
|
|
3739
3077
|
private rejoin;
|
|
3740
|
-
/** Shared refusal tail for the CAS transitions: idempotent replay or typed error. */
|
|
3741
3078
|
private refuseTransition;
|
|
3742
3079
|
/**
|
|
3743
|
-
*
|
|
3744
|
-
*
|
|
3745
|
-
*
|
|
3746
|
-
*
|
|
3747
|
-
* lots have to shrink with it or the deposit-liability rollup would keep
|
|
3748
|
-
* counting a balance that is gone.
|
|
3749
|
-
*
|
|
3750
|
-
* `owedSats` is `null` where {@link isInKindDepletion} refuses — the lots
|
|
3751
|
-
* are short of the balance, or the covering lots carry no sats basis (a
|
|
3752
|
-
* credit funded before internal-review). That row is priced off a live rate by the
|
|
3753
|
-
* admin surface instead, which is what *every* row did before this change,
|
|
3754
|
-
* so the fallback is the old behaviour rather than a new failure mode.
|
|
3755
|
-
*
|
|
3756
|
-
* What it publishes is **net of the delivery reserve** (internal-review): handing a
|
|
3757
|
-
* refund over costs a mint fee and, when the notes have to be minted just in
|
|
3758
|
-
* time, a Lightning hop. The caller carries that cost per internal-review, and a
|
|
3759
|
-
* flat reserve is how they carry it without the figure moving — an
|
|
3760
|
-
* actual-fee true-up could only be applied after the promise was made. The
|
|
3761
|
-
* debits stay gross because they are denominated in micro and are what
|
|
3762
|
-
* `releaseDrain` restores; only the sats figure is netted.
|
|
3080
|
+
* Deplete FIFO lots even when they cannot price the reclaim; stale open lots must not
|
|
3081
|
+
* remain behind a debited balance. Publish owedSats only with complete positive sats
|
|
3082
|
+
* coverage, net of one delivery reserve. Otherwise preserve null for a live-rate fallback.
|
|
3083
|
+
* Returned debits allow an unpaid drain release to restore the exact lots.
|
|
3763
3084
|
*/
|
|
3764
3085
|
private reclaimInKind;
|
|
3765
3086
|
/**
|
|
3766
|
-
* This credit's undepleted funding lots, oldest first
|
|
3087
|
+
* This credit's undepleted funding lots, oldest first.
|
|
3767
3088
|
*
|
|
3768
3089
|
* Ordered in SQL on the same `(created_at, lot_id)` key the partial index
|
|
3769
3090
|
* carries, so FIFO is the index's own order rather than something a reader
|
|
@@ -3779,7 +3100,6 @@ declare class CreditLedger {
|
|
|
3779
3100
|
* as every other balance move is.
|
|
3780
3101
|
*/
|
|
3781
3102
|
private applyLotDebits;
|
|
3782
|
-
/** Shared settle/release state machine — see the public JSDoc on each verb. */
|
|
3783
3103
|
private resolveDraw;
|
|
3784
3104
|
private resolveDrawLocked;
|
|
3785
3105
|
/**
|
|
@@ -3788,13 +3108,12 @@ declare class CreditLedger {
|
|
|
3788
3108
|
* success (harmless for read-only replay paths), rolls back on throw.
|
|
3789
3109
|
*/
|
|
3790
3110
|
private withCreditLock;
|
|
3791
|
-
/** `SELECT … FOR UPDATE` the credit row on `q` (which must be inside a transaction). */
|
|
3792
3111
|
private lockCreditRow;
|
|
3793
3112
|
private readDraw;
|
|
3794
3113
|
/**
|
|
3795
3114
|
* Held-but-unresolved totals for a credit, in all three units at once — the
|
|
3796
3115
|
* fiat micro that defines available balance plus the rail-value remainders
|
|
3797
|
-
* a new draw allocates against
|
|
3116
|
+
* a new draw allocates against. One round-trip, since every
|
|
3798
3117
|
* caller needs the micro sum anyway.
|
|
3799
3118
|
*/
|
|
3800
3119
|
private pendingSums;
|
|
@@ -3803,24 +3122,20 @@ declare class CreditLedger {
|
|
|
3803
3122
|
private readX402CreditLosses;
|
|
3804
3123
|
}
|
|
3805
3124
|
/**
|
|
3806
|
-
*
|
|
3807
|
-
*
|
|
3808
|
-
* the
|
|
3809
|
-
* tests / pool-less `createDVMServer` setups, mirroring the `MemoryJobStore`
|
|
3810
|
-
* precedent). The `tx` handles on `fund`/`draw` are meaningful only on the
|
|
3811
|
-
* Postgres implementation; the memory ledger ignores them — its operations
|
|
3812
|
-
* are synchronous-atomic in-process, so there is no rail transaction to join.
|
|
3125
|
+
* Implemented by durable Postgres and per-process memory ledgers. Production funding menus
|
|
3126
|
+
* require durable storage; memory is for explicit development and tests. Mutations accepting
|
|
3127
|
+
* tx join the caller’s transaction; memory implementations ignore that handle.
|
|
3813
3128
|
*/
|
|
3814
3129
|
interface CreditLedgerLike {
|
|
3815
3130
|
/**
|
|
3816
3131
|
* True when balances survive a restart and are visible fleet-wide
|
|
3817
|
-
*
|
|
3132
|
+
* The funding menu is suppressed on a non-durable ledger
|
|
3818
3133
|
* outside devMode: a caller who funds on machine A and draws on machine B
|
|
3819
3134
|
* would get `credit_not_found`, so advertising credit there misadvertises.
|
|
3820
3135
|
*/
|
|
3821
3136
|
readonly durable: boolean;
|
|
3822
3137
|
/**
|
|
3823
|
-
* Bind the durable x402 settlement state spending is gated on
|
|
3138
|
+
* Bind the durable x402 settlement state spending is gated on.
|
|
3824
3139
|
*
|
|
3825
3140
|
* Optional because a host may wire a ledger implementation that has no such
|
|
3826
3141
|
* gate; the SDK's own two both do. See
|
|
@@ -3829,7 +3144,6 @@ interface CreditLedgerLike {
|
|
|
3829
3144
|
useX402SettlementGate?(gate: X402RefundSettlementGate): void;
|
|
3830
3145
|
/**
|
|
3831
3146
|
* Bind the outbox that reports credit-expiry releases and their revivals
|
|
3832
|
-
* (internal-review).
|
|
3833
3147
|
*
|
|
3834
3148
|
* Optional, and absent on `MemoryCreditLedger`: an expiry release is a
|
|
3835
3149
|
* durable liability fact, and a ledger whose balances do not survive a
|
|
@@ -3838,7 +3152,7 @@ interface CreditLedgerLike {
|
|
|
3838
3152
|
*/
|
|
3839
3153
|
useCreditExpiryReleaseOutbox?(outbox: CreditExpiryReleaseOutbox): void;
|
|
3840
3154
|
/**
|
|
3841
|
-
* Release the undrawn remainder of every credit past its TTL
|
|
3155
|
+
* Release the undrawn remainder of every credit past its TTL.
|
|
3842
3156
|
* Optional on the same terms as the binder above. See
|
|
3843
3157
|
* {@link CreditLedger.sweepExpiredCredits}.
|
|
3844
3158
|
*/
|
|
@@ -3850,7 +3164,7 @@ interface CreditLedgerLike {
|
|
|
3850
3164
|
}>;
|
|
3851
3165
|
/**
|
|
3852
3166
|
* Ask that gate directly, so a route can refuse a wedged channel **before**
|
|
3853
|
-
* the rail moves anything
|
|
3167
|
+
* the rail moves anything. Optional on the same terms as the
|
|
3854
3168
|
* binder above; an implementation without it simply gets no preflight, and
|
|
3855
3169
|
* the ledger effect stays the authority. See
|
|
3856
3170
|
* {@link CreditLedger.blockingX402Refund}.
|
|
@@ -3999,9 +3313,7 @@ interface CreditLedgerLike {
|
|
|
3999
3313
|
creditId: string;
|
|
4000
3314
|
drawId: string;
|
|
4001
3315
|
}): Promise<DrawRecord | undefined>;
|
|
4002
|
-
/** Read the sole draw linked to a stable pre-payment job id. */
|
|
4003
3316
|
getDrawByJobId(jobId: string): Promise<DrawRecord | undefined>;
|
|
4004
|
-
/** Read every draw linked to a job so retention can resolve all holds before redaction. */
|
|
4005
3317
|
listDrawsByJobId(jobId: string): Promise<DrawRecord[]>;
|
|
4006
3318
|
listPendingDraws(creditId: string): Promise<DrawRecord[]>;
|
|
4007
3319
|
listStalePendingDraws(args: {
|
|
@@ -4034,9 +3346,8 @@ interface CreditLedgerLike {
|
|
|
4034
3346
|
drainId: string;
|
|
4035
3347
|
}): Promise<CreditDrainRecord | undefined>;
|
|
4036
3348
|
listPendingDrains(): Promise<CreditDrainRecord[]>;
|
|
4037
|
-
/** See {@link CreditLedger.bitcoinDepositLiability} — the in-kind deposit floor
|
|
3349
|
+
/** See {@link CreditLedger.bitcoinDepositLiability} — the in-kind deposit floor. */
|
|
4038
3350
|
bitcoinDepositLiability(): Promise<BitcoinDepositLiability>;
|
|
4039
|
-
/** One credit's undepleted funding lots, oldest first (internal-review). */
|
|
4040
3351
|
listFundingLots(creditId: string): Promise<FundingLot[]>;
|
|
4041
3352
|
listChannelDrains(args: {
|
|
4042
3353
|
limit: number;
|
|
@@ -4098,14 +3409,12 @@ interface CreditLedgerLike {
|
|
|
4098
3409
|
interface CreditLedgerQuerier {
|
|
4099
3410
|
query: Pool["query"];
|
|
4100
3411
|
}
|
|
4101
|
-
/** Reporter outbox joined to a draw release's own transaction (internal-review). */
|
|
4102
3412
|
interface CreditDrawReleaseOutbox {
|
|
4103
3413
|
/** Platform DVM record ID bound into the report. */
|
|
4104
3414
|
dvmId: string;
|
|
4105
3415
|
/** Reporter-owned insert executed through the ledger transaction. */
|
|
4106
3416
|
enqueue: CreditDrawReleaseEnqueue;
|
|
4107
3417
|
}
|
|
4108
|
-
/** Reporter seam for credit-expiry releases and the revivals that reverse them (internal-review). */
|
|
4109
3418
|
interface CreditExpiryReleaseOutbox {
|
|
4110
3419
|
/** Platform DVM record ID bound into the report. */
|
|
4111
3420
|
dvmId: string;
|
|
@@ -4113,16 +3422,15 @@ interface CreditExpiryReleaseOutbox {
|
|
|
4113
3422
|
enqueue: CreditExpiryReleaseEnqueue;
|
|
4114
3423
|
}
|
|
4115
3424
|
/**
|
|
4116
|
-
* One
|
|
4117
|
-
*
|
|
4118
|
-
*
|
|
4119
|
-
* `reversedAt` is set when a later funding revived the credit, at which point
|
|
4120
|
-
* the release stops counting against committed value — the row stays as the
|
|
4121
|
-
* record that it once did.
|
|
3425
|
+
* One committed expiry episode. The sweep releases the undrawn remainder; later funding sets
|
|
3426
|
+
* reversedAt and restores that release on the same transaction. Multiple episodes may share
|
|
3427
|
+
* an expiry instant.
|
|
4122
3428
|
*/
|
|
4123
3429
|
interface CreditExpiryRelease {
|
|
4124
3430
|
creditId: string;
|
|
4125
|
-
/**
|
|
3431
|
+
/**
|
|
3432
|
+
* Key derived from expiry instant and the release sequence counted under the credit lock.
|
|
3433
|
+
*/
|
|
4126
3434
|
releaseId: string;
|
|
4127
3435
|
callerPubkey: string;
|
|
4128
3436
|
currency: string;
|
|
@@ -4130,31 +3438,29 @@ interface CreditExpiryRelease {
|
|
|
4130
3438
|
rail: string | null;
|
|
4131
3439
|
/** Fiat micro released, 1e-6 of `currency`. */
|
|
4132
3440
|
amountMicro: number;
|
|
4133
|
-
/**
|
|
3441
|
+
/**
|
|
3442
|
+
* Nominal credit expiry, ms since epoch; the claim is released only when the sweep commits.
|
|
3443
|
+
*/
|
|
4134
3444
|
expiryMs: number;
|
|
4135
|
-
/**
|
|
3445
|
+
/**
|
|
3446
|
+
* When the sweep recorded this release, ms since epoch.
|
|
3447
|
+
*/
|
|
4136
3448
|
releasedAt: number;
|
|
4137
3449
|
/** When a funding revived the credit and undid this release. */
|
|
4138
3450
|
reversedAt: number | null;
|
|
4139
3451
|
}
|
|
4140
|
-
/** Pool-shaped handle (`query` + `connect`). Required for the locked write paths. */
|
|
4141
3452
|
type CreditLedgerPool = Pick<Pool, "query" | "connect">;
|
|
4142
3453
|
/**
|
|
4143
|
-
*
|
|
4144
|
-
*
|
|
4145
|
-
*
|
|
3454
|
+
* unbacked credit is unavailable for spending or reclaim while channel collateral is gone. An
|
|
3455
|
+
* x402 credit with no remaining liability can be restored to active by channel
|
|
3456
|
+
* reconciliation.
|
|
4146
3457
|
*/
|
|
4147
3458
|
type CreditStatus = "active" | "unbacked";
|
|
4148
3459
|
/** Draw lifecycle: two-phase hold → terminal settle/release. */
|
|
4149
3460
|
type DrawStatus = "pending" | "settled" | "released";
|
|
4150
3461
|
/**
|
|
4151
|
-
*
|
|
4152
|
-
*
|
|
4153
|
-
* Direct Lightning payout is deliberately absent (internal-review / internal-review): a
|
|
4154
|
-
* first-party deployment holds no send-capable Lightning credential by design
|
|
4155
|
-
* (the funding NWC is receive-only, internal-review), so P2PK-locked Cashu notes are
|
|
4156
|
-
* the only non-channel Bitcoin reclaim path. Adding `lightning` back means
|
|
4157
|
-
* re-deciding that, not just widening a union.
|
|
3462
|
+
* Drain payout rails. Direct Lightning send is excluded: deployments reclaiming Bitcoin can
|
|
3463
|
+
* use Cashu notes without putting a spending wallet credential in the DVM.
|
|
4158
3464
|
*/
|
|
4159
3465
|
type DrainMethod = "cashu" | "x402" | "tempo";
|
|
4160
3466
|
/**
|
|
@@ -4167,7 +3473,7 @@ type DrainMethod = "cashu" | "x402" | "tempo";
|
|
|
4167
3473
|
*/
|
|
4168
3474
|
type DrainStatus = "pending" | "parked" | "picked_up" | "sent" | "released";
|
|
4169
3475
|
/**
|
|
4170
|
-
* The sats a DVM owes back on undrawn non-channel Bitcoin credit
|
|
3476
|
+
* The sats a DVM owes back on undrawn non-channel Bitcoin credit —
|
|
4171
3477
|
* the deposit pot inside the hub's balance, which the operator's sweep may
|
|
4172
3478
|
* never bank below.
|
|
4173
3479
|
*/
|
|
@@ -4193,16 +3499,24 @@ interface BitcoinDepositLiability {
|
|
|
4193
3499
|
}[];
|
|
4194
3500
|
/** Credits holding balance no funding lot backs — a defect, not a liability figure. */
|
|
4195
3501
|
uncoveredCredits: number;
|
|
4196
|
-
/**
|
|
4197
|
-
|
|
3502
|
+
/** Whole balances of short-covered credits, by ledger currency; reclaim uses a live rate. */
|
|
3503
|
+
uncoveredByCurrency: {
|
|
3504
|
+
currency: string;
|
|
3505
|
+
micro: number;
|
|
3506
|
+
credits: number;
|
|
3507
|
+
}[];
|
|
4198
3508
|
/** Credits holding any open micro with no sats basis, so their reclaim prices off a live rate. */
|
|
4199
3509
|
unbackedCredits: number;
|
|
4200
|
-
/**
|
|
4201
|
-
|
|
3510
|
+
/** Whole balances of basisless credits, by ledger currency. */
|
|
3511
|
+
unbackedByCurrency: {
|
|
3512
|
+
currency: string;
|
|
3513
|
+
micro: number;
|
|
3514
|
+
credits: number;
|
|
3515
|
+
}[];
|
|
4202
3516
|
}
|
|
4203
3517
|
/**
|
|
4204
3518
|
* What it cost to put a just-in-time-minted refund in the caller's hands
|
|
4205
|
-
*
|
|
3519
|
+
* itemized on the drain row so the spend is auditable against the
|
|
4206
3520
|
* hub's own transaction history long after the worker's log has rotated.
|
|
4207
3521
|
*
|
|
4208
3522
|
* `faceSats` is what the caller receives and equals the drain's published
|
|
@@ -4236,16 +3550,9 @@ interface CreditDrainRecord {
|
|
|
4236
3550
|
currency: string;
|
|
4237
3551
|
method: DrainMethod;
|
|
4238
3552
|
/**
|
|
4239
|
-
*
|
|
4240
|
-
*
|
|
4241
|
-
*
|
|
4242
|
-
* The method alone does not say who settles a reclaim: an `mpp` or `x402`
|
|
4243
|
-
* drain on a channel-bound credit is closed by the channel itself, and a
|
|
4244
|
-
* `pending` one is routinely mid-reconciliation rather than owed by anybody
|
|
4245
|
-
* — `completeTempoDrain` leaves every ambiguous close pending on purpose for
|
|
4246
|
-
* `recoverTempoSessionClose` to finish on the caller's next poll. Carrying
|
|
4247
|
-
* the binding on the record is what lets a reader tell that apart from a
|
|
4248
|
-
* one-payment reclaim only a person can settle (internal-review).
|
|
3553
|
+
* Reusable Tempo or x402 channel backing this drain, or null for one-payment/non-channel
|
|
3554
|
+
* credit. The channel’s cooperative lifecycle owns fulfillment; the operator’s manual sent
|
|
3555
|
+
* route refuses channel-bound drains.
|
|
4249
3556
|
*/
|
|
4250
3557
|
channelId: string | null;
|
|
4251
3558
|
/** Method-specific payout target as signed by the caller. */
|
|
@@ -4257,16 +3564,14 @@ interface CreditDrainRecord {
|
|
|
4257
3564
|
* `null` on a rail that reports no native basis. This — not `amountMicro` —
|
|
4258
3565
|
* is what an on-chain payout must be sized against, because the fiat figure
|
|
4259
3566
|
* and the rail figure drift with the rate between funding and drain
|
|
4260
|
-
* (internal-review).
|
|
4261
3567
|
*/
|
|
4262
3568
|
drainedNative: number | null;
|
|
4263
3569
|
/**
|
|
4264
|
-
*
|
|
4265
|
-
*
|
|
4266
|
-
*
|
|
4267
|
-
*
|
|
4268
|
-
*
|
|
4269
|
-
* to a live rate on the admin surface.
|
|
3570
|
+
* In-kind sats obligation fixed at request time: the decrease in FIFO lots’ remaining
|
|
3571
|
+
* floored obligations, net of one delivery reserve. A non-null obligation is not repriced.
|
|
3572
|
+
* Null on unpriceable non-channel Bitcoin refunds requires the live-rate Cashu fallback.
|
|
3573
|
+
* Channel and stablecoin refunds also use null: they have no sats obligation and follow
|
|
3574
|
+
* their native/channel payout lifecycle, using `drainedNative` where applicable.
|
|
4270
3575
|
*/
|
|
4271
3576
|
owedSats: number | null;
|
|
4272
3577
|
/**
|
|
@@ -4283,7 +3588,7 @@ interface CreditDrainRecord {
|
|
|
4283
3588
|
/** Settlement evidence, present on `sent`. */
|
|
4284
3589
|
sentRef: Record<string, unknown> | null;
|
|
4285
3590
|
/**
|
|
4286
|
-
* How a parked refund was sourced and what delivering it cost
|
|
3591
|
+
* How a parked refund was sourced and what delivering it cost.
|
|
4287
3592
|
*
|
|
4288
3593
|
* `null` on an accumulator park — there the consumed rows *are* the record —
|
|
4289
3594
|
* and on every non-cashu rail. Set only where the notes were minted just in
|
|
@@ -4292,7 +3597,10 @@ interface CreditDrainRecord {
|
|
|
4292
3597
|
* already holding.
|
|
4293
3598
|
*/
|
|
4294
3599
|
fulfilment: DrainFulfilment | null;
|
|
4295
|
-
/**
|
|
3600
|
+
/**
|
|
3601
|
+
* Opaque supplied reclaim-event observations in append order; consumers shape-check and
|
|
3602
|
+
* authenticate them.
|
|
3603
|
+
*/
|
|
4296
3604
|
receipts: Record<string, unknown>[];
|
|
4297
3605
|
createdAt: number;
|
|
4298
3606
|
parkedAt: number | null;
|
|
@@ -4301,7 +3609,7 @@ interface CreditDrainRecord {
|
|
|
4301
3609
|
/** When an unfulfilled Tempo drain was restored to the credit balance. */
|
|
4302
3610
|
releasedAt: number | null;
|
|
4303
3611
|
/**
|
|
4304
|
-
* Why an operator closed this drain out without booking it
|
|
3612
|
+
* Why an operator closed this drain out without booking it, and
|
|
4305
3613
|
* when. Set only on a channel drain the payout floor refused: the chain paid
|
|
4306
3614
|
* the caller less than this ledger was extinguishing, the operator reviewed
|
|
4307
3615
|
* the two figures and is not booking the difference.
|
|
@@ -4336,7 +3644,6 @@ interface DrainWriteOff {
|
|
|
4336
3644
|
drain: CreditDrainRecord;
|
|
4337
3645
|
written: boolean;
|
|
4338
3646
|
}
|
|
4339
|
-
/** Result of restoring an unfulfilled Tempo drain to its credit. */
|
|
4340
3647
|
interface DrainReleaseResult {
|
|
4341
3648
|
drain: CreditDrainRecord;
|
|
4342
3649
|
credit: CreditSnapshot;
|
|
@@ -4344,7 +3651,7 @@ interface DrainReleaseResult {
|
|
|
4344
3651
|
}
|
|
4345
3652
|
/**
|
|
4346
3653
|
* Result of a terminal drain transition — {@link CreditLedger.markDrainPickedUp}
|
|
4347
|
-
* and {@link CreditLedger.markDrainSent}
|
|
3654
|
+
* and {@link CreditLedger.markDrainSent}.
|
|
4348
3655
|
*
|
|
4349
3656
|
* The record alone can't answer "did I move this money", because the CAS
|
|
4350
3657
|
* winner and an idempotent replay return the same row. Anything that must
|
|
@@ -4356,7 +3663,6 @@ interface DrainTransitionResult {
|
|
|
4356
3663
|
/** True when the drain was already terminal and this call changed nothing. */
|
|
4357
3664
|
replayed: boolean;
|
|
4358
3665
|
}
|
|
4359
|
-
/** Result of {@link CreditLedger.requestDrain}. */
|
|
4360
3666
|
interface DrainRequestResult {
|
|
4361
3667
|
drain: CreditDrainRecord;
|
|
4362
3668
|
/** The credit as of this call — balance already debited on a fresh drain. */
|
|
@@ -4365,9 +3671,8 @@ interface DrainRequestResult {
|
|
|
4365
3671
|
replayed: boolean;
|
|
4366
3672
|
}
|
|
4367
3673
|
/**
|
|
4368
|
-
* Read
|
|
4369
|
-
*
|
|
4370
|
-
* spending, never ownership — the balance stays readable).
|
|
3674
|
+
* Read model with funded balance and pending holds. Expiry is computed at read time and does
|
|
3675
|
+
* not erase the record; unbacked credit has no available balance.
|
|
4371
3676
|
*/
|
|
4372
3677
|
interface CreditSnapshot {
|
|
4373
3678
|
creditId: string;
|
|
@@ -4375,23 +3680,26 @@ interface CreditSnapshot {
|
|
|
4375
3680
|
currency: string;
|
|
4376
3681
|
/** Funded balance, 1e-6 currency units. Pending holds are NOT subtracted. */
|
|
4377
3682
|
balanceMicro: number;
|
|
4378
|
-
/**
|
|
3683
|
+
/**
|
|
3684
|
+
* Balance minus pending holds, or zero while backing is unbacked.
|
|
3685
|
+
*/
|
|
4379
3686
|
availableMicro: number;
|
|
4380
3687
|
/**
|
|
4381
3688
|
* `availableMicro` in the funding rail's own units — the ceiling any
|
|
4382
3689
|
* rail-denominated payout must respect. `null` marks a rail that reports no
|
|
4383
3690
|
* native basis, which is why a channel drain refuses rather than guessing
|
|
4384
|
-
* (internal-review).
|
|
4385
3691
|
*/
|
|
4386
3692
|
nativeAvailable: number | null;
|
|
4387
3693
|
expiryMs: number;
|
|
4388
3694
|
expired: boolean;
|
|
4389
3695
|
status: CreditStatus;
|
|
4390
|
-
/**
|
|
3696
|
+
/**
|
|
3697
|
+
* High-water sequence shared by draw placements and drains.
|
|
3698
|
+
*/
|
|
4391
3699
|
lastLedgerSeq: number;
|
|
4392
3700
|
createdAt: number;
|
|
4393
3701
|
/**
|
|
4394
|
-
* The rail this balance was funded on
|
|
3702
|
+
* The rail this balance was funded on; `null` on a credit opened
|
|
4395
3703
|
* before rail basis was recorded, which conflicts with nothing. Exposed so a
|
|
4396
3704
|
* route can refuse a cross-rail top-up **before** taking the payment `fund`
|
|
4397
3705
|
* would then refuse `rail_mismatch`.
|
|
@@ -4402,37 +3710,30 @@ interface CreditSnapshot {
|
|
|
4402
3710
|
/** Reusable x402 channel bound to this credit, if one funded it. */
|
|
4403
3711
|
x402ChannelId: string | null;
|
|
4404
3712
|
}
|
|
4405
|
-
/**
|
|
4406
|
-
* Result of a draw — carries the receipt tuple the internal-review countersignature
|
|
4407
|
-
* consumes (`ReceiptCredit`: `draw_id`, `amount`, `balance_after`,
|
|
4408
|
-
* `ledger_seq`). `balanceAfterMicro` and `ledgerSeq` are the values recorded
|
|
4409
|
-
* when the draw was first placed; a replay returns them unchanged.
|
|
4410
|
-
*/
|
|
4411
3713
|
interface DrawResult {
|
|
4412
3714
|
creditId: string;
|
|
4413
3715
|
drawId: string;
|
|
4414
3716
|
amountMicro: number;
|
|
4415
|
-
/**
|
|
3717
|
+
/**
|
|
3718
|
+
* Recorded balance after placement or latest growth, at the draw’s original sequence.
|
|
3719
|
+
*/
|
|
4416
3720
|
balanceAfterMicro: number;
|
|
4417
|
-
/**
|
|
3721
|
+
/**
|
|
3722
|
+
* Per-credit gap-free sequence shared with drains; growth keeps the original value.
|
|
3723
|
+
*/
|
|
4418
3724
|
ledgerSeq: number;
|
|
4419
3725
|
status: DrawStatus;
|
|
4420
3726
|
/** True when this call was an idempotent replay of an already-recorded state. */
|
|
4421
3727
|
replayed: boolean;
|
|
4422
|
-
/** Rail value allocated to this draw, plus the credit's rail identity
|
|
3728
|
+
/** Rail value allocated to this draw, plus the credit's rail identity. */
|
|
4423
3729
|
railValue: DrawRailValue;
|
|
4424
3730
|
/** When the draw reached its terminal state; `null` while pending. */
|
|
4425
3731
|
resolvedAt: number | null;
|
|
4426
3732
|
}
|
|
4427
3733
|
/**
|
|
4428
|
-
*
|
|
4429
|
-
*
|
|
4430
|
-
*
|
|
4431
|
-
* Allocated at draw time as `remaining × amountMicro / availableMicro`, where
|
|
4432
|
-
* both remainders are net of pending holds. The draw that consumes a credit's
|
|
4433
|
-
* last micro therefore takes ratio 1 and the exact remainder, so the draws of
|
|
4434
|
-
* a credit sum to precisely the value funded — no rounding residue, and no
|
|
4435
|
-
* last-draw special case.
|
|
3734
|
+
* A draw’s rail value and funding identity. Placement records a pooled forecast; settling
|
|
3735
|
+
* non-channel Bitcoin can replace it with FIFO lot pricing while retaining the original as
|
|
3736
|
+
* allocatedMsats. Native null means no native amount was reported, distinct from zero.
|
|
4436
3737
|
*/
|
|
4437
3738
|
interface DrawRailValue {
|
|
4438
3739
|
/** Millisatoshis of rail value allocated to this draw. */
|
|
@@ -4441,7 +3742,7 @@ interface DrawRailValue {
|
|
|
4441
3742
|
drawNative: number | null;
|
|
4442
3743
|
/**
|
|
4443
3744
|
* Rail that funded the credit — a {@link FundingMethod} on any row `fund`
|
|
4444
|
-
* wrote, `null` on one it opened before
|
|
3745
|
+
* wrote, `null` on one it opened before recorded the column. Stays
|
|
4445
3746
|
* `string` rather than the union: this is a database read, and a fleet
|
|
4446
3747
|
* mid-rollout can hold a rail a newer build wrote ({@link isFundingRail}).
|
|
4447
3748
|
*/
|
|
@@ -4456,7 +3757,7 @@ interface DrawRailValue {
|
|
|
4456
3757
|
creditCurrency: string | null;
|
|
4457
3758
|
}
|
|
4458
3759
|
/**
|
|
4459
|
-
* Result of `growDraw`
|
|
3760
|
+
* Result of `growDraw` — the draw's post-growth tuple, plus the
|
|
4460
3761
|
* slice this top-up added. The added slice is what the mid-job rail result
|
|
4461
3762
|
* reports as its `paidMsats` delta: the job's counters accumulate increments,
|
|
4462
3763
|
* not the running total the draw now holds.
|
|
@@ -4470,7 +3771,7 @@ interface GrownDrawResult extends DrawResult {
|
|
|
4470
3771
|
/** Fiat micro this growth added — `amountMicro` minus the pre-growth amount. */
|
|
4471
3772
|
addedAmountMicro: number;
|
|
4472
3773
|
/**
|
|
4473
|
-
* Fiat micro the
|
|
3774
|
+
* Fiat micro the cap refused, because growing by it would have
|
|
4474
3775
|
* pushed the draw past what the job cumulatively asked. Zero on every
|
|
4475
3776
|
* ordinary top-up. Non-zero means money landed on the credit that this job
|
|
4476
3777
|
* did not buy anything with — it stays the caller's available balance, and
|
|
@@ -4485,13 +3786,12 @@ interface DrawResolution extends DrawResult {
|
|
|
4485
3786
|
/** Settlement instant — the timestamp a draw-keyed revenue event is dated by. */
|
|
4486
3787
|
resolvedAt: number;
|
|
4487
3788
|
}
|
|
4488
|
-
/** Full persisted draw row, camel-cased. */
|
|
4489
3789
|
interface DrawRecord extends Omit<DrawResult, "replayed"> {
|
|
4490
3790
|
jobId: string | null;
|
|
4491
3791
|
createdAt: number;
|
|
4492
3792
|
/**
|
|
4493
3793
|
* What `allocateDrawValue` allocated this draw when the payment landed
|
|
4494
|
-
*
|
|
3794
|
+
* the pro-rata slice of the credit's pooled rail value that
|
|
4495
3795
|
* `withDrawBasis` mirrored onto the job's `paidMsats` counter.
|
|
4496
3796
|
*
|
|
4497
3797
|
* Equal to `railValue.drawMsats` on every draw the settle did not re-price,
|
|
@@ -4504,8 +3804,8 @@ interface DrawRecord extends Omit<DrawResult, "replayed"> {
|
|
|
4504
3804
|
allocatedMsats: number;
|
|
4505
3805
|
}
|
|
4506
3806
|
/**
|
|
4507
|
-
* Keyset cursor into {@link CreditLedgerLike.listStalePendingDraws}
|
|
4508
|
-
*
|
|
3807
|
+
* Keyset cursor into {@link CreditLedgerLike.listStalePendingDraws}, holding the
|
|
3808
|
+
* sort key of the last row a caller examined; offset paging would not
|
|
4509
3809
|
* do: the scan skips holds it can't resolve (their job row exists), so those
|
|
4510
3810
|
* rows stay `pending` and reappear at the head of every subsequent page. The
|
|
4511
3811
|
* tuple is `(createdAt, creditId, ledgerSeq)`, matching the read's `ORDER BY`;
|
|
@@ -4518,7 +3818,7 @@ interface StalePendingDrawCursor {
|
|
|
4518
3818
|
ledgerSeq: number;
|
|
4519
3819
|
}
|
|
4520
3820
|
/**
|
|
4521
|
-
* Rail value one funding contributes to a credit
|
|
3821
|
+
* Rail value one funding contributes to a credit. Recorded so a
|
|
4522
3822
|
* later draw can be allocated its share of what the rail actually paid, and so
|
|
4523
3823
|
* the platform can book the funding as a deposit.
|
|
4524
3824
|
*/
|
|
@@ -4527,11 +3827,11 @@ interface CreditFundingBasis {
|
|
|
4527
3827
|
* The rail whose value this funding carries. A top-up on another rail is
|
|
4528
3828
|
* refused (`rail_mismatch`).
|
|
4529
3829
|
*
|
|
4530
|
-
* Narrowed from `string` to {@link FundingMethod} by
|
|
3830
|
+
* Narrowed from `string` to {@link FundingMethod} by — the same type
|
|
4531
3831
|
* {@link FUNDING_RAILS} is keyed by — so a rail cannot enter `credits.rail`
|
|
4532
3832
|
* that the terminal has no way to book revenue under. That silently costs
|
|
4533
3833
|
* every draw the credit backs its booking, which is what happened when
|
|
4534
|
-
* `lightning` reached the ledger before the reader's allow-list
|
|
3834
|
+
* `lightning` reached the ledger before the reader's allow-list.
|
|
4535
3835
|
*/
|
|
4536
3836
|
rail: FundingMethod;
|
|
4537
3837
|
/** Rail payment value in millisatoshis. */
|
|
@@ -4555,9 +3855,11 @@ interface CreditFundingBasis {
|
|
|
4555
3855
|
maxClaimableAmount: string;
|
|
4556
3856
|
};
|
|
4557
3857
|
}
|
|
4558
|
-
/** What {@link CreditLedger.fund} takes. Named so the private body can share the shape. */
|
|
4559
3858
|
interface CreditFundArgs {
|
|
4560
|
-
/**
|
|
3859
|
+
/**
|
|
3860
|
+
* Omit to mint a credit ID; provide to add funding to that credit. Replay prevention is the
|
|
3861
|
+
* caller’s responsibility.
|
|
3862
|
+
*/
|
|
4561
3863
|
creditId?: string;
|
|
4562
3864
|
callerPubkey: string;
|
|
4563
3865
|
/** The DVM's pricing currency; `amountMicro` is 1e-6 of it. */
|
|
@@ -4565,14 +3867,18 @@ interface CreditFundArgs {
|
|
|
4565
3867
|
amountMicro: number;
|
|
4566
3868
|
/** Absolute expiry, ms since epoch. Overwrites the previous value on top-up. */
|
|
4567
3869
|
expiryMs: number;
|
|
4568
|
-
/**
|
|
3870
|
+
/**
|
|
3871
|
+
* Rail value contributed by this funding; required on writes.
|
|
3872
|
+
*/
|
|
4569
3873
|
basis: CreditFundingBasis;
|
|
4570
|
-
/**
|
|
3874
|
+
/**
|
|
3875
|
+
* External transaction handle; caller owns BEGIN, COMMIT and ROLLBACK.
|
|
3876
|
+
*/
|
|
4571
3877
|
tx?: CreditLedgerQuerier;
|
|
4572
3878
|
nowMs?: number;
|
|
4573
3879
|
}
|
|
4574
3880
|
/**
|
|
4575
|
-
* Allocate a draw's share of a credit's unspent rail value
|
|
3881
|
+
* Allocate a draw's share of a credit's unspent rail value. Pure —
|
|
4576
3882
|
* shared by the Postgres and in-memory ledgers so the two can't drift.
|
|
4577
3883
|
*
|
|
4578
3884
|
* `availableMicro` and both remainders must be net of pending holds; the
|
|
@@ -4587,7 +3893,6 @@ declare function allocateDrawValue(args: {
|
|
|
4587
3893
|
drawMsats: number;
|
|
4588
3894
|
drawNative: number | null;
|
|
4589
3895
|
};
|
|
4590
|
-
/** Full persisted `credit_fundings` row, camel-cased (internal-review). */
|
|
4591
3896
|
interface FundingRecord {
|
|
4592
3897
|
creditId: string;
|
|
4593
3898
|
fundId: string;
|
|
@@ -4603,7 +3908,7 @@ interface FundingRecord {
|
|
|
4603
3908
|
receipt: FundingReceipt | null;
|
|
4604
3909
|
}
|
|
4605
3910
|
/**
|
|
4606
|
-
* Lifecycle of a Lightning funding invoice
|
|
3911
|
+
* Lifecycle of a Lightning funding invoice.
|
|
4607
3912
|
*
|
|
4608
3913
|
* Only `pending` is live; everything else is terminal. The two that carry no
|
|
4609
3914
|
* balance carry opposite money: `expired` was never paid and owes nobody
|
|
@@ -4611,7 +3916,7 @@ interface FundingRecord {
|
|
|
4611
3916
|
* operator. Both exist so that a row the sweep can never advance stops being
|
|
4612
3917
|
* swept.
|
|
4613
3918
|
*
|
|
4614
|
-
* `reconciled` and `written_off`
|
|
3919
|
+
* `reconciled` and `written_off` are where a `blocked` row goes once
|
|
4615
3920
|
* that operator acts — respectively, the money was credited onto a credit they
|
|
4616
3921
|
* named, or they decided it would not be. Neither is `settled`: that one means
|
|
4617
3922
|
* the ledger applied the payment to the top-up the caller asked for, and
|
|
@@ -4619,7 +3924,6 @@ interface FundingRecord {
|
|
|
4619
3924
|
* human had to intervene.
|
|
4620
3925
|
*/
|
|
4621
3926
|
type CreditInvoiceStatus = "pending" | "settled" | "expired" | "blocked" | "reconciled" | "written_off";
|
|
4622
|
-
/** Full persisted `credit_invoices` row, camel-cased (internal-review). */
|
|
4623
3927
|
interface CreditInvoiceRecord {
|
|
4624
3928
|
creditId: string;
|
|
4625
3929
|
fundId: string;
|
|
@@ -4638,7 +3942,7 @@ interface CreditInvoiceRecord {
|
|
|
4638
3942
|
* trail for why a human had to touch this row at all.
|
|
4639
3943
|
*/
|
|
4640
3944
|
blockedReason: string | null;
|
|
4641
|
-
/** Credit an operator sent a blocked invoice's money to
|
|
3945
|
+
/** Credit an operator sent a blocked invoice's money to. */
|
|
4642
3946
|
reconciledCreditId: string | null;
|
|
4643
3947
|
/** Funding key on that credit — the payment hash. See `reconcileBlockedInvoice`. */
|
|
4644
3948
|
reconciledFundId: string | null;
|
|
@@ -4652,20 +3956,17 @@ interface CreditInvoiceRecord {
|
|
|
4652
3956
|
/** When the payment landed at the wallet — never the instant an operator acted on it. */
|
|
4653
3957
|
settledAt: number | null;
|
|
4654
3958
|
}
|
|
4655
|
-
/** Outcome of applying an observed settlement to the ledger (internal-review). */
|
|
4656
3959
|
interface InvoiceSettlement {
|
|
4657
3960
|
invoice: CreditInvoiceRecord;
|
|
4658
|
-
/** The `credit_fundings` row; absent only if a
|
|
3961
|
+
/** The `credit_fundings` row; absent only if a legacy row somehow lacks one. */
|
|
4659
3962
|
funding: FundingRecord | undefined;
|
|
4660
3963
|
credit: CreditSnapshot | undefined;
|
|
4661
3964
|
/** True when this settlement had already been applied — nothing moved. */
|
|
4662
3965
|
replayed: boolean;
|
|
4663
3966
|
}
|
|
4664
3967
|
/**
|
|
4665
|
-
*
|
|
4666
|
-
*
|
|
4667
|
-
* `credit` describe the **target** credit, not the one the invoice was minted
|
|
4668
|
-
* against.
|
|
3968
|
+
* Reconciliation keeps the original invoice identity; funding and credit describe the
|
|
3969
|
+
* operator-selected target credit, which may differ from the invoice’s original credit.
|
|
4669
3970
|
*/
|
|
4670
3971
|
interface InvoiceReconciliation {
|
|
4671
3972
|
invoice: CreditInvoiceRecord;
|
|
@@ -4674,7 +3975,6 @@ interface InvoiceReconciliation {
|
|
|
4674
3975
|
/** True when this reconcile had already been applied — nothing moved. */
|
|
4675
3976
|
replayed: boolean;
|
|
4676
3977
|
}
|
|
4677
|
-
/** Outcome of `writeOffBlockedInvoice` — the recorded row, plus whether this call wrote it. */
|
|
4678
3978
|
interface InvoiceWriteOff {
|
|
4679
3979
|
invoice: CreditInvoiceRecord;
|
|
4680
3980
|
/** False when the invoice was already written off, so nothing changed. */
|
|
@@ -4686,31 +3986,13 @@ interface BlockedInvoiceCursor {
|
|
|
4686
3986
|
creditId: string;
|
|
4687
3987
|
fundId: string;
|
|
4688
3988
|
}
|
|
4689
|
-
/** Typed failure codes for {@link CreditLedgerError}. */
|
|
4690
3989
|
type CreditLedgerErrorCode = "credit_not_found" | "credit_expired" | "credit_unbacked" | "tempo_channel_closing" | "tempo_settlement_pending" | "settlement_pending" | "insufficient_credit" | "draw_conflict" | "draw_not_found" | "invalid_draw_state" | "funding_replayed" | "currency_mismatch" | "caller_mismatch" | "rail_mismatch" | "invalid_amount" | "invalid_expiry" | "invalid_basis" | "nothing_to_drain" | "drain_below_dust" | "drain_not_found" | "drain_conflict" | "invalid_drain_state" | "invoice_not_found" | "invoice_conflict" | "invoice_not_blocked" | "below_rail_minimum";
|
|
4691
3990
|
/**
|
|
4692
|
-
*
|
|
4693
|
-
*
|
|
4694
|
-
*
|
|
4695
|
-
* booked; the copy a caller relays to a human differs entirely between them.
|
|
4696
|
-
* A tag rather than a message substring because that is what the copy used to
|
|
4697
|
-
* key on, and both of the x402 commit leg's conflicts missed it by a word —
|
|
4698
|
-
* silently, since the fallback is a well-formed answer about a different
|
|
4699
|
-
* refusal.
|
|
4700
|
-
*
|
|
4701
|
-
* The three are not one shape with three causes. `pending_draws` and
|
|
4702
|
-
* `balance_moved` roll back with nothing on any chain and clear on their own;
|
|
4703
|
-
* `refund_floor` is the opposite — the cooperative refund reached the chain and
|
|
4704
|
-
* paid short, so the caller's money moved and only the operator repair resolves
|
|
4705
|
-
* it (internal-review). A route's `retryable` and `drain_registered` follow from which
|
|
4706
|
-
* one it is, so the tag has to reach it.
|
|
4707
|
-
*
|
|
4708
|
-
* Absent means the duplicate-payout conflict `PAYMENT_ERROR_COPY.drain_conflict`
|
|
4709
|
-
* was written for, which is also the conservative default for a conflict nobody
|
|
4710
|
-
* has classified yet.
|
|
3991
|
+
* Structured drain_conflict reasons select caller instructions without matching prose. A
|
|
3992
|
+
* refund_floor shortfall needs operator resolution, unlike duplicate payout; foreign_credit
|
|
3993
|
+
* discloses no liability to the wrong owner; drain_id_retired requires a fresh ID.
|
|
4711
3994
|
*/
|
|
4712
3995
|
type DrainConflictReason = "pending_draws" | "balance_moved" | "refund_floor";
|
|
4713
|
-
/** Structured context on a {@link CreditLedgerError}, per code. */
|
|
4714
3996
|
interface CreditLedgerErrorDetails {
|
|
4715
3997
|
/** On `drain_conflict`: which conflict it is, so a route can answer it without matching on prose. */
|
|
4716
3998
|
drainConflict?: DrainConflictReason;
|
|
@@ -4740,13 +4022,13 @@ interface CreditLedgerErrorDetails {
|
|
|
4740
4022
|
settlementId?: string;
|
|
4741
4023
|
/** On `settlement_pending`: that settlement's status, so an operator sees which repair applies. */
|
|
4742
4024
|
settlementStatus?: string;
|
|
4743
|
-
/** On `settlement_pending`: the drain id that settlement is under — the one id this gate admits
|
|
4025
|
+
/** On `settlement_pending`: the drain id that settlement is under — the one id this gate admits. */
|
|
4744
4026
|
blockingDrainId?: string;
|
|
4745
4027
|
}
|
|
4746
4028
|
/**
|
|
4747
4029
|
* Thrown by every {@link CreditLedger} method on refusal. Carries a `code`
|
|
4748
4030
|
* discriminator (plus structured `details`) so callers branch on a field
|
|
4749
|
-
* instead of string-matching messages — the
|
|
4031
|
+
* instead of string-matching messages — the quote surface maps
|
|
4750
4032
|
* `insufficient_credit` + `details.availableMicro` onto the structured
|
|
4751
4033
|
* 402-class wire error.
|
|
4752
4034
|
*/
|
|
@@ -4810,7 +4092,8 @@ interface TempoSessionLifecycle {
|
|
|
4810
4092
|
*
|
|
4811
4093
|
* Every mutation takes an advisory transaction lock keyed by the channel
|
|
4812
4094
|
* before locking its row with `FOR UPDATE`, applies mppx's synchronous
|
|
4813
|
-
* transition
|
|
4095
|
+
* transition and commits before returning. Transaction recovery may retry a callback
|
|
4096
|
+
* attempt; a rolled-back attempt is not a second committed transition. The advisory lock
|
|
4814
4097
|
* covers a channel's first write, when there is no row for `FOR UPDATE` to
|
|
4815
4098
|
* lock. Together these are the cross-machine linearization point for voucher,
|
|
4816
4099
|
* top-up, spend, and close state; mppx's process-local fallback is
|
|
@@ -4822,14 +4105,14 @@ interface TempoSessionLifecycle {
|
|
|
4822
4105
|
* parent-credit lock through the same client.
|
|
4823
4106
|
*
|
|
4824
4107
|
* {@link withVoucherAcceptance} acquires its pooled client **lazily**, on the
|
|
4825
|
-
* first store mutation rather than up front
|
|
4108
|
+
* first store mutation rather than up front. Its `operation` is
|
|
4826
4109
|
* mppx's credential verification, which does the chain work before it touches
|
|
4827
4110
|
* the store: an `open` submits the escrow transaction and awaits its receipt,
|
|
4828
4111
|
* a `voucher` reads channel state over public RPC. Holding a connection across
|
|
4829
4112
|
* that starves everything else on the shared host pool — `/v1/job`, the job
|
|
4830
4113
|
* store, KV, the credit ledger, the replay store — and exposes the open
|
|
4831
4114
|
* transaction to `idle_in_transaction_session_timeout`. This is the same shape
|
|
4832
|
-
*
|
|
4115
|
+
* removed from the x402 settlement path. Laziness costs nothing in
|
|
4833
4116
|
* atomicity: the `FOR UPDATE` row lock was always taken at the first mutation,
|
|
4834
4117
|
* never at `BEGIN`, so the critical section is unchanged — only the
|
|
4835
4118
|
* connection-holding window shrinks to the database work it actually covers.
|
|
@@ -4838,13 +4121,10 @@ declare class PostgresTempoSessionStore implements Store.AtomicStore {
|
|
|
4838
4121
|
private readonly pool;
|
|
4839
4122
|
private readonly acceptance;
|
|
4840
4123
|
constructor(pool: Pool);
|
|
4841
|
-
/** Create the channel table under the SDK-wide migration lock. */
|
|
4842
4124
|
init(): Promise<void>;
|
|
4843
|
-
/** Read one channel snapshot. */
|
|
4844
4125
|
get(key: string): Promise<unknown>;
|
|
4845
4126
|
/** Replace one channel snapshot. Used by mppx only for non-RMW maintenance. */
|
|
4846
4127
|
put(key: string, value: unknown): Promise<void>;
|
|
4847
|
-
/** Delete one channel snapshot. */
|
|
4848
4128
|
delete(key: string): Promise<void>;
|
|
4849
4129
|
/** Atomic read-modify-write using a row lock shared by every SDK replica. */
|
|
4850
4130
|
update<result>(key: string, fn: (current: unknown) => Store.Change<unknown, result>): Promise<result>;
|
|
@@ -4933,14 +4213,14 @@ interface MppOpts {
|
|
|
4933
4213
|
/**
|
|
4934
4214
|
* When set, only register MPP methods whose name appears in this list, even
|
|
4935
4215
|
* if the per-rail env var (`DVMKIT_TEMPO_RECIPIENT`) is configured
|
|
4936
|
-
*
|
|
4216
|
+
* Lets operators temporarily disable a rail without unsetting
|
|
4937
4217
|
* other knobs. Empty/undefined falls back to "register everything that has
|
|
4938
4218
|
* a configured recipient".
|
|
4939
4219
|
*/
|
|
4940
4220
|
methodsAllowlist?: string[];
|
|
4941
4221
|
/**
|
|
4942
4222
|
* Durable replay protection for the one-shot `tempo/charge` method
|
|
4943
|
-
*
|
|
4223
|
+
* Omit and mppx falls back to `Store.memory()`, whose consumed
|
|
4944
4224
|
* transaction hashes are per-process — invisible to sibling machines and lost
|
|
4945
4225
|
* on restart. Pass a cross-machine store (`PostgresTempoChargeStore`) on any
|
|
4946
4226
|
* multi-instance deploy.
|
|
@@ -4970,7 +4250,7 @@ interface MppOpts {
|
|
|
4970
4250
|
amount?: string | bigint;
|
|
4971
4251
|
intervalMs?: number;
|
|
4972
4252
|
/**
|
|
4973
|
-
* How long a settlement holds its in-flight lease
|
|
4253
|
+
* How long a settlement holds its in-flight lease. Defaults to
|
|
4974
4254
|
* {@link TEMPO_SETTLEMENT_LEASE_MS}, which documents the receipt-timeout
|
|
4975
4255
|
* floor an override has to stay above.
|
|
4976
4256
|
*/
|
|
@@ -5031,7 +4311,7 @@ interface MppxServer {
|
|
|
5031
4311
|
* Reconcile active TIP-1034 channels; present only on a durable session
|
|
5032
4312
|
* handle. `inFlight` counts channels the sweep found due but declined to
|
|
5033
4313
|
* settle because another path already holds their settlement lease
|
|
5034
|
-
*
|
|
4314
|
+
* a deferral, not a failure.
|
|
5035
4315
|
*/
|
|
5036
4316
|
sweepTempoSessions?: () => Promise<{
|
|
5037
4317
|
scanned: number;
|
|
@@ -5041,7 +4321,7 @@ interface MppxServer {
|
|
|
5041
4321
|
}>;
|
|
5042
4322
|
/**
|
|
5043
4323
|
* Reconcile and, if the channel is at risk, settle exactly one TIP-1034
|
|
5044
|
-
* channel
|
|
4324
|
+
* channel. The forced-close protection path: a verified on-chain
|
|
5045
4325
|
* `CloseRequested` event authorises this and nothing else.
|
|
5046
4326
|
*
|
|
5047
4327
|
* Needs no `listActive`, unlike {@link sweepTempoSessions} — a store that
|
|
@@ -5074,7 +4354,7 @@ interface MppxServer {
|
|
|
5074
4354
|
/** Recover a close result after a process failure between chain and ledger commits. */
|
|
5075
4355
|
recoverTempoSessionClose?: (channelId: string, drainId: string) => Promise<string | null>;
|
|
5076
4356
|
/**
|
|
5077
|
-
* Read a channel's close intent without touching it
|
|
4357
|
+
* Read a channel's close intent without touching it.
|
|
5078
4358
|
*
|
|
5079
4359
|
* The operator repair needs the reference a confirmed close already carries,
|
|
5080
4360
|
* and it must not be `recoverTempoSessionClose`: that one *writes* — an
|
|
@@ -5086,14 +4366,14 @@ interface MppxServer {
|
|
|
5086
4366
|
/** Read terminally consumed value for an exact cooperative-close challenge. */
|
|
5087
4367
|
getTempoSessionSpent?: (channelId: string) => Promise<bigint>;
|
|
5088
4368
|
/**
|
|
5089
|
-
* Read the immutable chain binding of a stored channel
|
|
4369
|
+
* Read the immutable chain binding of a stored channel, or `null`
|
|
5090
4370
|
* when this DVM holds no such channel. What a close event must match before
|
|
5091
4371
|
* it may authorise anything.
|
|
5092
4372
|
*/
|
|
5093
4373
|
getTempoSessionBinding?: (channelId: string) => Promise<TempoChannelBinding | null>;
|
|
5094
4374
|
/**
|
|
5095
4375
|
* Fetch a transaction receipt from this DVM's **own** configured Tempo RPC
|
|
5096
|
-
*
|
|
4376
|
+
* the independent read that makes a close-event locator
|
|
5097
4377
|
* self-verifying rather than something the platform asserts.
|
|
5098
4378
|
*/
|
|
5099
4379
|
getTempoReceipt?: (args: {
|
|
@@ -5107,7 +4387,7 @@ interface MppxServer {
|
|
|
5107
4387
|
}[];
|
|
5108
4388
|
} | null>;
|
|
5109
4389
|
/**
|
|
5110
|
-
* Read what a landed cooperative close actually paid out
|
|
4390
|
+
* Read what a landed cooperative close actually paid out.
|
|
5111
4391
|
*
|
|
5112
4392
|
* mppx decodes `ChannelClosed` while verifying the close credential and then
|
|
5113
4393
|
* drops both amounts — its session receipt carries only the channel, the
|
|
@@ -5132,7 +4412,7 @@ interface TempoChannelCloseAmounts {
|
|
|
5132
4412
|
refundedToPayer: bigint;
|
|
5133
4413
|
}
|
|
5134
4414
|
/**
|
|
5135
|
-
* What a targeted single-channel settlement did
|
|
4415
|
+
* What a targeted single-channel settlement did.
|
|
5136
4416
|
*
|
|
5137
4417
|
* The platform's close observer acknowledges its durable event on a terminal
|
|
5138
4418
|
* outcome and retries otherwise, so the distinctions here are a delivery
|
|
@@ -5140,16 +4420,16 @@ interface TempoChannelCloseAmounts {
|
|
|
5140
4420
|
*
|
|
5141
4421
|
* - `settled` — a settlement transaction landed for this channel.
|
|
5142
4422
|
* - `already_safe` — the on-chain watermark already covers every accepted
|
|
5143
|
-
*
|
|
4423
|
+
* voucher, so there is nothing to protect. A replay lands here.
|
|
5144
4424
|
* - `unknown_channel` — not in this DVM's durable store. Terminal: a DVM must
|
|
5145
|
-
*
|
|
4425
|
+
* never settle a channel it does not own.
|
|
5146
4426
|
* - `finalized_before_protection` — the channel finalized while it still owed
|
|
5147
|
-
*
|
|
5148
|
-
*
|
|
5149
|
-
*
|
|
4427
|
+
* us value. A **money incident**, not a success: the grace period elapsed
|
|
4428
|
+
* without settlement and the deposit is gone. Preserved and paged, never
|
|
4429
|
+
* quietly acknowledged.
|
|
5150
4430
|
* - `retryable_failure` — a chain read or settlement attempt failed, or another
|
|
5151
|
-
*
|
|
5152
|
-
*
|
|
4431
|
+
* path is already broadcasting a settlement for this channel. The
|
|
4432
|
+
* event stays pending and is retried inside the grace period.
|
|
5153
4433
|
*/
|
|
5154
4434
|
type TempoChannelSettlementOutcome = {
|
|
5155
4435
|
outcome: "settled";
|
|
@@ -5174,7 +4454,6 @@ type TempoChannelSettlementOutcome = {
|
|
|
5174
4454
|
};
|
|
5175
4455
|
/**
|
|
5176
4456
|
* The immutable chain identity a TIP-1034 channel was opened against
|
|
5177
|
-
* (internal-review).
|
|
5178
4457
|
*
|
|
5179
4458
|
* Doubles as the close-event authorisation record and as what the DVM reports
|
|
5180
4459
|
* to the platform so a close can be routed back to it. `payee` is the DVM's
|
|
@@ -5225,7 +4504,6 @@ declare const _testing: {
|
|
|
5225
4504
|
attachTempoSessionRuntime: typeof attachTempoSessionRuntime;
|
|
5226
4505
|
};
|
|
5227
4506
|
|
|
5228
|
-
/** Envelope fields the verifier validates around the signed payload. */
|
|
5229
4507
|
interface CanonicalEnvelope {
|
|
5230
4508
|
pubkey: string;
|
|
5231
4509
|
signature: string;
|
|
@@ -5233,26 +4511,24 @@ interface CanonicalEnvelope {
|
|
|
5233
4511
|
nonce: string;
|
|
5234
4512
|
auth_statement?: SignedRequestStatementHeader;
|
|
5235
4513
|
}
|
|
5236
|
-
/** Options for `createSignedRequestVerifier`. */
|
|
5237
4514
|
interface CreateSignedRequestVerifierOpts {
|
|
5238
4515
|
/** Fixed audience/operation for a custom signed route or standalone verifier. */
|
|
5239
4516
|
domain?: SignedRequestDomain;
|
|
5240
4517
|
/**
|
|
5241
|
-
* Permitted clock drift in seconds
|
|
5242
|
-
* cast's `SIGNATURE_DRIFT_SECONDS`).
|
|
4518
|
+
* Permitted clock drift in seconds; default 300.
|
|
5243
4519
|
*/
|
|
5244
4520
|
driftSeconds?: number;
|
|
5245
4521
|
/**
|
|
5246
|
-
*
|
|
5247
|
-
*
|
|
5248
|
-
* itself is always stripped (callers don't need to repeat it).
|
|
4522
|
+
* Fields stripped from raw input when forming signing bytes; default signature,
|
|
4523
|
+
* which is always stripped even when additional fields are configured.
|
|
5249
4524
|
*/
|
|
5250
4525
|
envelopeFields?: string[];
|
|
5251
4526
|
/**
|
|
5252
4527
|
* Field names removed from the input before `schema` validates it — and
|
|
5253
4528
|
* **kept** in the canonical bytes the signature covers. For protocol-reserved
|
|
5254
|
-
* keys the caller signs but the schema never declares: the
|
|
5255
|
-
* envelope riding a capability schema
|
|
4529
|
+
* keys the caller signs but the schema never declares: the credit
|
|
4530
|
+
* envelope riding a capability schema. No additional fields are ignored by default;
|
|
4531
|
+
* auth_statement is always excluded from schema validation.
|
|
5256
4532
|
*
|
|
5257
4533
|
* This is the one place where "what is validated" and "what is signed"
|
|
5258
4534
|
* legitimately differ. Don't reach for it to paper over a schema mismatch —
|
|
@@ -5267,7 +4543,6 @@ interface CreateSignedRequestVerifierOpts {
|
|
|
5267
4543
|
/** Current Unix-seconds source. Defaults to `() => Math.floor(Date.now() / 1000)`. */
|
|
5268
4544
|
now?: () => number;
|
|
5269
4545
|
}
|
|
5270
|
-
/** Minimal replay-store contract — same shape as cast's `ReplayStore`. */
|
|
5271
4546
|
interface SignedRequestReplayStore {
|
|
5272
4547
|
/**
|
|
5273
4548
|
* True iff (pubkey, timestamp, nonce) was already seen within the retention
|
|
@@ -5277,7 +4552,6 @@ interface SignedRequestReplayStore {
|
|
|
5277
4552
|
*/
|
|
5278
4553
|
checkAndRecord(pubkey: string, timestamp: number, nonce: string, now: number): Promise<boolean>;
|
|
5279
4554
|
}
|
|
5280
|
-
/** Options for the signer half. */
|
|
5281
4555
|
interface SignedRequestSignOpts {
|
|
5282
4556
|
/** Unix timestamp in seconds. Defaults to `now()`. */
|
|
5283
4557
|
timestamp?: number;
|
|
@@ -5285,21 +4559,9 @@ interface SignedRequestSignOpts {
|
|
|
5285
4559
|
nonce?: string;
|
|
5286
4560
|
}
|
|
5287
4561
|
/**
|
|
5288
|
-
*
|
|
5289
|
-
*
|
|
5290
|
-
*
|
|
5291
|
-
* Authentication is two-step so the replay nonce is committed iff a job is
|
|
5292
|
-
* created, not on every request entry:
|
|
5293
|
-
*
|
|
5294
|
-
* 1. {@link checkAuth} runs schema → drift → signature at request entry on
|
|
5295
|
-
* both `/v1/quote` and `/v1/job`. Sync; no replay-store touch.
|
|
5296
|
-
* 2. {@link recordReplay} commits `(pubkey, timestamp, nonce)` to the replay
|
|
5297
|
-
* store. Called from `/v1/job` only, after upfront payment is verified
|
|
5298
|
-
* and immediately before `jobManager.createJob`.
|
|
5299
|
-
*
|
|
5300
|
-
* The unpaid `/v1/job` warmup probe (price discovery → 402) thus no longer
|
|
5301
|
-
* burns the nonce — clients can re-submit the same envelope with a real
|
|
5302
|
-
* payment.
|
|
4562
|
+
* Authentication does not touch replay state. Job submission commits replay
|
|
4563
|
+
* only after upfront payment verification, so unpaid discovery cannot burn
|
|
4564
|
+
* the nonce. Reactivation rechecks the persisted signature without drift/replay.
|
|
5303
4565
|
*/
|
|
5304
4566
|
interface SignedRequestVerifier<T> {
|
|
5305
4567
|
/**
|
|
@@ -5310,7 +4572,7 @@ interface SignedRequestVerifier<T> {
|
|
|
5310
4572
|
checkAuth(input: unknown, domain?: SignedRequestDomain): T & CanonicalEnvelope;
|
|
5311
4573
|
/**
|
|
5312
4574
|
* Schema + signature check against `input`, without the drift window check
|
|
5313
|
-
* or replay-store touch
|
|
4575
|
+
* or replay-store touch. For the reactivation path where the
|
|
5314
4576
|
* persisted timestamp is from the original signing instant (could be hours
|
|
5315
4577
|
* or days ago) and the replay nonce was already committed at submission.
|
|
5316
4578
|
* Returns the verified envelope; throws {@link SignedRequestError} with
|
|
@@ -5333,20 +4595,11 @@ interface SignedRequestVerifier<T> {
|
|
|
5333
4595
|
*/
|
|
5334
4596
|
signRequest(privkey: Uint8Array, body: Record<string, unknown>, opts?: SignedRequestSignOpts, domain?: SignedRequestDomain): T & CanonicalEnvelope;
|
|
5335
4597
|
}
|
|
5336
|
-
/** Failure mode taxonomy returned from `verify()`. */
|
|
5337
4598
|
type SignedRequestFailure = "schema_invalid" | "timestamp_drift" | "signature_invalid" | "replay_detected";
|
|
5338
4599
|
/**
|
|
5339
|
-
*
|
|
5340
|
-
*
|
|
5341
|
-
*
|
|
5342
|
-
* The single definition of "which part of a schema failure is the envelope's":
|
|
5343
|
-
* the post-parse shape check in `parse()` iterates it, and `authErrorBody`'s
|
|
5344
|
-
* internal-review classifier reads its keys to decide whether a `schema_invalid` throw
|
|
5345
|
-
* is the caller's *input* being wrong (disclosable: the schema is public on
|
|
5346
|
-
* `/v1/info`) or their *signing* being wrong (folded into `signature_invalid`,
|
|
5347
|
-
* one answer for every key and signature failure). `satisfies` pins it to
|
|
5348
|
-
* {@link CanonicalEnvelope}, so a fifth envelope field can't be added to the
|
|
5349
|
-
* type without both readers picking it up.
|
|
4600
|
+
* One definition of envelope field types shared by the runtime shape guard
|
|
4601
|
+
* and error disclosure classifier. Envelope failures stay private; violations
|
|
4602
|
+
* of the published builder input schema can be disclosed.
|
|
5350
4603
|
*/
|
|
5351
4604
|
declare const SIGNED_ENVELOPE_TYPES: {
|
|
5352
4605
|
readonly pubkey: "string";
|
|
@@ -5355,14 +4608,12 @@ declare const SIGNED_ENVELOPE_TYPES: {
|
|
|
5355
4608
|
readonly nonce: "string";
|
|
5356
4609
|
readonly auth_statement: "object";
|
|
5357
4610
|
};
|
|
5358
|
-
/** The envelope field names — {@link SIGNED_ENVELOPE_TYPES}' keys. */
|
|
5359
4611
|
declare const SIGNED_ENVELOPE_FIELDS: readonly (keyof CanonicalEnvelope)[];
|
|
5360
|
-
/** Structured error from `verify()` — `sub_reason` is the failure kind. */
|
|
5361
4612
|
declare class SignedRequestError extends Error {
|
|
5362
4613
|
readonly sub_reason: SignedRequestFailure;
|
|
5363
4614
|
/**
|
|
5364
4615
|
* The Zod issues behind a `schema_invalid`, when the failure came from a
|
|
5365
|
-
* `safeParse` that produced any
|
|
4616
|
+
* `safeParse` that produced any. `authErrorBody` reads their
|
|
5366
4617
|
* paths to tell an input violation from an envelope one, and renders the
|
|
5367
4618
|
* input half into the caller-facing `display`. Absent on every other
|
|
5368
4619
|
* `sub_reason`, and on the `schema_invalid` throws that aren't a parse
|
|
@@ -5374,7 +4625,7 @@ declare class SignedRequestError extends Error {
|
|
|
5374
4625
|
constructor(message: string, sub_reason: SignedRequestFailure,
|
|
5375
4626
|
/**
|
|
5376
4627
|
* The Zod issues behind a `schema_invalid`, when the failure came from a
|
|
5377
|
-
* `safeParse` that produced any
|
|
4628
|
+
* `safeParse` that produced any. `authErrorBody` reads their
|
|
5378
4629
|
* paths to tell an input violation from an envelope one, and renders the
|
|
5379
4630
|
* input half into the caller-facing `display`. Absent on every other
|
|
5380
4631
|
* `sub_reason`, and on the `schema_invalid` throws that aren't a parse
|
|
@@ -5384,39 +4635,15 @@ declare class SignedRequestError extends Error {
|
|
|
5384
4635
|
issues?: readonly z.core.$ZodIssue[] | undefined);
|
|
5385
4636
|
}
|
|
5386
4637
|
/**
|
|
5387
|
-
*
|
|
5388
|
-
*
|
|
5389
|
-
*
|
|
5390
|
-
* Generalises cast's `canonicalSignedPayload` + `verifyCanonicalSignedRequest`
|
|
5391
|
-
* into a reusable primitive — `(schema, opts) -> { verify, canonicalBytes, signRequest }`.
|
|
5392
|
-
* Both halves derive their canonical bytes through the same function so any
|
|
5393
|
-
* Zod schema change automatically propagates to both signer and verifier.
|
|
4638
|
+
* Signatures cover the raw input, not Zod-parsed output. Schema validation
|
|
4639
|
+
* may apply defaults or transformations without changing the signed bytes.
|
|
5394
4640
|
*/
|
|
5395
4641
|
declare function createSignedRequestVerifier<T = unknown>(schema: ZodLike<T>, opts?: CreateSignedRequestVerifierOpts): SignedRequestVerifier<T>;
|
|
5396
4642
|
/**
|
|
5397
|
-
*
|
|
5398
|
-
*
|
|
5399
|
-
*
|
|
5400
|
-
*
|
|
5401
|
-
* re-verify. They used to disagree — quote checked the raw `body.data`, submit
|
|
5402
|
-
* checked `JobManager.parseInput`'s Zod output — so the two agreed only where
|
|
5403
|
-
* the parse happened to be an identity on the caller's payload. A nested
|
|
5404
|
-
* `.default()`, a `.transform()`, a `z.coerce.*`, or any key the caller sent
|
|
5405
|
-
* that the schema doesn't declare made the quote pass and the paid submit 401
|
|
5406
|
-
* `signature_invalid`, blaming the signature for a schema shape the caller
|
|
5407
|
-
* never saw.
|
|
5408
|
-
*
|
|
5409
|
-
* Raw is what `public SDK documentation` specifies ("take the body, remove the
|
|
5410
|
-
* `signature` field, and canonicalise"), what the CLI signs, and what the SDK
|
|
5411
|
-
* persists as `record.input` — so the parsed form has no claim here. The
|
|
5412
|
-
* capability schema still validates what the handler receives; it just doesn't
|
|
5413
|
-
* get to rewrite the bytes the signature covers. It also means the credit
|
|
5414
|
-
* envelope needs no re-merge: it never left.
|
|
5415
|
-
*
|
|
5416
|
-
* `body.data` wins when present; otherwise the JSON-encoded `body.input` form
|
|
5417
|
-
* is parsed. Anything else — no input at all, a non-JSON legacy string — yields
|
|
5418
|
-
* `{}`, which carries no envelope and so fails the auth check, exactly as
|
|
5419
|
-
* before.
|
|
4643
|
+
* Raw structured input for authentication and durable replay. data wins;
|
|
4644
|
+
* otherwise input is parsed as JSON, with absent/legacy text becoming {}.
|
|
4645
|
+
* Builder validation may transform the handler input but cannot rewrite these
|
|
4646
|
+
* signing bytes, including protocol-owned credit fields.
|
|
5420
4647
|
*/
|
|
5421
4648
|
declare function signedRequestInput(body: {
|
|
5422
4649
|
input?: string;
|
|
@@ -5427,13 +4654,13 @@ declare function signedRequestInput(body: {
|
|
|
5427
4654
|
* cross-machine `SignedRequestReplayStore` is supplied. 10-minute retention
|
|
5428
4655
|
* window, 100k entry cap, per-process. Exported so `secp256k1Auth(...)` can
|
|
5429
4656
|
* pre-build one and share it across every per-schema verifier plus its own
|
|
5430
|
-
* `recordReplay` path
|
|
4657
|
+
* `recordReplay` path.
|
|
5431
4658
|
*
|
|
5432
4659
|
* When the cap is hit after the time-expired sweep, fresh inserts are
|
|
5433
4660
|
* **rejected** (return `true` — treated as replay at the wire) rather than
|
|
5434
4661
|
* silently evicting an in-window entry. Silent eviction would flush a
|
|
5435
4662
|
* still-valid nonce back to "unseen" and let an attacker who can drive cap
|
|
5436
|
-
* pressure replay it
|
|
4663
|
+
* pressure replay it. A rate-limited structured warning fires so
|
|
5437
4664
|
* operators see saturation in `fly logs`; reaching cap means in-memory has
|
|
5438
4665
|
* been pushed past where it's safe and the deploy should move to
|
|
5439
4666
|
* `PostgresReplayStore`.
|
|
@@ -5441,55 +4668,15 @@ declare function signedRequestInput(body: {
|
|
|
5441
4668
|
declare function createDefaultReplayStore(): SignedRequestReplayStore;
|
|
5442
4669
|
|
|
5443
4670
|
/**
|
|
5444
|
-
*
|
|
5445
|
-
*
|
|
5446
|
-
*
|
|
5447
|
-
* 1. {@link checkAuth} fires at request entry on `/v1/quote` and `/v1/job`,
|
|
5448
|
-
* before payment verification. Schema / drift / signature checks only —
|
|
5449
|
-
* no replay-store touch. Unsigned / malformed / stale / wrong-sig
|
|
5450
|
-
* envelopes reject with HTTP 401 with no Cashu round-trip and no pending
|
|
5451
|
-
* revenue row (internal-review).
|
|
5452
|
-
* 2. {@link recordReplay} fires on `/v1/job` only, after upfront payment is
|
|
5453
|
-
* verified and immediately before `jobManager.createJob`. Commits the
|
|
5454
|
-
* `(pubkey, timestamp, nonce)` tuple to the replay store; throws 401
|
|
5455
|
-
* `replay_detected` on duplicate. The unpaid 402-discovery hop does not
|
|
5456
|
-
* burn the nonce, so clients can re-submit with payment.
|
|
5457
|
-
*
|
|
5458
|
-
* On `replay_detected` after payment was already accepted, the SDK responds
|
|
5459
|
-
* 401 fail-closed uniformly across cashu / mppx / x402 — no rail-specific
|
|
5460
|
-
* refund or compensation logic (internal-review / internal-review). The Cashu accept path
|
|
5461
|
-
* commits proofs into the accumulator wallet inside `verifyUpfrontPayment`
|
|
5462
|
-
* before `recordReplay` runs (internal-review rejects unlocked Cashu upstream, so
|
|
5463
|
-
* the accumulator is the only production path), so no proofs are held to
|
|
5464
|
-
* refund; mppx / x402 credentials are single-use and already consumed by
|
|
5465
|
-
* the facilitator.
|
|
5466
|
-
*
|
|
5467
|
-
* The SDK picks the active capability's `input` schema and dispatches the
|
|
5468
|
-
* `checkAuth` call against it — schemes themselves take no schema parameter,
|
|
5469
|
-
* so one declaration covers every capability under a multi-capability DVM.
|
|
5470
|
-
*
|
|
5471
|
-
* A scheme validates the input it is handed, and on `/v1/job` that input
|
|
5472
|
-
* carries the credit envelope (`credit_id` / `draw_id` / `fund`) because the
|
|
5473
|
-
* signature covers it. Those keys are the protocol's, not the capability's, so
|
|
5474
|
-
* a scheme must not fail a schema that doesn't declare them — `secp256k1Auth`
|
|
5475
|
-
* removes them for the schema check only, keeping the signing bytes verbatim
|
|
5476
|
-
* (internal-review). A custom scheme owes callers the same tolerance, or every strict
|
|
5477
|
-
* schema 401s on an explicit draw.
|
|
4671
|
+
* Boot-summary backend: postgres is shared across machines; memory is per-process.
|
|
4672
|
+
* Explicit user-supplied stores and null (disabled) are never overridden.
|
|
5478
4673
|
*/
|
|
4674
|
+
type ReplayStoreBackend = "postgres" | "memory" | "user-supplied" | "disabled";
|
|
5479
4675
|
/**
|
|
5480
|
-
*
|
|
5481
|
-
*
|
|
5482
|
-
*
|
|
5483
|
-
* - `postgres` — SDK auto-wired a `PostgresReplayStore` against the host's
|
|
5484
|
-
* pool. Cross-machine safe.
|
|
5485
|
-
* - `memory` — SDK fell back to the in-memory FIFO. Per-process; unsafe for
|
|
5486
|
-
* multi-machine deploys. `createDVMHost` emits a warning (or throws under
|
|
5487
|
-
* `DVMKIT_FAIL_FAST=true`) when this kicks in outside `devMode`.
|
|
5488
|
-
* - `user-supplied` — user passed an explicit `replayStore` at factory time.
|
|
5489
|
-
* SDK doesn't override.
|
|
5490
|
-
* - `disabled` — user passed `replayStore: null`. No replay tracking.
|
|
4676
|
+
* Auth runs before payment; replay commits after accepted upfront payment.
|
|
4677
|
+
* Custom schemes must validate builder input while tolerating signed protocol
|
|
4678
|
+
* fields (credit, recovery and auth statement) outside the builder schema.
|
|
5491
4679
|
*/
|
|
5492
|
-
type ReplayStoreBackend = "postgres" | "memory" | "user-supplied" | "disabled";
|
|
5493
4680
|
interface DVMAuthScheme {
|
|
5494
4681
|
/** Wire-level identifier surfaced on `/v1/info#auth`. */
|
|
5495
4682
|
readonly id: typeof SIGNED_REQUEST_AUTH_ID;
|
|
@@ -5499,19 +4686,15 @@ interface DVMAuthScheme {
|
|
|
5499
4686
|
envelope: T & CanonicalEnvelope;
|
|
5500
4687
|
};
|
|
5501
4688
|
/**
|
|
5502
|
-
*
|
|
5503
|
-
*
|
|
5504
|
-
*
|
|
5505
|
-
* otherwise be trusted as authentic, and a compromised row could feed an
|
|
5506
|
-
* attacker-supplied pubkey/envelope into the handler. Throws
|
|
5507
|
-
* {@link SignedRequestError} with `sub_reason: "signature_invalid"` or
|
|
5508
|
-
* `"schema_invalid"` on failure.
|
|
4689
|
+
* Reactivation validates schema and signature without timestamp drift or replay
|
|
4690
|
+
* checks: the original timestamp and already-committed nonce must remain usable.
|
|
4691
|
+
* Throws signature_invalid or schema_invalid on failed validation.
|
|
5509
4692
|
*/
|
|
5510
4693
|
recheckSignature<T>(schema: ZodLike<T>, input: unknown, domain?: SignedRequestDomain): void;
|
|
5511
4694
|
/** Commit the envelope's `(pubkey, timestamp, nonce)` to the replay store. Throws on duplicate. */
|
|
5512
4695
|
recordReplay(envelope: CanonicalEnvelope): Promise<void>;
|
|
5513
4696
|
/**
|
|
5514
|
-
* Called by `createDVMHost` after the Postgres pool is resolved
|
|
4697
|
+
* Called by `createDVMHost` after the Postgres pool is resolved.
|
|
5515
4698
|
* The SDK passes a Postgres store when one is available; the scheme adopts
|
|
5516
4699
|
* it iff the user did not supply `replayStore` explicitly at factory time.
|
|
5517
4700
|
* Returns the resolved backend for boot-summary logging. User code should
|
|
@@ -5521,20 +4704,11 @@ interface DVMAuthScheme {
|
|
|
5521
4704
|
postgresStore?: SignedRequestReplayStore;
|
|
5522
4705
|
}): ReplayStoreBackend;
|
|
5523
4706
|
}
|
|
5524
|
-
/** Options for `secp256k1Auth()`. */
|
|
5525
4707
|
interface Secp256k1AuthOpts {
|
|
5526
4708
|
/**
|
|
5527
|
-
*
|
|
5528
|
-
*
|
|
5529
|
-
*
|
|
5530
|
-
* window, 100k cap) only when no pool exists — emitting a warning (or
|
|
5531
|
-
* throwing under `DVMKIT_FAIL_FAST=true`) outside `devMode` so multi-machine
|
|
5532
|
-
* deploys can't silently inherit the per-process default (internal-review).
|
|
5533
|
-
*
|
|
5534
|
-
* Pass a custom `SignedRequestReplayStore` here only to override that
|
|
5535
|
-
* default (e.g. a different cross-machine backend), or pass `null` to
|
|
5536
|
-
* disable replay tracking entirely. Any explicit value — including `null` —
|
|
5537
|
-
* locks the choice: the host-boot wiring never overrides it.
|
|
4709
|
+
* Omitting this lets the host adopt Postgres, otherwise falling back to a
|
|
4710
|
+
* bounded per-process store with a warning or fail-fast refusal outside dev mode.
|
|
4711
|
+
* Any explicit store, including null to disable replay tracking, is locked.
|
|
5538
4712
|
*/
|
|
5539
4713
|
replayStore?: SignedRequestReplayStore | null;
|
|
5540
4714
|
/** Permitted clock drift in seconds. Defaults to 300 (5 minutes). */
|
|
@@ -5543,31 +4717,16 @@ interface Secp256k1AuthOpts {
|
|
|
5543
4717
|
now?: () => number;
|
|
5544
4718
|
}
|
|
5545
4719
|
/**
|
|
5546
|
-
*
|
|
5547
|
-
*
|
|
5548
|
-
*
|
|
5549
|
-
* Declare on `configureDVM({ auth: secp256k1Auth({...}) })` to gate every
|
|
5550
|
-
* capability's `/v1/job` and `/v1/quote` on a signed request envelope
|
|
5551
|
-
* (`pubkey` / `signature` / `timestamp` / `nonce`) over the canonical JSON
|
|
5552
|
-
* form of the capability's input. The SDK calls {@link DVMAuthScheme.checkAuth}
|
|
5553
|
-
* at request entry on both endpoints, and {@link DVMAuthScheme.recordReplay}
|
|
5554
|
-
* on `/v1/job` only — after upfront payment is verified, just before the job
|
|
5555
|
-
* is created. The 402-discovery hop on `/v1/job` therefore does not consume
|
|
5556
|
-
* the replay nonce; the same signed envelope is accepted on the paid resubmit.
|
|
5557
|
-
*
|
|
5558
|
-
* One scheme instance covers every capability under the DVM — internally
|
|
5559
|
-
* the SDK caches a per-schema verifier so each capability's Zod schema is
|
|
5560
|
-
* compiled once. The replay store is shared across all capabilities.
|
|
4720
|
+
* One scheme covers all capabilities and shares their replay store. Per-schema
|
|
4721
|
+
* verifiers validate without touching replay state; the host commits replay
|
|
4722
|
+
* only after upfront payment verification, immediately before job creation.
|
|
5561
4723
|
*/
|
|
5562
4724
|
declare function secp256k1Auth(opts?: Secp256k1AuthOpts): DVMAuthScheme;
|
|
5563
4725
|
|
|
5564
4726
|
/** Structural type matching any Zod schema. Avoids a hard dependency on zod. */
|
|
5565
4727
|
interface ZodLike<T = unknown> {
|
|
5566
|
-
/** Internal Zod metadata (used for duck-type detection). */
|
|
5567
4728
|
_zod: unknown;
|
|
5568
|
-
/** Parse and validate data, throwing on failure. */
|
|
5569
4729
|
parse(data: unknown): T;
|
|
5570
|
-
/** Parse and validate data, returning a result object instead of throwing. */
|
|
5571
4730
|
safeParse(data: unknown): {
|
|
5572
4731
|
success: true;
|
|
5573
4732
|
data: T;
|
|
@@ -5576,28 +4735,13 @@ interface ZodLike<T = unknown> {
|
|
|
5576
4735
|
error: unknown;
|
|
5577
4736
|
};
|
|
5578
4737
|
}
|
|
5579
|
-
/** Extract the output type from a ZodLike schema, or default to string. */
|
|
5580
4738
|
type InputType<T> = T extends ZodLike<infer O> ? O : string;
|
|
5581
4739
|
/**
|
|
5582
|
-
*
|
|
5583
|
-
*
|
|
5584
|
-
* Surfaces the Zod schema's output type so a drifted example produces a
|
|
5585
|
-
* TypeScript error at the `configureDVM` call site. Falls back to
|
|
5586
|
-
* `Record<string, unknown>` when `InputSchema` is undefined or a plain JSON
|
|
5587
|
-
* Schema object (which can't validate at type-level; the runtime
|
|
5588
|
-
* `validateExampleAgainstInput` in `configure.ts` covers schema-less paths).
|
|
5589
|
-
*
|
|
5590
|
-
* Approximation: `ZodLike<T>` exposes only one type param. True `z.input`
|
|
5591
|
-
* distinguishes pre-coercion input from post-coercion output for schemas
|
|
5592
|
-
* that use `.transform()` or `z.coerce.*`. None of the first-party DVMs do
|
|
5593
|
-
* that on their input schemas today. Revisit — extend `ZodLike` to a
|
|
5594
|
-
* two-param shape `ZodLike<Output, Input = Output>` — if a builder ever
|
|
5595
|
-
* hits a real divergence on `example`.
|
|
4740
|
+
* Zod examples use the schema output type, including transforms. Plain JSON Schema examples have
|
|
4741
|
+
* no equivalent static validation.
|
|
5596
4742
|
*/
|
|
5597
4743
|
type ExampleType<InputSchema> = [InputSchema] extends [ZodLike<infer O>] ? O : Record<string, unknown>;
|
|
5598
|
-
/** Runtime check for whether a value is a Zod schema. */
|
|
5599
4744
|
declare function isZodSchema(value: unknown): value is ZodLike;
|
|
5600
|
-
/** Structured logging interface injected via ctx.log. */
|
|
5601
4745
|
interface Logger {
|
|
5602
4746
|
debug(message: string, data?: Record<string, unknown>): void;
|
|
5603
4747
|
info(message: string, data?: Record<string, unknown>): void;
|
|
@@ -5612,21 +4756,17 @@ interface KVStore {
|
|
|
5612
4756
|
set(key: string, value: unknown, opts?: {
|
|
5613
4757
|
ttl?: number;
|
|
5614
4758
|
}): Promise<void>;
|
|
5615
|
-
/** Delete a key. */
|
|
5616
4759
|
delete(key: string): Promise<void>;
|
|
5617
4760
|
/** List keys, optionally filtered by prefix. */
|
|
5618
4761
|
list(prefix?: string): Promise<string[]>;
|
|
5619
4762
|
}
|
|
5620
|
-
/** Options for ctx.prompt(). */
|
|
5621
4763
|
interface PromptOpts {
|
|
5622
4764
|
/** Preset choices the client can select from. */
|
|
5623
4765
|
options?: string[];
|
|
5624
4766
|
/** JSON Schema describing the expected structured response. */
|
|
5625
4767
|
schema?: Record<string, unknown>;
|
|
5626
4768
|
}
|
|
5627
|
-
/** Options for ctx.requestPayment(). */
|
|
5628
4769
|
interface SDKPaymentRequestOpts {
|
|
5629
|
-
/** Cashu mints accepted for this payment. */
|
|
5630
4770
|
mints?: string[];
|
|
5631
4771
|
/** Minimum P2PK locktime in seconds. */
|
|
5632
4772
|
minLocktimeSeconds?: number;
|
|
@@ -5642,11 +4782,11 @@ interface SDKPaymentRequestOpts {
|
|
|
5642
4782
|
* pre-issued mppx challenge per accepted method/intent. Overrides the
|
|
5643
4783
|
* SDK's auto-issued challenges; if omitted, and the SDK is configured for
|
|
5644
4784
|
* MPP, challenges are issued automatically. Each challenge's `id` becomes
|
|
5645
|
-
* part of this job's per-job replay binding
|
|
4785
|
+
* part of this job's per-job replay binding.
|
|
5646
4786
|
*
|
|
5647
4787
|
* `session`-intent challenges are dropped from this list, whoever issued
|
|
5648
4788
|
* them: reusable channels fund credits on `POST /v1/credit` and are refused
|
|
5649
|
-
* everywhere on the job surface
|
|
4789
|
+
* everywhere on the job surface.
|
|
5650
4790
|
*/
|
|
5651
4791
|
mpp?: {
|
|
5652
4792
|
challenges: MppxChallenge[];
|
|
@@ -5679,22 +4819,18 @@ interface JobCost {
|
|
|
5679
4819
|
/** ISO 4217 lowercase, e.g. `"usd"`. */
|
|
5680
4820
|
currency: Currency;
|
|
5681
4821
|
}
|
|
5682
|
-
/** Extended job context provided by the SDK runtime to DVM handlers. */
|
|
5683
4822
|
interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
5684
|
-
/** Server-assigned job ID. */
|
|
5685
4823
|
readonly jobId: string;
|
|
5686
|
-
/** Tags from the DVM descriptor (what this DVM handles). */
|
|
5687
4824
|
readonly tags: string[];
|
|
5688
4825
|
/** Job input — raw string or parsed/validated object when an input schema is defined. */
|
|
5689
4826
|
readonly input: Input;
|
|
5690
|
-
/** Job parameters as key-value pairs. */
|
|
5691
4827
|
readonly params: Record<string, string>;
|
|
5692
4828
|
/** Opaque requester identifier (derived from auth token or "anonymous"). */
|
|
5693
4829
|
readonly requesterId: string;
|
|
5694
4830
|
/** Total msats paid so far for this job. */
|
|
5695
4831
|
readonly paidMsats: number;
|
|
5696
4832
|
/**
|
|
5697
|
-
* Verified caller identity from the descriptor's `auth` scheme
|
|
4833
|
+
* Verified caller identity from the descriptor's `auth` scheme.
|
|
5698
4834
|
* Present when the DVM declares `auth: secp256k1Auth(...)`; undefined for
|
|
5699
4835
|
* DVMs without descriptor-level auth. The envelope is the same `T &
|
|
5700
4836
|
* CanonicalEnvelope` the auth scheme produced — handlers can read
|
|
@@ -5704,24 +4840,17 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5704
4840
|
pubkey: string;
|
|
5705
4841
|
envelope: CanonicalEnvelope & Record<string, unknown>;
|
|
5706
4842
|
};
|
|
5707
|
-
/** Send a progress text message to the client. */
|
|
5708
4843
|
text(message: string): void;
|
|
5709
|
-
/** Send an artifact (output data) to the client. */
|
|
5710
4844
|
artifact(content: ArtifactContent): void;
|
|
5711
4845
|
/** Send a raw typed message (escape hatch). */
|
|
5712
4846
|
sendMessage(type: MessageType, content: Record<string, unknown>): void;
|
|
5713
4847
|
/** Ask the client a question. Resolves when the client responds. */
|
|
5714
4848
|
prompt(id: string, text: string, opts?: PromptOpts): Promise<ResponseContent>;
|
|
5715
4849
|
/**
|
|
5716
|
-
* Request payment
|
|
5717
|
-
*
|
|
5718
|
-
*
|
|
5719
|
-
*
|
|
5720
|
-
* send-time via its centralised fx fetcher. v1 supports `currency: "usd"`
|
|
5721
|
-
* only; other currencies throw `UnsupportedCurrencyError`.
|
|
5722
|
-
*
|
|
5723
|
-
* The legacy `string` form (`"$0.05"`) was removed in internal-review under the
|
|
5724
|
-
* project's no-shims convention. Migrate callers to the fiat envelope.
|
|
4850
|
+
* Request payment; resolves when the ask is satisfied by the upfront pool or a payment.
|
|
4851
|
+
* Numbers are millisatoshis. Fiat envelopes use major units in the supplied
|
|
4852
|
+
* supported currency; the ledger pin is converted into the job's denomination.
|
|
4853
|
+
* x402 is offered only for USD; other configured rails can use supported FX currencies.
|
|
5725
4854
|
*/
|
|
5726
4855
|
requestPayment(amount: number | {
|
|
5727
4856
|
amount: number;
|
|
@@ -5742,14 +4871,14 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5742
4871
|
/**
|
|
5743
4872
|
* Fail the job with an error message. No debit happens either way — the
|
|
5744
4873
|
* terminal funnel releases the credit draw ({@link RAIL_REFUNDABLE}).
|
|
5745
|
-
* `{ refund: true }` annotates the failure as caller-fault
|
|
4874
|
+
* `{ refund: true }` annotates the failure as caller-fault; it
|
|
5746
4875
|
* moves no money on any rail and never has.
|
|
5747
4876
|
*/
|
|
5748
4877
|
fail(error: string, opts?: {
|
|
5749
4878
|
refund?: boolean;
|
|
5750
4879
|
}): void;
|
|
5751
4880
|
/**
|
|
5752
|
-
* Declare what this job cost **you**, the builder, to serve
|
|
4881
|
+
* Declare what this job cost **you**, the builder, to serve.
|
|
5753
4882
|
*
|
|
5754
4883
|
* The SDK reports it to the platform alongside the job's revenue, so the
|
|
5755
4884
|
* dashboard can state a margin. Without it the margin is unknowable, and the
|
|
@@ -5777,9 +4906,9 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5777
4906
|
*
|
|
5778
4907
|
* ```ts
|
|
5779
4908
|
* const r = await ctx.step("transcribe", async () => {
|
|
5780
|
-
*
|
|
5781
|
-
*
|
|
5782
|
-
*
|
|
4909
|
+
* const result = await whisper(audio);
|
|
4910
|
+
* ctx.cost({ amount: result.seconds * 0.0001, currency: "usd" });
|
|
4911
|
+
* return result;
|
|
5783
4912
|
* });
|
|
5784
4913
|
* ```
|
|
5785
4914
|
*
|
|
@@ -5805,7 +4934,7 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5805
4934
|
step<T>(id: string, fn: () => Promise<T>): Promise<T>;
|
|
5806
4935
|
/** Per-job typed state, auto-loaded and auto-persisted. */
|
|
5807
4936
|
state: State;
|
|
5808
|
-
/** Persistent
|
|
4937
|
+
/** Persistent storage shared across jobs. */
|
|
5809
4938
|
store: KVStore;
|
|
5810
4939
|
/**
|
|
5811
4940
|
* Instrumented fetch — emits `dvm.fetch` spans with redacted URLs, and
|
|
@@ -5814,7 +4943,7 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5814
4943
|
*/
|
|
5815
4944
|
fetch: typeof globalThis.fetch;
|
|
5816
4945
|
/**
|
|
5817
|
-
* Cancellation signal for this job
|
|
4946
|
+
* Cancellation signal for this job. Aborts when the caller
|
|
5818
4947
|
* cancels, the job idles out, or the handler is superseded. `ctx.fetch`
|
|
5819
4948
|
* already carries it; pass it into anything else that can be torn down —
|
|
5820
4949
|
* a provider SDK's `signal` option, a `spawn`, your own sleeps — so a
|
|
@@ -5822,24 +4951,20 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5822
4951
|
* / `ctx.fail` are no-ops: the job stays `cancelled`.
|
|
5823
4952
|
*/
|
|
5824
4953
|
readonly signal: AbortSignal;
|
|
5825
|
-
/** Environment variables configured by the operator. */
|
|
5826
4954
|
env: Record<string, string>;
|
|
5827
|
-
/** Structured logging. */
|
|
5828
4955
|
log: Logger;
|
|
5829
4956
|
}
|
|
5830
|
-
/** Context provided to the onQuote handler. */
|
|
5831
4957
|
interface QuoteContext<QuoteData = Record<string, unknown>> {
|
|
5832
|
-
/**
|
|
4958
|
+
/** Quote data, parsed by the configured Zod schema when present. */
|
|
5833
4959
|
data: QuoteData;
|
|
5834
|
-
/**
|
|
4960
|
+
/** The client's raw input string, when supplied. */
|
|
5835
4961
|
input?: string;
|
|
5836
|
-
/** Job parameters from the client, if provided. */
|
|
5837
4962
|
params?: Record<string, string>;
|
|
5838
4963
|
/**
|
|
5839
4964
|
* Operator environment — same object threaded into `JobContext.env`.
|
|
5840
4965
|
* Quote handlers read it for pre-payment checks (API-key presence, feature
|
|
5841
4966
|
* flags, model overrides) so failures surface as a structured 400 rather
|
|
5842
|
-
* than after the upfront fee has been debited.
|
|
4967
|
+
* than after the upfront fee has been debited.
|
|
5843
4968
|
*/
|
|
5844
4969
|
env: Record<string, string>;
|
|
5845
4970
|
/**
|
|
@@ -5849,16 +4974,16 @@ interface QuoteContext<QuoteData = Record<string, unknown>> {
|
|
|
5849
4974
|
*/
|
|
5850
4975
|
fetch: typeof globalThis.fetch;
|
|
5851
4976
|
/**
|
|
5852
|
-
* Verified caller pubkey from the signed-request envelope
|
|
5853
|
-
*
|
|
4977
|
+
* Verified caller pubkey from the signed-request envelope. Present only when
|
|
4978
|
+
* the descriptor declares an auth scheme and the
|
|
5854
4979
|
* envelope verified — `/v1/quote` runs `checkAuth` before the handler, so by
|
|
5855
4980
|
* the time this is set the signature is good. Undefined on unauthenticated
|
|
5856
4981
|
* DVMs; quote handlers that price per-caller must handle that.
|
|
5857
4982
|
*/
|
|
5858
4983
|
callerPubkey?: string;
|
|
5859
4984
|
/**
|
|
5860
|
-
* The authenticated caller's standing prepaid credit at this DVM, if any
|
|
5861
|
-
*
|
|
4985
|
+
* The authenticated caller's standing prepaid credit at this DVM, if any,
|
|
4986
|
+
* so one round trip answers "cost + my remaining
|
|
5862
4987
|
* credit". Undefined when the DVM doesn't offer credit, the caller is
|
|
5863
4988
|
* unauthenticated, or they hold no live credit in this DVM's currency.
|
|
5864
4989
|
*
|
|
@@ -5869,8 +4994,8 @@ interface QuoteContext<QuoteData = Record<string, unknown>> {
|
|
|
5869
4994
|
credit?: CreditView;
|
|
5870
4995
|
}
|
|
5871
4996
|
/**
|
|
5872
|
-
* Read-only snapshot of a caller's credit, surfaced on {@link QuoteContext}
|
|
5873
|
-
*
|
|
4997
|
+
* Read-only snapshot of a caller's credit, surfaced on {@link QuoteContext};
|
|
4998
|
+
* it mirrors the ledger's `CreditSnapshot` minus the write surface
|
|
5874
4999
|
* and the fields a quote handler has no business branching on.
|
|
5875
5000
|
*/
|
|
5876
5001
|
interface CreditView {
|
|
@@ -5888,7 +5013,7 @@ interface CreditView {
|
|
|
5888
5013
|
expired: boolean;
|
|
5889
5014
|
}
|
|
5890
5015
|
/**
|
|
5891
|
-
* Result returned by the onQuote handler — the fiat-first envelope
|
|
5016
|
+
* Result returned by the onQuote handler — the fiat-first envelope.
|
|
5892
5017
|
*
|
|
5893
5018
|
* The protocol enforces exactly one hard invariant — `upfront` — and pushes
|
|
5894
5019
|
* every other pricing detail into prose. LLM-agent intermediaries read
|
|
@@ -5955,35 +5080,29 @@ interface QuoteResult {
|
|
|
5955
5080
|
max_units?: number;
|
|
5956
5081
|
};
|
|
5957
5082
|
}
|
|
5958
|
-
/** Quote handler configuration with a Zod schema for structured quote data. */
|
|
5959
5083
|
interface QuoteConfig<QuoteData = Record<string, unknown>> {
|
|
5960
|
-
/** Schema describing the data clients must provide for a quote. */
|
|
5961
5084
|
schema: ZodLike<QuoteData> | Record<string, unknown>;
|
|
5962
|
-
/** Compute a price based on the structured quote data. */
|
|
5963
5085
|
handler(ctx: QuoteContext<QuoteData>): Promise<QuoteResult>;
|
|
5964
5086
|
}
|
|
5965
|
-
/** Incoming message shape for the onMessage catch-all handler. */
|
|
5966
5087
|
interface IncomingMessage {
|
|
5967
5088
|
/** Message type (e.g. "response", "payment", "approval", "cancel"). */
|
|
5968
5089
|
type: MessageType;
|
|
5969
|
-
/** Raw message content. */
|
|
5970
5090
|
content: Record<string, unknown>;
|
|
5971
5091
|
}
|
|
5972
5092
|
/**
|
|
5973
5093
|
* A builder-declared price — a USD literal, always (`"$0.05"`).
|
|
5974
5094
|
*
|
|
5975
|
-
* Fiat is the only static price form
|
|
5095
|
+
* Fiat is the only static price form. The credit ledger is
|
|
5976
5096
|
* fiat-micro denominated, so a raw-msats price could only reach the ledger
|
|
5977
5097
|
* through a BTC/USD lookup that may be unavailable — which is how a
|
|
5978
5098
|
* numeric-priced DVM under a cold-start rate outage used to take money and
|
|
5979
|
-
* record no draw
|
|
5099
|
+
* record no draw. Rails are unaffected: callers still pay sats,
|
|
5980
5100
|
* the SDK converts at request time.
|
|
5981
5101
|
*/
|
|
5982
5102
|
type PriceValue = string;
|
|
5983
|
-
/** Accepted payment rail identifier. */
|
|
5984
5103
|
type PaymentMethod = "cashu" | "x402" | "tempo";
|
|
5985
5104
|
/**
|
|
5986
|
-
* Rails a prepaid **credit
|
|
5105
|
+
* Rails that may fund a prepaid **credit**.
|
|
5987
5106
|
*
|
|
5988
5107
|
* A deliberate superset of {@link PaymentMethod}, not a widening of it.
|
|
5989
5108
|
* `lightning` funds a credit through the interactive invoice leg on
|
|
@@ -5991,59 +5110,40 @@ type PaymentMethod = "cashu" | "x402" | "tempo";
|
|
|
5991
5110
|
* connection and credits when settlement is observed — but it is **not** an
|
|
5992
5111
|
* attached-proof wire method: there is no header a caller can hang a bolt11
|
|
5993
5112
|
* on, nothing to verify inline, and no `lightning` entry in `/v1/info` or
|
|
5994
|
-
* `/v1/quote`'s `payment.methods
|
|
5995
|
-
* remnants of the per-call surface). Keeping the two types distinct is what
|
|
5113
|
+
* `/v1/quote`'s `payment.methods`. Keeping the two types distinct is what
|
|
5996
5114
|
* stops that dead rail from reappearing every time the funding menu grows.
|
|
5997
5115
|
*/
|
|
5998
5116
|
type FundingMethod = PaymentMethod | "lightning";
|
|
5999
5117
|
/**
|
|
6000
|
-
* Whether
|
|
6001
|
-
*
|
|
6002
|
-
*
|
|
6003
|
-
* the P2PK-accumulator Cashu path — the only Cashu path post-internal-review/internal-review —
|
|
6004
|
-
* `verifyAccumulatorReceipt` commits the caller's proofs straight into
|
|
6005
|
-
* `wallet_accumulator` and leaves `job.receivedProofs` empty; every producer in
|
|
6006
|
-
* `payment.ts` returns `[]`. The `ctx.fail(err, { refund: true })` branch that
|
|
6007
|
-
* used to send proofs back gated on those held proofs, so it had been an
|
|
6008
|
-
* unreachable no-op for as long as the accumulator has been the commit boundary,
|
|
6009
|
-
* and internal-review deleted it. This fail-closed posture was locked by internal-review
|
|
6010
|
-
* (Decision C) and pinned by internal-review. mpp (Tempo) and x402 credentials are
|
|
6011
|
-
* single-use, final-settlement — no SDK-managed reversal path either.
|
|
6012
|
-
*
|
|
6013
|
-
* **This is a rail-level question, and since internal-review it is no longer the whole
|
|
6014
|
-
* story.** A failed job never debits: the draw is a hold the terminal funnel
|
|
6015
|
-
* releases (`JobManager.resolveCreditDraw`), so the value stays on the caller's
|
|
6016
|
-
* credit. What this flag reports is narrower and still true — no rail hands
|
|
6017
|
-
* value backwards — and reclaiming a released balance runs through the
|
|
6018
|
-
* internal-review `drain` op, which exists only on a DVM that advertises credit.
|
|
5118
|
+
* Whether the payment rail returns value when a job fails. No rail reverses
|
|
5119
|
+
* `ctx.fail`: Cashu proofs are committed to the accumulator; Tempo and x402
|
|
5120
|
+
* credentials settle without an SDK reversal.
|
|
6019
5121
|
*
|
|
6020
|
-
*
|
|
6021
|
-
*
|
|
6022
|
-
*
|
|
6023
|
-
*
|
|
6024
|
-
* `true`, was cancelled as superseded: the ledger is where reclaim lives now.
|
|
6025
|
-
* x402 refund parity remains a deferred internal-review exploration.
|
|
5122
|
+
* Failed credit jobs release their draw hold back to the caller's balance.
|
|
5123
|
+
* Reclaiming that balance uses `drain`; this flag describes rail reversal,
|
|
5124
|
+
* independently of the credit ledger. `{ refund: true }` records caller fault
|
|
5125
|
+
* and does not move funds.
|
|
6026
5126
|
*/
|
|
6027
5127
|
declare const RAIL_REFUNDABLE: Record<PaymentMethod, boolean>;
|
|
6028
5128
|
/**
|
|
6029
|
-
* Cashu receive mode for builder DVMs
|
|
5129
|
+
* Cashu receive mode for builder DVMs.
|
|
6030
5130
|
*
|
|
6031
5131
|
* `"p2pk-accumulator"` — accept P2PK-locked proofs from agent wallets and persist
|
|
6032
5132
|
* them in `wallet_accumulator`. Melting runs builder-local via `dvmctl melt-pending`
|
|
6033
|
-
*
|
|
5133
|
+
* the SDK only monitors batches for `t_expire` proximity.
|
|
6034
5134
|
*/
|
|
6035
5135
|
type CashuMode = "p2pk-accumulator";
|
|
6036
5136
|
/**
|
|
6037
|
-
* Prepaid-credit sizing this DVM advertises
|
|
5137
|
+
* Prepaid-credit sizing this DVM advertises.
|
|
6038
5138
|
*
|
|
6039
5139
|
* Declaring the block is the opt-in: a DVM without `credit` never offers the
|
|
6040
5140
|
* funding menu on `/v1/quote` or the 402, and `/v1/credit` refuses with
|
|
6041
5141
|
* `credit_not_supported`. Every payment still funds and draws the ledger
|
|
6042
|
-
* internally (the implicit N=1 case
|
|
5142
|
+
* internally (the implicit N=1 case) — this block is only about
|
|
6043
5143
|
* whether callers may hold a balance across jobs.
|
|
6044
5144
|
*
|
|
6045
5145
|
* Isolate-runtime DVMs never advertise credit regardless of this config
|
|
6046
|
-
* (
|
|
5146
|
+
* (the isolate-runtime rule): they have no Postgres of their own, so the money-safety
|
|
6047
5147
|
* conditions the ledger relies on can't hold there.
|
|
6048
5148
|
*/
|
|
6049
5149
|
interface CreditConfig {
|
|
@@ -6058,14 +5158,14 @@ interface CreditConfig {
|
|
|
6058
5158
|
min?: PriceValue;
|
|
6059
5159
|
/**
|
|
6060
5160
|
* Largest **residual balance** a caller may hold, as a `PriceValue`.
|
|
6061
|
-
* Binds the balance, not the funding amount (
|
|
5161
|
+
* Binds the balance, not the funding amount (the balance-sizing contract): a job priced above
|
|
6062
5162
|
* `max` still clears as fund-and-immediately-draw. Bounds the DVM's
|
|
6063
5163
|
* prepaid-deposit liability. Default `"$5.00"`.
|
|
6064
5164
|
*/
|
|
6065
5165
|
max?: PriceValue;
|
|
6066
5166
|
/**
|
|
6067
5167
|
* Credit lifetime in **seconds**, from the most recent funding. Post-TTL
|
|
6068
|
-
* the credit refuses new draws; the balance stays the caller's (
|
|
5168
|
+
* the credit refuses new draws; the balance stays the caller's (the caller-ownership contract).
|
|
6069
5169
|
* Default 30 days.
|
|
6070
5170
|
*/
|
|
6071
5171
|
ttl?: number;
|
|
@@ -6099,17 +5199,17 @@ interface ResolvedCreditConfig {
|
|
|
6099
5199
|
declare const DEFAULT_CREDIT_MIN: PriceValue;
|
|
6100
5200
|
/**
|
|
6101
5201
|
* Platform default for {@link CreditConfig.max} — the largest residual balance
|
|
6102
|
-
* a caller may hold. Pocket-money sizing (
|
|
5202
|
+
* a caller may hold. Pocket-money sizing (the exposure limit: prepaid exposure is
|
|
6103
5203
|
* capped by policy at pocket-money size), set above the caller-side tier-1
|
|
6104
5204
|
* cap of $2 so an established caller can actually reach the builder ceiling.
|
|
6105
5205
|
*/
|
|
6106
5206
|
declare const DEFAULT_CREDIT_MAX: PriceValue;
|
|
6107
|
-
/** Platform default for {@link CreditConfig.ttl}
|
|
5207
|
+
/** Platform default for {@link CreditConfig.ttl}: 30 days. */
|
|
6108
5208
|
declare const DEFAULT_CREDIT_TTL_SECONDS: number;
|
|
6109
5209
|
/** Default lifetime of caller-controlled job content before it is redacted. */
|
|
6110
5210
|
declare const DEFAULT_JOB_RETENTION_DAYS = 90;
|
|
6111
5211
|
/**
|
|
6112
|
-
* Per-capability configuration for the multi-capability shape
|
|
5212
|
+
* Per-capability configuration for the multi-capability shape.
|
|
6113
5213
|
* Each capability is an independent unit a caller pays for: its own input
|
|
6114
5214
|
* schema, pricing, handler, and (optional) message handlers. DVM-level
|
|
6115
5215
|
* concerns (auth, payment rails, tags, builder) stay on the parent
|
|
@@ -6121,13 +5221,13 @@ interface CapabilityConfig<State = Record<string, unknown>, InputSchema extends
|
|
|
6121
5221
|
/** Zod schema for structured job input. When set, ctx.input is parsed and typed. */
|
|
6122
5222
|
input?: InputSchema;
|
|
6123
5223
|
/**
|
|
6124
|
-
* Example input payload surfaced on `/v1/info
|
|
5224
|
+
* Example input payload surfaced on `/v1/info`. Lets the CLI render
|
|
6125
5225
|
* a runnable `dvm quote --data '<payload>'` line when the JSON Schema alone
|
|
6126
5226
|
* is ambiguous — e.g. cross-field Zod refinements that don't survive
|
|
6127
5227
|
* serialization (narrate's `text` XOR `dialogue`). Must satisfy the schema
|
|
6128
5228
|
* including any `.refine()` checks.
|
|
6129
5229
|
*
|
|
6130
|
-
* Typed via `ExampleType<InputSchema
|
|
5230
|
+
* Typed via `ExampleType<InputSchema>`: narrows to the schema's
|
|
6131
5231
|
* output type when `input` is a Zod schema, falls back to
|
|
6132
5232
|
* `Record<string, unknown>` otherwise.
|
|
6133
5233
|
*/
|
|
@@ -6136,7 +5236,6 @@ interface CapabilityConfig<State = Record<string, unknown>, InputSchema extends
|
|
|
6136
5236
|
state?: State;
|
|
6137
5237
|
/** Static price — a USD literal ("$0.05"). Free capabilities omit this and `onQuote`. */
|
|
6138
5238
|
price?: PriceValue;
|
|
6139
|
-
/** Quote handler for dynamic pricing. */
|
|
6140
5239
|
onQuote?: QuoteConfig;
|
|
6141
5240
|
/** Handle a newly submitted job for this capability (required). */
|
|
6142
5241
|
onJob: (ctx: SDKJobContext<State, InputType<InputSchema>>) => void | Promise<void>;
|
|
@@ -6152,7 +5251,7 @@ interface CapabilityConfig<State = Record<string, unknown>, InputSchema extends
|
|
|
6152
5251
|
onMessage?: (ctx: SDKJobContext<State, InputType<InputSchema>>, msg: IncomingMessage) => void | Promise<void>;
|
|
6153
5252
|
}
|
|
6154
5253
|
/**
|
|
6155
|
-
* Desugared per-capability descriptor
|
|
5254
|
+
* Desugared per-capability descriptor. `configureDVM` produces one
|
|
6156
5255
|
* of these per capability — the SDK runtime always operates on this shape.
|
|
6157
5256
|
*/
|
|
6158
5257
|
interface CapabilityDescriptor<State = unknown, InputSchema extends ZodLike | undefined = undefined> {
|
|
@@ -6162,15 +5261,13 @@ interface CapabilityDescriptor<State = unknown, InputSchema extends ZodLike | un
|
|
|
6162
5261
|
readonly description: string;
|
|
6163
5262
|
/** Zod schema for structured job input. */
|
|
6164
5263
|
readonly input?: InputSchema;
|
|
6165
|
-
/** Example input payload surfaced on `/v1/info
|
|
5264
|
+
/** Example input payload surfaced on `/v1/info`. See `CapabilityConfig.example`. */
|
|
6166
5265
|
readonly example?: ExampleType<InputSchema>;
|
|
6167
5266
|
/** Default per-job state for jobs of this capability. */
|
|
6168
5267
|
readonly state: State;
|
|
6169
5268
|
/** Static price — a USD literal ("$0.05"). Undefined for dynamic-priced or free. */
|
|
6170
5269
|
readonly price?: PriceValue;
|
|
6171
|
-
/** Quote handler for dynamic pricing. */
|
|
6172
5270
|
readonly onQuote?: QuoteConfig;
|
|
6173
|
-
/** Handle a newly submitted job. */
|
|
6174
5271
|
readonly onJob: (ctx: SDKJobContext<State, InputType<InputSchema>>) => void | Promise<void>;
|
|
6175
5272
|
/** Handle a client response to a prompt. */
|
|
6176
5273
|
readonly onResponse?: (ctx: SDKJobContext<State, InputType<InputSchema>>, content: ResponseContent) => void | Promise<void>;
|
|
@@ -6186,30 +5283,28 @@ interface CapabilityDescriptor<State = unknown, InputSchema extends ZodLike | un
|
|
|
6186
5283
|
/**
|
|
6187
5284
|
* Configuration object passed to configureDVM().
|
|
6188
5285
|
*
|
|
6189
|
-
* Two equivalent shapes
|
|
5286
|
+
* Two equivalent shapes:
|
|
6190
5287
|
*
|
|
6191
5288
|
* - **Flat single-capability shape** — declare `capability: "<name>"` plus
|
|
6192
|
-
*
|
|
6193
|
-
*
|
|
6194
|
-
*
|
|
6195
|
-
*
|
|
5289
|
+
* the per-capability fields (`input`, `state`, `price`/`onQuote`, `onJob`,
|
|
5290
|
+
* `onResponse`, …) at the top level. Desugars internally to a one-entry
|
|
5291
|
+
* `capabilities` map. Ergonomic for the common case (narrate, scribe,
|
|
5292
|
+
* scrape).
|
|
6196
5293
|
*
|
|
6197
5294
|
* - **Explicit multi-capability shape** — declare `capabilities: { "<name>":
|
|
6198
|
-
*
|
|
6199
|
-
*
|
|
6200
|
-
*
|
|
5295
|
+
* { input, onJob, … } }`. Used by services that expose several operations
|
|
5296
|
+
* on a shared resource model (cast's add-episode / update-feed /
|
|
5297
|
+
* delete-episode / delete-feed).
|
|
6201
5298
|
*
|
|
6202
5299
|
* The two are mutually exclusive: providing both throws at `configureDVM`.
|
|
6203
5300
|
* DVM-level concerns (tags, payment rails, idle timeout, lifecycle,
|
|
6204
5301
|
* descriptor routes) live on `DVMConfig` regardless of shape.
|
|
6205
5302
|
*/
|
|
6206
5303
|
interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike | undefined = undefined> {
|
|
6207
|
-
/** Display name for the DVM. */
|
|
6208
5304
|
name: string;
|
|
6209
|
-
/** Human-readable description. */
|
|
6210
5305
|
description?: string;
|
|
6211
5306
|
/**
|
|
6212
|
-
* Single-capability name for the flat shape
|
|
5307
|
+
* Single-capability name for the flat shape. Mutually exclusive
|
|
6213
5308
|
* with `capabilities`. Single-capability DVMs declare it here and put the
|
|
6214
5309
|
* per-capability fields (`input`, `state`, `price`/`onQuote`, `onJob`,
|
|
6215
5310
|
* `onResponse`, …) at the top level; `configureDVM` desugars to a
|
|
@@ -6217,7 +5312,7 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6217
5312
|
*/
|
|
6218
5313
|
capability?: string;
|
|
6219
5314
|
/**
|
|
6220
|
-
* Explicit multi-capability block
|
|
5315
|
+
* Explicit multi-capability block. Mutually exclusive with the
|
|
6221
5316
|
* flat shape's `capability` + top-level handler fields. Each key is a
|
|
6222
5317
|
* capability name (tag-shape: lowercase letters/digits/hyphens, no
|
|
6223
5318
|
* slashes); each value carries its own `input`, `state`, `price`/`onQuote`,
|
|
@@ -6230,7 +5325,7 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6230
5325
|
tags?: string[];
|
|
6231
5326
|
/**
|
|
6232
5327
|
* The currency this DVM prices in — and therefore the one its credit ledger
|
|
6233
|
-
* is denominated in
|
|
5328
|
+
* is denominated in. ISO 4217 lowercase; default `"usd"`.
|
|
6234
5329
|
*
|
|
6235
5330
|
* The single authoritative answer to "what denomination does this DVM deal
|
|
6236
5331
|
* in", read by the funding menu, `POST /v1/credit`, and the quote balance
|
|
@@ -6241,11 +5336,11 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6241
5336
|
* Two consequences, both enforced rather than documented:
|
|
6242
5337
|
*
|
|
6243
5338
|
* - **`onQuote` must return it.** A quote denominated in anything else is a
|
|
6244
|
-
*
|
|
6245
|
-
*
|
|
5339
|
+
* builder config error, refused as `quote_currency_mismatch` — never
|
|
5340
|
+
* coerced, never converted.
|
|
6246
5341
|
* - **Static `price` literals are USD-only** (`"$0.05"` — that is the whole
|
|
6247
|
-
*
|
|
6248
|
-
*
|
|
5342
|
+
* grammar), so declaring a non-USD currency alongside one is a
|
|
5343
|
+
* `configureDVM` error. Price a non-USD DVM dynamically with `onQuote`.
|
|
6249
5344
|
*
|
|
6250
5345
|
* The rails are unaffected: callers still pay sats, and the SDK converts at
|
|
6251
5346
|
* request time through whichever rate the fx fetcher carries for this
|
|
@@ -6256,7 +5351,7 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6256
5351
|
price?: PriceValue;
|
|
6257
5352
|
/** Zod schema for structured job input. Flat shape only. */
|
|
6258
5353
|
input?: InputSchema;
|
|
6259
|
-
/** Example input payload surfaced on `/v1/info
|
|
5354
|
+
/** Example input payload surfaced on `/v1/info`. Flat shape only — see `CapabilityConfig.example`. */
|
|
6260
5355
|
example?: ExampleType<InputSchema>;
|
|
6261
5356
|
/** Per-job state shape and defaults. Flat shape only. */
|
|
6262
5357
|
state?: State;
|
|
@@ -6265,7 +5360,7 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6265
5360
|
/**
|
|
6266
5361
|
* Seconds a job may sit in `processing`/`working` with no SDK liveness
|
|
6267
5362
|
* heartbeat before the stale-job watchdog declares its worker dead and marks
|
|
6268
|
-
* it `failed
|
|
5363
|
+
* it `failed`. The SDK heartbeats locally-active jobs every ~30s,
|
|
6269
5364
|
* so this bounds *crashed/OOM-killed-worker* detection — not legitimate
|
|
6270
5365
|
* handler runtime, which the heartbeat keeps fresh regardless of how long it
|
|
6271
5366
|
* runs. Distinct from `idleTimeout`, which governs the `awaiting-input`
|
|
@@ -6281,7 +5376,7 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6281
5376
|
*/
|
|
6282
5377
|
jobRetentionDays?: number;
|
|
6283
5378
|
/**
|
|
6284
|
-
* Descriptor-level auth scheme
|
|
5379
|
+
* Descriptor-level auth scheme. Fires on `/v1/job` and
|
|
6285
5380
|
* `/v1/quote` before payment verification and handler dispatch — unsigned
|
|
6286
5381
|
* or malformed envelopes reject with HTTP 401, no payment is attempted.
|
|
6287
5382
|
*
|
|
@@ -6319,14 +5414,14 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
|
|
|
6319
5414
|
/** MPP multi-rail payment handle (provider-side, built via `createMppFromOpts`). */
|
|
6320
5415
|
mpp?: MppxServer;
|
|
6321
5416
|
/**
|
|
6322
|
-
* Prepaid-credit sizing
|
|
5417
|
+
* Prepaid-credit sizing. Declaring the block opts this DVM into
|
|
6323
5418
|
* the funding menu on `/v1/quote` / the 402 and into `POST /v1/credit`;
|
|
6324
5419
|
* omitting it leaves callers on per-call payment. See {@link CreditConfig}.
|
|
6325
5420
|
*/
|
|
6326
5421
|
credit?: CreditConfig;
|
|
6327
5422
|
/**
|
|
6328
5423
|
* Register DVM-scoped non-protocol routes on the descriptor's Hono sub-app
|
|
6329
|
-
*
|
|
5424
|
+
* Invoked once per `host.mount(dvm)` against the sub-app that
|
|
6330
5425
|
* receives `/v1/info`, `/v1/job`, etc. — any prefix passed to `host.mount`
|
|
6331
5426
|
* applies automatically, so a route registered as `/feeds/:owner/:slug` on
|
|
6332
5427
|
* a DVM mounted under `/cast` becomes `/cast/feeds/:owner/:slug` externally.
|
|
@@ -6349,14 +5444,12 @@ interface DVMRouteContext {
|
|
|
6349
5444
|
}
|
|
6350
5445
|
/** Immutable descriptor returned by configureDVM(). */
|
|
6351
5446
|
interface DVMDescriptor<State = Record<string, unknown>, InputSchema extends ZodLike | undefined = undefined> {
|
|
6352
|
-
/** Display name for the DVM. */
|
|
6353
5447
|
readonly name: string;
|
|
6354
5448
|
/** Human-readable description (empty string if not provided). */
|
|
6355
5449
|
readonly description: string;
|
|
6356
|
-
/** Freeform tags this DVM handles. */
|
|
6357
5450
|
readonly tags: string[];
|
|
6358
5451
|
/**
|
|
6359
|
-
* Resolved pricing currency
|
|
5452
|
+
* Resolved pricing currency — `config.currency` after validation,
|
|
6360
5453
|
* or `"usd"` when the builder declared none. Always present, so no consumer
|
|
6361
5454
|
* re-derives the default, and always the exact code the ledger compares
|
|
6362
5455
|
* against (`"EUR"` is a `configureDVM` error, not a case to normalise: the
|
|
@@ -6385,14 +5478,14 @@ interface DVMDescriptor<State = Record<string, unknown>, InputSchema extends Zod
|
|
|
6385
5478
|
readonly state: State;
|
|
6386
5479
|
/** Seconds before an idle job is auto-cancelled. */
|
|
6387
5480
|
readonly idleTimeout: number;
|
|
6388
|
-
/** Seconds before a worker-stranded `processing`/`working` job is marked `failed
|
|
5481
|
+
/** Seconds before a worker-stranded `processing`/`working` job is marked `failed`. Undefined → SDK default (5 min). */
|
|
6389
5482
|
readonly processingWatchdog?: number;
|
|
6390
5483
|
/** Days terminal job content is retained; `0` disables automatic redaction. */
|
|
6391
5484
|
readonly jobRetentionDays: number;
|
|
6392
5485
|
/** Reference to the original DVMConfig (handler functions, quote config, etc.). */
|
|
6393
5486
|
readonly config: DVMConfig<State, InputSchema>;
|
|
6394
5487
|
/**
|
|
6395
|
-
* Desugared per-capability descriptors
|
|
5488
|
+
* Desugared per-capability descriptors. The SDK runtime always
|
|
6396
5489
|
* operates on this map: single-capability DVMs have one entry keyed by
|
|
6397
5490
|
* `config.capability`; multi-capability DVMs have N entries with the
|
|
6398
5491
|
* names declared in `config.capabilities`.
|
|
@@ -6410,24 +5503,22 @@ interface DVMDescriptor<State = Record<string, unknown>, InputSchema extends Zod
|
|
|
6410
5503
|
/** MPP multi-rail payment handle. */
|
|
6411
5504
|
readonly mpp?: MppxServer;
|
|
6412
5505
|
/**
|
|
6413
|
-
* Prepaid-credit sizing
|
|
5506
|
+
* Prepaid-credit sizing, with every optional field resolved to
|
|
6414
5507
|
* its platform default. Present iff `configureDVM` was called with a
|
|
6415
5508
|
* `credit` block — the presence check IS the opt-in gate.
|
|
6416
5509
|
*/
|
|
6417
5510
|
readonly credit?: ResolvedCreditConfig;
|
|
6418
5511
|
/**
|
|
6419
|
-
* Descriptor-level auth scheme
|
|
5512
|
+
* Descriptor-level auth scheme. Set when `configureDVM` was
|
|
6420
5513
|
* called with `auth: secp256k1Auth(...)` — the SDK fires this on
|
|
6421
5514
|
* `/v1/job` and `/v1/quote` before payment verification.
|
|
6422
5515
|
*/
|
|
6423
5516
|
readonly auth?: DVMAuthScheme;
|
|
6424
5517
|
}
|
|
6425
5518
|
|
|
6426
|
-
/** Serializable record of a completed step. */
|
|
6427
5519
|
interface StepRecord {
|
|
6428
5520
|
/** Step identifier (must be unique within a job). */
|
|
6429
5521
|
id: string;
|
|
6430
|
-
/** Cached return value of the step function. */
|
|
6431
5522
|
value: unknown;
|
|
6432
5523
|
/** Cost declarations made while the step body ran, replayed on cache hits. */
|
|
6433
5524
|
costs?: JobCost[];
|
|
@@ -6442,18 +5533,12 @@ interface StepRecord {
|
|
|
6442
5533
|
*/
|
|
6443
5534
|
declare class StepCache {
|
|
6444
5535
|
private cache;
|
|
6445
|
-
/** Check if a step result is cached. */
|
|
6446
5536
|
has(id: string): boolean;
|
|
6447
|
-
/** Get a cached step result. Throws if not present. */
|
|
6448
5537
|
get(id: string): unknown;
|
|
6449
|
-
/** Get a cached step record, including replay metadata. Throws if not present. */
|
|
6450
5538
|
getRecord(id: string): StepRecord;
|
|
6451
|
-
/** Cache a step result. */
|
|
6452
5539
|
set(id: string, value: unknown, costs?: readonly JobCost[]): void;
|
|
6453
|
-
/** Export all cached steps for persistence. */
|
|
6454
5540
|
serialize(): StepRecord[];
|
|
6455
|
-
/** Restore a StepCache from persisted records. */
|
|
6456
5541
|
static deserialize(records: StepRecord[]): StepCache;
|
|
6457
5542
|
}
|
|
6458
5543
|
|
|
6459
|
-
export { type X402Version as $, type ApprovalContent as A, SignedRequestError as B, type Currency as C, type DVMConfig as D, type SignedRequestFailure as E, type SignedRequestReplayStore as F, type SignedRequestSignOpts as G, type SignedRequestStatementHeader as H, type IncomingMessage as I, type JobCost as J, type KVStore as K, type Logger as L, type SignedRequestVerifier as M, createSignedRequestVerifier as N, isZodSchema as O, type PaymentContent as P, type QuoteConfig as Q, type ResolvedCreditConfig as R, type SDKJobContext as S, signedRequestStatementHeader as T, UnsupportedCurrencyError as U, validateCurrency as V, type JsonValue as W, type FundingReceipt as X, type JobReceipt as Y, type ZodLike as Z, type PaymentRequired as _, type DVMDescriptor as a, isDrainReceipt as a$, type MppxCredential as a0, type Message as a1, type MessageType as a2, type MppxChallenge as a3, X402_BATCH_SETTLEMENT_SCHEME as a4, X402_EXACT_SCHEME as a5, type ReceiptCredit as a6, type X402Wallet as a7, type ResourceInfo as a8, type BuildPaymentRequirementsOpts as a9, type UnsignedFundingReceipt as aA, type UnsignedJobReceipt as aB, type VerifyResponse as aC, type WorkingContent as aD, type X402Config as aE, type X402ResponseBody as aF, type X402SelfRelayRpcFailureReason as aG, X402_DEFAULT_NETWORK as aH, X402_V1_VERSION as aI, X402_VERSION as aJ, buildPaymentRequiredV2 as aK, buildPaymentRequirements as aL, caip2ToX402Network as aM, canonicalRequestPath as aN, canonicaliseForSigning as aO, canonicalize as aP, chainIdFromCaip2 as aQ, computeResultHash as aR, decodePayment as aS, decodePaymentRequiredHeader as aT, encodePayment as aU, encodePaymentRequiredHeader as aV, encodeSettleResponseHeader as aW, exactEvmAuthorization as aX, isArtifactMessage as aY, isCancelMessage as aZ, isCompleteMessage as a_, type CapabilityDescriptor as aa, type CashuMode as ab, type CompleteContent as ac, type DrainReceipt as ad, type DrainReceiptEvent as ae, type ExactEvmPayload as af, type ExactEvmPayloadAuthorization as ag, type FundingMethod as ah, type MessageFrom as ai, type PaymentPayload as aj, type PaymentPayloadV1 as ak, type PaymentPayloadV2 as al, type PaymentRequestContent as am, type PaymentRequiredV2 as an, type PaymentRequirements as ao, type PaymentRequirementsV1 as ap, type PaymentRequirementsV2 as aq, type PromptContent as ar, RAIL_REFUNDABLE as as, type ReceiptOutcome as at, type ReceiptPayment as au, type SettleResponse as av, StepCache as aw, type StepRecord as ax, type TextContent as ay, type UnsignedDrainReceipt as az, type ArtifactContent as b, type StalePendingDrawCursor as b$, isFundingReceipt as b0, isPaymentRequestMessage as b1, isPromptMessage as b2, isSignedJobReceipt as b3, isTextMessage as b4, isWorkingMessage as b5, paymentRequiredV2FromV1 as b6, signDrainReceipt as b7, signFundingReceipt as b8, signReceipt as b9, _testing as bA, createDefaultReplayStore as bB, depleteLots as bC, fifoOrder as bD, inKindDrawMsats as bE, isInKindDepletion as bF, isNonChannelBitcoinRail as bG, lotOwedSats as bH, netOwedSats as bI, wrapMppx as bJ, type CreditDrainEnqueue as bK, type DVMAuthScheme as bL, type CreditLedgerLike as bM, type X402RefundSettlementGate as bN, type CreditSnapshot as bO, type DrawResult as bP, type X402SettlementStatus as bQ, type X402UnresolvedRefund as bR, type GrownDrawResult as bS, type DrawResolution as bT, type FundingRecord as bU, type CreditInvoiceRecord as bV, type InvoiceSettlement as bW, type BlockedInvoiceCursor as bX, type InvoiceReconciliation as bY, type InvoiceWriteOff as bZ, type DrawRecord as b_, usdcContractByCaip2 as ba, usdcContractFor as bb, usdcDomainNameFor as bc, usdcDomainVersionFor as bd, verifyDrainReceipt as be, verifyFundingReceipt as bf, verifyReceipt as bg, x402NetworkToCaip2 as bh, type X402ExactVersionSupport as bi, type X402Receipt as bj, type TransactionalPayoutHook as bk, type CashuMeltCompleted as bl, type CreditDepositPayload as bm, type CreditDrainPayload as bn, type FundingLot as bo, type LotDebit as bp, type LotDepletion as bq, type MppxServer as br, NON_CHANNEL_BITCOIN_RAILS as bs, type NonChannelBitcoinRail as bt, PayoutReporter as bu, PostgresTempoSessionStore as bv, RevenueReporter as bw, SIGNED_ENVELOPE_FIELDS as bx, SIGNED_ENVELOPE_TYPES as by, type X402TrackedChannel as bz, type CancelContent as c, type TempoCreditLossEvidence as c0, type CreditLedgerQuerier as c1, type TempoCreditLoss as c2, type X402CreditLossEvidence as c3, type X402CreditLoss as c4, type DrainMethod as c5, type DrainRequestResult as c6, type BitcoinDepositLiability as c7, type CreditDrainRecord as c8, type ChannelDrainCursor as c9, type
|
|
5544
|
+
export { type X402Version as $, type ApprovalContent as A, SignedRequestError as B, type Currency as C, type DVMConfig as D, type SignedRequestFailure as E, type SignedRequestReplayStore as F, type SignedRequestSignOpts as G, type SignedRequestStatementHeader as H, type IncomingMessage as I, type JobCost as J, type KVStore as K, type Logger as L, type SignedRequestVerifier as M, createSignedRequestVerifier as N, isZodSchema as O, type PaymentContent as P, type QuoteConfig as Q, type ResolvedCreditConfig as R, type SDKJobContext as S, signedRequestStatementHeader as T, UnsupportedCurrencyError as U, validateCurrency as V, type JsonValue as W, type FundingReceipt as X, type JobReceipt as Y, type ZodLike as Z, type PaymentRequired as _, type DVMDescriptor as a, isDrainReceipt as a$, type MppxCredential as a0, type Message as a1, type MessageType as a2, type MppxChallenge as a3, X402_BATCH_SETTLEMENT_SCHEME as a4, X402_EXACT_SCHEME as a5, type ReceiptCredit as a6, type X402Wallet as a7, type ResourceInfo as a8, type BuildPaymentRequirementsOpts as a9, type UnsignedFundingReceipt as aA, type UnsignedJobReceipt as aB, type VerifyResponse as aC, type WorkingContent as aD, type X402Config as aE, type X402ResponseBody as aF, type X402SelfRelayRpcFailureReason as aG, X402_DEFAULT_NETWORK as aH, X402_V1_VERSION as aI, X402_VERSION as aJ, buildPaymentRequiredV2 as aK, buildPaymentRequirements as aL, caip2ToX402Network as aM, canonicalRequestPath as aN, canonicaliseForSigning as aO, canonicalize as aP, chainIdFromCaip2 as aQ, computeResultHash as aR, decodePayment as aS, decodePaymentRequiredHeader as aT, encodePayment as aU, encodePaymentRequiredHeader as aV, encodeSettleResponseHeader as aW, exactEvmAuthorization as aX, isArtifactMessage as aY, isCancelMessage as aZ, isCompleteMessage as a_, type CapabilityDescriptor as aa, type CashuMode as ab, type CompleteContent as ac, type DrainReceipt as ad, type DrainReceiptEvent as ae, type ExactEvmPayload as af, type ExactEvmPayloadAuthorization as ag, type FundingMethod as ah, type MessageFrom as ai, type PaymentPayload as aj, type PaymentPayloadV1 as ak, type PaymentPayloadV2 as al, type PaymentRequestContent as am, type PaymentRequiredV2 as an, type PaymentRequirements as ao, type PaymentRequirementsV1 as ap, type PaymentRequirementsV2 as aq, type PromptContent as ar, RAIL_REFUNDABLE as as, type ReceiptOutcome as at, type ReceiptPayment as au, type SettleResponse as av, StepCache as aw, type StepRecord as ax, type TextContent as ay, type UnsignedDrainReceipt as az, type ArtifactContent as b, type StalePendingDrawCursor as b$, isFundingReceipt as b0, isPaymentRequestMessage as b1, isPromptMessage as b2, isSignedJobReceipt as b3, isTextMessage as b4, isWorkingMessage as b5, paymentRequiredV2FromV1 as b6, signDrainReceipt as b7, signFundingReceipt as b8, signReceipt as b9, _testing as bA, createDefaultReplayStore as bB, depleteLots as bC, fifoOrder as bD, inKindDrawMsats as bE, isInKindDepletion as bF, isNonChannelBitcoinRail as bG, lotOwedSats as bH, netOwedSats as bI, wrapMppx as bJ, type CreditDrainEnqueue as bK, type DVMAuthScheme as bL, type CreditLedgerLike as bM, type X402RefundSettlementGate as bN, type CreditSnapshot as bO, type DrawResult as bP, type X402SettlementStatus as bQ, type X402UnresolvedRefund as bR, type GrownDrawResult as bS, type DrawResolution as bT, type FundingRecord as bU, type CreditInvoiceRecord as bV, type InvoiceSettlement as bW, type BlockedInvoiceCursor as bX, type InvoiceReconciliation as bY, type InvoiceWriteOff as bZ, type DrawRecord as b_, usdcContractByCaip2 as ba, usdcContractFor as bb, usdcDomainNameFor as bc, usdcDomainVersionFor as bd, verifyDrainReceipt as be, verifyFundingReceipt as bf, verifyReceipt as bg, x402NetworkToCaip2 as bh, type X402ExactVersionSupport as bi, type X402Receipt as bj, type TransactionalPayoutHook as bk, type CashuMeltCompleted as bl, type CreditDepositPayload as bm, type CreditDrainPayload as bn, type FundingLot as bo, type LotDebit as bp, type LotDepletion as bq, type MppxServer as br, NON_CHANNEL_BITCOIN_RAILS as bs, type NonChannelBitcoinRail as bt, PayoutReporter as bu, PostgresTempoSessionStore as bv, RevenueReporter as bw, SIGNED_ENVELOPE_FIELDS as bx, SIGNED_ENVELOPE_TYPES as by, type X402TrackedChannel as bz, type CancelContent as c, type CreditDrawReleaseEnqueue as c$, type TempoCreditLossEvidence as c0, type CreditLedgerQuerier as c1, type TempoCreditLoss as c2, type X402CreditLossEvidence as c3, type X402CreditLoss as c4, type DrainMethod as c5, type DrainRequestResult as c6, type BitcoinDepositLiability as c7, type CreditDrainRecord as c8, type ChannelDrainCursor as c9, type ReplayStoreBackend as cA, type RevenueSkippedNoRailPayload as cB, type RevenueSkippedNoRailReason as cC, type Secp256k1AuthOpts as cD, type X402ChannelStorageOpts as cE, type X402RelayLockHolder as cF, type X402RelaySubmissionLock as cG, X402RelaySubmissionLockError as cH, allocateDrawValue as cI, clientCompatibilityAttributes as cJ, clientCompatibilityMiddleware as cK, clientUpgradeRequired as cL, parseClientCapabilities as cM, parseClientCompatibility as cN, parseDvmClient as cO, parseProtocolVersion as cP, requireClientCompatibility as cQ, secp256k1Auth as cR, signedRequestInput as cS, type X402SettlementIntent as cT, type X402SettlementCursor as cU, type X402SettlementWriteOff as cV, type X402FacilitatorAuth as cW, type X402BatchSettlementConfig as cX, type X402PayoutObserver as cY, type CreditDepositEnqueue as cZ, type X402SettlementReconciliationReason as c_, type DrainWriteOff as ca, type DrainReleaseResult as cb, type DrainFulfilment as cc, type DrainTransitionResult as cd, CLIENT_COMPATIBILITY_HEADERS as ce, type ClientCompatibility as cf, type ClientCompatibilityEnv as cg, type ClientCompatibilityGate as ch, type ClientCompatibilityRequirement as ci, type ClientSemVer as cj, type CreditFundingBasis as ck, type CreditInvoiceStatus as cl, CreditLedger as cm, CreditLedgerError as cn, type CreditLedgerErrorCode as co, type CreditLedgerErrorDetails as cp, type CreditLedgerPool as cq, type CreditStatus as cr, DRAIN_DELIVERY_RESERVE_SATS as cs, DVM_PROTOCOL_VERSION as ct, type DrainConflictReason as cu, type DrawRailValue as cv, type DrawStatus as cw, type JobCostReportPayload as cx, type JobTerminalReportPayload as cy, PostgresX402ChannelStorage as cz, type CanonicalEnvelope as d, type CreateSignedRequestVerifierOpts as e, type CreditConfig as f, type CreditView as g, DEFAULT_CREDIT_MAX as h, DEFAULT_CREDIT_MIN as i, DEFAULT_CREDIT_TTL_SECONDS as j, DEFAULT_JOB_RETENTION_DAYS as k, type DVMRouteContext as l, type InputType as m, InvalidCurrencyError as n, type PaymentMethod as o, type PriceValue as p, type ProgressContent as q, type PromptOpts as r, type QuoteContext as s, type QuoteResult as t, type ResponseContent as u, type SDKPaymentRequestOpts as v, SIGNED_REQUEST_AUTH_ID as w, SIGNED_REQUEST_STATEMENT_VERSION as x, type SignedRequestAudience as y, type SignedRequestDomain as z };
|