@dvmkit/sdk 0.2.0-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 +2 -0
- package/dist/{chunk-V7EVFLAK.js → chunk-2D75MNNG.js} +154 -523
- package/dist/{chunk-BIFLRKMO.js → chunk-2KGG5ORW.js} +1 -1
- package/dist/{chunk-5URG56JJ.js → chunk-A4CM2BD3.js} +18 -2
- package/dist/{chunk-FT6HTUM4.js → chunk-AJNFXR7J.js} +1 -1
- package/dist/{chunk-S3XAHZQY.js → chunk-COYKJRMM.js} +24 -3
- package/dist/{chunk-UP2F5RRT.js → chunk-D7YQYLLM.js} +1 -4
- package/dist/{chunk-OMIQMMME.js → chunk-FPOLJRBF.js} +3 -3
- package/dist/{chunk-QP53RWAD.js → chunk-JAHRF5X3.js} +1 -1
- package/dist/{chunk-PCUQZDZA.js → chunk-JNKQZVZX.js} +95 -321
- package/dist/{chunk-JDT5LCJC.js → chunk-MD3H3BF7.js} +1 -1
- package/dist/{chunk-KSQEFVFJ.js → chunk-MMCE7XSR.js} +1 -1
- package/dist/{chunk-MRAGS5VP.js → chunk-N5B66UOS.js} +4 -16
- package/dist/{chunk-2ATUAUAO.js → chunk-O6D72KYA.js} +2 -2
- package/dist/{chunk-KXCKFIJI.js → chunk-QX52F6IB.js} +1 -1
- package/dist/{chunk-PHHAYRQV.js → chunk-U5VNEALI.js} +23 -10
- package/dist/{chunk-JLXYOV4Y.js → chunk-V5ZRATJH.js} +1 -1
- package/dist/{chunk-6GRIKOFB.js → chunk-VA7GT6C6.js} +5 -16
- package/dist/{chunk-O2X2CCKH.js → chunk-VIVXB4CD.js} +1 -1
- package/dist/{chunk-L67WTZX2.js → chunk-XMDQOXHO.js} +10 -33
- package/dist/{chunk-QT4ONTST.js → chunk-XRLKGCAW.js} +1 -1
- package/dist/{chunk-KMZXTBLA.js → chunk-XZ7LW4S5.js} +0 -1
- package/dist/{chunk-XQXJKJ3P.js → chunk-YO56VCTQ.js} +0 -4
- package/dist/{credit-ledger-5ZEJRI46.js → credit-ledger-MHA652TV.js} +2 -2
- package/dist/{credit-menu-D4Gcdgc4.d.ts → credit-menu-kDA9RVzD.d.ts} +135 -532
- package/dist/{fx-B0SLBe5x.d.ts → fx-DhyT1Cjh.d.ts} +1 -1
- package/dist/index.d.ts +5 -17
- package/dist/internal/caller.d.ts +109 -698
- package/dist/internal/caller.js +10 -12
- package/dist/internal/server.d.ts +17 -42
- package/dist/internal/server.js +15 -15
- package/dist/{job-store-B2uZvga4.d.ts → job-store-D3CcZp_g.d.ts} +6 -10
- package/dist/{lightning-backend-CQBnQgsT.d.ts → lightning-backend-BhsyrBLa.d.ts} +1 -15
- package/dist/{memory-credit-ledger-ZOH6C3N4.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-IOJBF7DB.js → mpp-setup-MPP3ST5X.js} +1 -1
- package/dist/{payout-reporter-5PIRYFVQ.js → payout-reporter-KEMUXEUO.js} +2 -2
- package/dist/{postgres-consumed-credential-store-ISRHBMOU.js → postgres-consumed-credential-store-6N2V5MJN.js} +1 -1
- package/dist/postgres-job-store-MZZZMS2B.js +7 -0
- package/dist/postgres-kv-store-DXGTKXLU.js +7 -0
- package/dist/{postgres-replay-store-IZFLTTAC.js → postgres-replay-store-ZF4JNZOW.js} +2 -2
- package/dist/{processed-payment-store-FIDI3RNH.js → processed-payment-store-GKMAWOM3.js} +2 -2
- package/dist/revenue-reporter-O75BGYFK.js +7 -0
- package/dist/server/index.d.ts +34 -140
- package/dist/server/index.js +52 -124
- package/dist/{step-cache-CXg7ziML.d.ts → step-cache-v2yhyhSZ.d.ts} +359 -1112
- package/dist/{tempo-charge-store-76TDAF34.js → tempo-charge-store-SDTUNTKA.js} +2 -2
- package/dist/{tempo-lifecycle-DXM7QXJQ.js → tempo-lifecycle-G5SB4HFC.js} +4 -4
- package/dist/{tempo-session-store-2JNOKJGX.js → tempo-session-store-WF3VZXNF.js} +2 -2
- package/dist/{tempo-wallet-O67H5M4N.js → tempo-wallet-ZFNUDFVO.js} +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/{usd-BNDg1715.d.ts → usd-Cu9G6xv2.d.ts} +1 -1
- package/dist/{x402-7S2EFINY.js → x402-CI6PSHNO.js} +4 -4
- package/package.json +2 -2
- package/dist/postgres-job-store-OGQ6IT4U.js +0 -7
- package/dist/postgres-kv-store-D5E2EZ24.js +0 -7
- package/dist/revenue-reporter-NNCNRY4C.js +0 -7
|
@@ -170,20 +170,7 @@ interface X402Receipt {
|
|
|
170
170
|
invalidReason?: string;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
/**
|
|
174
|
-
* dvmkit's x402 wire-format implementation — the v1/v2 `PaymentRequirements` /
|
|
175
|
-
* `PaymentPayload` envelope shapes, payment-header encode/decode, and the
|
|
176
|
-
* facilitator verify/settle calls that a deployed DVM's 402 response and
|
|
177
|
-
* payment parsing are built from. A wire-format change lands here.
|
|
178
|
-
*
|
|
179
|
-
* **On a change here, or an x402 reference-client upgrade, run the conformance
|
|
180
|
-
* check** — `public compatibility guide`. Our own SDK round-trips
|
|
181
|
-
* against itself (the vitest tiers, `money-loop:x402`), so only a client
|
|
182
|
-
* that isn't dvmkit can catch a *mutual* misreading of the spec. The runbook
|
|
183
|
-
* names the out-of-tree v1 and v2 clients plus the funded Base Sepolia key
|
|
184
|
-
* (`public compatibility guide`); the runbook carries a run log of
|
|
185
|
-
* what it last ran against.
|
|
186
|
-
*/
|
|
173
|
+
/** Wire compatibility requires independent v1 and v2 reference-client checks. */
|
|
187
174
|
|
|
188
175
|
/** Current x402 protocol version preferred by dvmkit clients and servers. */
|
|
189
176
|
declare const X402_VERSION = 2;
|
|
@@ -341,11 +328,8 @@ interface BuildPaymentRequirementsOpts {
|
|
|
341
328
|
version?: X402Version;
|
|
342
329
|
}
|
|
343
330
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* The resource string is bound into the EIP-3009 `extra` field on signing in
|
|
347
|
-
* the reference clients, so it must match exactly between the 402 response
|
|
348
|
-
* 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.
|
|
349
333
|
*/
|
|
350
334
|
declare function buildPaymentRequirements(opts: BuildPaymentRequirementsOpts & {
|
|
351
335
|
version: typeof X402_V1_VERSION;
|
|
@@ -361,13 +345,7 @@ declare function buildPaymentRequiredV2(opts: Omit<BuildPaymentRequirementsOpts,
|
|
|
361
345
|
}): PaymentRequiredV2;
|
|
362
346
|
/** Convert the server's legacy body requirements into the parallel v2 declaration. */
|
|
363
347
|
declare function paymentRequiredV2FromV1(requirements: PaymentRequirementsV1[], error?: string): PaymentRequiredV2;
|
|
364
|
-
/**
|
|
365
|
-
* Translate a CAIP-2 chain id (`eip155:8453`) to the x402-spec network slug
|
|
366
|
-
* (`base`). Operators configure DVMs in CAIP-2 because it's the lingua-franca
|
|
367
|
-
* across our other rails; the x402 facilitator API only accepts named slugs.
|
|
368
|
-
* Unknown CAIP-2 ids fall through to the original string so non-EVM rails
|
|
369
|
-
* (Solana mainnet, etc.) can also be plumbed once supported.
|
|
370
|
-
*/
|
|
348
|
+
/** Legacy v1 network slugs; unknown identifiers pass through unchanged. */
|
|
371
349
|
declare function caip2ToX402Network(network: string): string;
|
|
372
350
|
/** Translate a legacy x402 network slug back to its CAIP-2 chain id. */
|
|
373
351
|
declare function x402NetworkToCaip2(network: string): string;
|
|
@@ -375,12 +353,6 @@ declare function x402NetworkToCaip2(network: string): string;
|
|
|
375
353
|
declare function chainIdFromCaip2(network: string): number;
|
|
376
354
|
/** USDC contract address for a network slug (post-translation). */
|
|
377
355
|
declare function usdcContractFor(networkSlug: string): `0x${string}` | undefined;
|
|
378
|
-
/**
|
|
379
|
-
* USDC contract address for a CAIP-2 chain id (e.g. `eip155:8453`). Resolves
|
|
380
|
-
* via {@link caip2ToX402Network} so the one network table is the source of
|
|
381
|
-
* truth — keeps callers that hold the CAIP-2 form (CLI wallet) in lockstep
|
|
382
|
-
* with the SDK 402-builder which already speaks x402 slugs.
|
|
383
|
-
*/
|
|
384
356
|
declare function usdcContractByCaip2(caip2: string): `0x${string}` | undefined;
|
|
385
357
|
/** USDC EIP-712 domain `name` for a network slug. */
|
|
386
358
|
declare function usdcDomainNameFor(networkSlug: string): string | undefined;
|
|
@@ -575,7 +547,6 @@ interface ProgressContent {
|
|
|
575
547
|
/** Optional human-readable hint about what's happening right now. */
|
|
576
548
|
hint?: string;
|
|
577
549
|
}
|
|
578
|
-
/** A single message in a job conversation — discriminated union on `type`. */
|
|
579
550
|
type Message = (MessageBase & {
|
|
580
551
|
type: "text";
|
|
581
552
|
content: TextContent;
|
|
@@ -610,37 +581,30 @@ type Message = (MessageBase & {
|
|
|
610
581
|
type: "progress";
|
|
611
582
|
content: ProgressContent;
|
|
612
583
|
});
|
|
613
|
-
/** Type guard: message is a prompt from the provider. */
|
|
614
584
|
declare function isPromptMessage(msg: Message): msg is MessageBase & {
|
|
615
585
|
type: "prompt";
|
|
616
586
|
content: PromptContent;
|
|
617
587
|
};
|
|
618
|
-
/** Type guard: message contains an artifact. */
|
|
619
588
|
declare function isArtifactMessage(msg: Message): msg is MessageBase & {
|
|
620
589
|
type: "artifact";
|
|
621
590
|
content: ArtifactContent;
|
|
622
591
|
};
|
|
623
|
-
/** Type guard: message is a payment request. */
|
|
624
592
|
declare function isPaymentRequestMessage(msg: Message): msg is MessageBase & {
|
|
625
593
|
type: "payment-request";
|
|
626
594
|
content: PaymentRequestContent;
|
|
627
595
|
};
|
|
628
|
-
/** Type guard: message is a text message. */
|
|
629
596
|
declare function isTextMessage(msg: Message): msg is MessageBase & {
|
|
630
597
|
type: "text";
|
|
631
598
|
content: TextContent;
|
|
632
599
|
};
|
|
633
|
-
/** Type guard: message signals completion. */
|
|
634
600
|
declare function isCompleteMessage(msg: Message): msg is MessageBase & {
|
|
635
601
|
type: "complete";
|
|
636
602
|
content: CompleteContent;
|
|
637
603
|
};
|
|
638
|
-
/** Type guard: message is a cancellation. */
|
|
639
604
|
declare function isCancelMessage(msg: Message): msg is MessageBase & {
|
|
640
605
|
type: "cancel";
|
|
641
606
|
content: CancelContent;
|
|
642
607
|
};
|
|
643
|
-
/** Type guard: message is a working signal. */
|
|
644
608
|
declare function isWorkingMessage(msg: Message): msg is MessageBase & {
|
|
645
609
|
type: "working";
|
|
646
610
|
content: WorkingContent;
|
|
@@ -686,7 +650,6 @@ declare const CLIENT_COMPATIBILITY_HEADERS: {
|
|
|
686
650
|
readonly protocolVersion: "DVM-Protocol-Version";
|
|
687
651
|
readonly capabilities: "DVM-Client-Capabilities";
|
|
688
652
|
};
|
|
689
|
-
/** A parsed SemVer value carried by `DVM-Client`. */
|
|
690
653
|
interface ClientSemVer {
|
|
691
654
|
readonly version: string;
|
|
692
655
|
readonly major: number;
|
|
@@ -694,7 +657,6 @@ interface ClientSemVer {
|
|
|
694
657
|
readonly patch: number;
|
|
695
658
|
readonly prerelease: readonly (string | number)[];
|
|
696
659
|
}
|
|
697
|
-
/** Validated compatibility metadata from one HTTP request. */
|
|
698
660
|
interface ClientCompatibility {
|
|
699
661
|
readonly clientVersion?: ClientSemVer;
|
|
700
662
|
readonly protocolVersion?: number;
|
|
@@ -710,16 +672,13 @@ interface ClientCompatibility {
|
|
|
710
672
|
readonly capabilities: boolean;
|
|
711
673
|
};
|
|
712
674
|
}
|
|
713
|
-
/** Compatibility requirement attached to one endpoint. */
|
|
714
675
|
interface ClientCompatibilityRequirement {
|
|
715
676
|
/** Additive capability tokens the caller must advertise. */
|
|
716
677
|
readonly requiredCapabilities?: readonly string[];
|
|
717
678
|
/** Lowest `dvm` SemVer accepted by this endpoint. */
|
|
718
679
|
readonly minimumClientVersion?: string;
|
|
719
680
|
}
|
|
720
|
-
/** Factory for compatibility middleware attached to one custom route. */
|
|
721
681
|
type ClientCompatibilityGate = (requirement: ClientCompatibilityRequirement) => MiddlewareHandler<ClientCompatibilityEnv>;
|
|
722
|
-
/** Hono variables installed by the SDK compatibility middleware. */
|
|
723
682
|
interface ClientCompatibilityEnv {
|
|
724
683
|
Variables: {
|
|
725
684
|
clientCompatibility?: ClientCompatibility;
|
|
@@ -737,35 +696,20 @@ declare function parseClientCapabilities(value: string): readonly string[] | und
|
|
|
737
696
|
declare function clientCompatibilityAttributes(compatibility: ClientCompatibility): Record<string, unknown>;
|
|
738
697
|
/** Parse request metadata and advertise the server protocol version on every response. */
|
|
739
698
|
declare function clientCompatibilityMiddleware(): MiddlewareHandler<ClientCompatibilityEnv>;
|
|
740
|
-
/** Build pre-handler enforcement middleware for one endpoint declaration. */
|
|
741
699
|
declare const requireClientCompatibility: ClientCompatibilityGate;
|
|
742
|
-
/** Render the normal structured HTTP 426 compatibility error. */
|
|
743
700
|
declare function clientUpgradeRequired(c: Context, compatibility: ClientCompatibility, requirement: ClientCompatibilityRequirement): Response;
|
|
744
701
|
|
|
745
|
-
/**
|
|
746
|
-
* Algorithm: recursively sort object keys lexicographically (UTF-16 code-unit
|
|
747
|
-
* order — JavaScript's default), then JSON.stringify with no whitespace.
|
|
748
|
-
* Arrays preserve order.
|
|
749
|
-
*/
|
|
750
|
-
/** Any JSON-serialisable value accepted by `canonicalize`. */
|
|
751
702
|
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
752
703
|
[key: string]: JsonValue;
|
|
753
704
|
};
|
|
754
705
|
/**
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
* `JSON.stringify`), so callers don't need to explicitly delete optional fields
|
|
759
|
-
* that weren't set. Throws `TypeError` for unsupported scalar types (functions,
|
|
760
|
-
* symbols, non-finite numbers). The caller is responsible for stripping fields
|
|
761
|
-
* 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.
|
|
762
709
|
*/
|
|
763
710
|
declare function canonicalize(value: JsonValue): string;
|
|
764
711
|
/**
|
|
765
|
-
*
|
|
766
|
-
* signature over. Both the cast client signer (`signAddEpisode`) and the
|
|
767
|
-
* server verifier (`verifyCanonicalSignedRequest`) call this — sharing the function
|
|
768
|
-
* guarantees byte-identical input on both sides.
|
|
712
|
+
* UTF-8 encoding of the canonical JSON form.
|
|
769
713
|
*/
|
|
770
714
|
declare function canonicaliseForSigning(value: JsonValue): Uint8Array;
|
|
771
715
|
|
|
@@ -895,9 +839,8 @@ interface DrainReceipt {
|
|
|
895
839
|
/** Amount reclaimed, micro-units of the credit's currency. */
|
|
896
840
|
amount_micro: number;
|
|
897
841
|
/**
|
|
898
|
-
* Credit balance
|
|
899
|
-
* pending job holds at request time
|
|
900
|
-
* 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.
|
|
901
844
|
*/
|
|
902
845
|
balance_after_micro: number;
|
|
903
846
|
/** Per-credit monotonic sequence the drain consumed, shared with draws. */
|
|
@@ -965,13 +908,6 @@ type UnsignedFundingReceipt = Omit<FundingReceipt, "signature">;
|
|
|
965
908
|
* issuer does the same (`resultHashFor` in `sdk/server/receipt-issuer.ts`).
|
|
966
909
|
*/
|
|
967
910
|
declare function computeResultHash(summary: string | undefined, artifactContents: JsonValue[]): string;
|
|
968
|
-
/**
|
|
969
|
-
* Sign an unsigned receipt with the DVM's receipt secret, returning the
|
|
970
|
-
* complete receipt. Signs `canonicaliseForSigning(receipt)` — the same
|
|
971
|
-
* sorted-key canonical JSON the caller-auth envelope and the builder
|
|
972
|
-
* attestation use, so {@link verifyReceipt} is symmetric with
|
|
973
|
-
* `verifyCanonicalSignedRequest`. There is no second canonicalisation.
|
|
974
|
-
*/
|
|
975
911
|
declare function signReceipt(unsigned: UnsignedJobReceipt, secretHex: string): JobReceipt;
|
|
976
912
|
/**
|
|
977
913
|
* Verify a receipt's self-consistency: the BIP-340 signature over the
|
|
@@ -1155,43 +1091,41 @@ declare function isInKindDepletion(depletion: LotDepletion, amountMicro: number)
|
|
|
1155
1091
|
/** The rails a payout lands on. Lightning never pays out: credits fund straight into the receive wallet. */
|
|
1156
1092
|
type PayoutRail = "cashu" | "x402" | "tempo";
|
|
1157
1093
|
/**
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
* 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.
|
|
1161
1096
|
*/
|
|
1162
1097
|
type PayoutKind = "melt" | "batch" | "close" | "settle";
|
|
1163
|
-
/** Which repair verb completed a stuck movement. Only `tempo_close_reconciled` is produced here today. */
|
|
1164
1098
|
type PayoutRepairKind = "x402_settlement_reconciled" | "lightning_invoice_reconciled" | "tempo_close_reconciled";
|
|
1165
1099
|
/**
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
* on `(dvmId, payoutId)`, so the durable retry loop can redeliver freely; a
|
|
1169
|
-
* `repaired` redelivery of an id that already landed upgrades that row rather
|
|
1170
|
-
* 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.
|
|
1171
1102
|
*/
|
|
1172
1103
|
interface PayoutReportPayload {
|
|
1173
|
-
/**
|
|
1104
|
+
/**
|
|
1105
|
+
* Platform record ID; must match the bearer token’s DVM.
|
|
1106
|
+
*/
|
|
1174
1107
|
dvmId: string;
|
|
1175
1108
|
/** Required. The DVM's own id for the movement — `cashu:melt:<quote>`, `x402:batch:<uuid>`, `tempo:<tx>`. */
|
|
1176
1109
|
payoutId: string;
|
|
1177
1110
|
rail: PayoutRail;
|
|
1178
1111
|
kind: PayoutKind;
|
|
1179
|
-
/**
|
|
1112
|
+
/**
|
|
1113
|
+
* Positive rail-native atomic units: sats or USDC micro.
|
|
1114
|
+
*/
|
|
1180
1115
|
nativeAmount: number;
|
|
1181
|
-
/** Required. `sats` for cashu, `usdc` for the stablecoin rails. */
|
|
1182
1116
|
nativeAsset: "sats" | "usdc";
|
|
1183
1117
|
/** Where it landed; `null` for a melt, whose Lightning destination the platform never records. */
|
|
1184
1118
|
recipient: string | null;
|
|
1185
|
-
/**
|
|
1119
|
+
/**
|
|
1120
|
+
* Descriptive mechanism references, never an arithmetic basis.
|
|
1121
|
+
*/
|
|
1186
1122
|
refs: Record<string, unknown>;
|
|
1187
1123
|
/** Required. When the money landed (epoch ms). */
|
|
1188
1124
|
landedAt: number;
|
|
1189
|
-
/** Set when a repair verb completed this movement. */
|
|
1190
1125
|
repaired?: boolean;
|
|
1191
1126
|
/** Required iff `repaired`. */
|
|
1192
1127
|
repairKind?: PayoutRepairKind;
|
|
1193
1128
|
}
|
|
1194
|
-
/** A stuck movement inside a pending snapshot. */
|
|
1195
1129
|
interface PayoutWedge {
|
|
1196
1130
|
id: string;
|
|
1197
1131
|
/**
|
|
@@ -1211,17 +1145,14 @@ interface PayoutWedge {
|
|
|
1211
1145
|
/** Rail-native amount held up, when known. */
|
|
1212
1146
|
native?: number;
|
|
1213
1147
|
}
|
|
1214
|
-
/** One pool inside a rail's pending figure — a mint, a batch, a channel. */
|
|
1215
1148
|
interface PayoutPool {
|
|
1216
1149
|
id: string;
|
|
1217
1150
|
native: number;
|
|
1218
1151
|
label?: string;
|
|
1219
1152
|
}
|
|
1220
1153
|
/**
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
* replaced on each report. Best-effort: a lost one is superseded by the next
|
|
1224
|
-
* 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.
|
|
1225
1156
|
*/
|
|
1226
1157
|
interface PayoutPendingPayload {
|
|
1227
1158
|
dvmId: string;
|
|
@@ -1229,14 +1160,12 @@ interface PayoutPendingPayload {
|
|
|
1229
1160
|
/** Everything on this rail still moving toward the builder, in atomic units. */
|
|
1230
1161
|
poolNative: number;
|
|
1231
1162
|
nativeAsset: "sats" | "usdc";
|
|
1232
|
-
/** How many pools the figure is made of. */
|
|
1233
1163
|
items: number;
|
|
1234
1164
|
pools: PayoutPool[];
|
|
1235
1165
|
wedged: PayoutWedge[];
|
|
1236
1166
|
/** This DVM's clock when the snapshot was taken (epoch ms). */
|
|
1237
1167
|
snapshotAt: number;
|
|
1238
1168
|
}
|
|
1239
|
-
/** The delivery seam — implemented by `RevenueReporter`, stubbed in tests. */
|
|
1240
1169
|
interface PayoutTransport {
|
|
1241
1170
|
/**
|
|
1242
1171
|
* Queue a payout through the caller's open transaction, so the row and the
|
|
@@ -1253,19 +1182,13 @@ interface PayoutTransport {
|
|
|
1253
1182
|
drainPending?(): Promise<void>;
|
|
1254
1183
|
}
|
|
1255
1184
|
/**
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
* back with it — the drain report's own discipline: a call the
|
|
1259
|
-
* caller has to repeat costs a round trip, where a landed movement with no
|
|
1260
|
-
* row understates paid-out forever. `committed` runs once that transaction
|
|
1261
|
-
* has committed, for the delivery and the snapshot that must not read
|
|
1262
|
-
* 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.
|
|
1263
1187
|
*/
|
|
1264
1188
|
interface TransactionalPayoutHook<E> {
|
|
1265
1189
|
enqueue(event: E, tx: RevenueReporterQuerier): Promise<void>;
|
|
1266
1190
|
committed(): void;
|
|
1267
1191
|
}
|
|
1268
|
-
/** What `/admin/cashu/mark-melted` knows once the rows are marked. */
|
|
1269
1192
|
interface CashuMeltCompleted {
|
|
1270
1193
|
rows: {
|
|
1271
1194
|
id: string;
|
|
@@ -1275,7 +1198,6 @@ interface CashuMeltCompleted {
|
|
|
1275
1198
|
meltQuoteId: string;
|
|
1276
1199
|
paymentPreimage: string;
|
|
1277
1200
|
}
|
|
1278
|
-
/** What mppx reports after a Tempo channel settlement or close confirmed on chain. */
|
|
1279
1201
|
interface TempoSessionSettled {
|
|
1280
1202
|
txHash: string;
|
|
1281
1203
|
channelId: string;
|
|
@@ -1285,27 +1207,23 @@ interface TempoSessionSettled {
|
|
|
1285
1207
|
/** Newly settled to the payee by this transaction, atomic units. */
|
|
1286
1208
|
delta: bigint;
|
|
1287
1209
|
}
|
|
1288
|
-
/** What `reconcile-tempo-drain` re-read from the close receipt it booked against. */
|
|
1289
1210
|
interface TempoCloseReconciled {
|
|
1290
1211
|
channelId: string;
|
|
1291
1212
|
txHash: string;
|
|
1292
1213
|
/** Captured by the payee — this DVM — per the receipt, atomic units as a decimal string. */
|
|
1293
1214
|
settledToPayee: string;
|
|
1294
1215
|
}
|
|
1295
|
-
/** The channel view the tracker sums claims over. */
|
|
1296
1216
|
interface X402TrackedChannel {
|
|
1297
1217
|
channelId: string;
|
|
1298
1218
|
chargedCumulativeAmount: string;
|
|
1299
1219
|
totalClaimed: string;
|
|
1300
1220
|
}
|
|
1301
|
-
/** A refund settlement wedged between chain and ledger, for the wedge list. */
|
|
1302
1221
|
interface X402WedgedRefund {
|
|
1303
1222
|
settlementId: string;
|
|
1304
1223
|
/** Epoch ms the settlement was first prepared. */
|
|
1305
1224
|
createdAt: number;
|
|
1306
1225
|
native?: number;
|
|
1307
1226
|
}
|
|
1308
|
-
/** What the batch-settlement server hands the tracker once it knows its scope. */
|
|
1309
1227
|
interface X402PayoutContext {
|
|
1310
1228
|
/** `${network}|${payTo}|${token}` — the scope the settle-pending marker is keyed on. */
|
|
1311
1229
|
scope: string;
|
|
@@ -1318,7 +1236,6 @@ interface X402PayoutContext {
|
|
|
1318
1236
|
settleIntervalMs: number;
|
|
1319
1237
|
listWedgedRefunds?: () => Promise<X402WedgedRefund[]>;
|
|
1320
1238
|
}
|
|
1321
|
-
/** The two manager verbs the tracker wraps. */
|
|
1322
1239
|
interface X402TrackedManager {
|
|
1323
1240
|
claim(...args: never[]): Promise<{
|
|
1324
1241
|
vouchers: number;
|
|
@@ -1341,9 +1258,7 @@ interface X402PayoutObserver {
|
|
|
1341
1258
|
/** A settle attempt that failed outside the tracked manager. One call per attempt a builder would count as one. */
|
|
1342
1259
|
recordSettleFailure(error: unknown): Promise<void>;
|
|
1343
1260
|
}
|
|
1344
|
-
/** The Tempo readers the host attaches after mounting. */
|
|
1345
1261
|
interface TempoPayoutReader {
|
|
1346
|
-
/** The recipient address every Tempo payout names. */
|
|
1347
1262
|
recipient?: string;
|
|
1348
1263
|
/** CAIP-2 network for the refs, when known. */
|
|
1349
1264
|
network?: string;
|
|
@@ -1363,11 +1278,8 @@ interface TempoPayoutReader {
|
|
|
1363
1278
|
}) => Promise<CreditDrainRecord[]>;
|
|
1364
1279
|
}
|
|
1365
1280
|
/**
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
* whichever machine won the tick, and the settle that finally moves the money
|
|
1369
|
-
* may run on another: an in-process figure would report a partial amount
|
|
1370
|
-
* 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.
|
|
1371
1283
|
*/
|
|
1372
1284
|
interface X402OpenBatch {
|
|
1373
1285
|
scope: string;
|
|
@@ -1381,55 +1293,39 @@ interface X402OpenBatch {
|
|
|
1381
1293
|
lastError: string | null;
|
|
1382
1294
|
nextRetryAt: number | null;
|
|
1383
1295
|
}
|
|
1384
|
-
/** Where the open batch lives — Postgres beside the settlement rows, or memory in dev. */
|
|
1385
1296
|
interface X402BatchStore {
|
|
1386
1297
|
get(scope: string): Promise<X402OpenBatch | undefined>;
|
|
1387
1298
|
put(batch: X402OpenBatch): Promise<void>;
|
|
1388
1299
|
delete(scope: string): Promise<void>;
|
|
1389
1300
|
/**
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1392
|
-
* a settle can never lose its payout, because a failed enqueue leaves the
|
|
1393
|
-
* batch open for the next settle to report in full. `false` when the batch
|
|
1394
|
-
* under `scope` is no longer `batchId` — a sibling settled it first — in
|
|
1395
|
-
* 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.
|
|
1396
1303
|
*/
|
|
1397
1304
|
settle(scope: string, batchId: string, enqueue: (tx?: RevenueReporterQuerier) => Promise<void>): Promise<boolean>;
|
|
1398
1305
|
}
|
|
1399
|
-
/** Construction options for {@link PayoutReporter}. */
|
|
1400
1306
|
interface PayoutReporterOpts {
|
|
1401
|
-
/** Platform DVM record ID, bound into every report. */
|
|
1402
1307
|
dvmId: string;
|
|
1403
1308
|
transport: PayoutTransport;
|
|
1404
1309
|
/**
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1407
|
-
* 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.
|
|
1408
1312
|
*/
|
|
1409
1313
|
db?: Pool;
|
|
1410
1314
|
/**
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
* 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.
|
|
1414
1317
|
*/
|
|
1415
1318
|
cashu?: boolean;
|
|
1416
1319
|
/** Override the batch store (tests); defaults to Postgres on `db`, memory without. */
|
|
1417
1320
|
batchStore?: X402BatchStore;
|
|
1418
1321
|
/** Snapshot cadence, ms. Default 5 minutes. */
|
|
1419
1322
|
snapshotIntervalMs?: number;
|
|
1420
|
-
/** Override for the current time (tests). */
|
|
1421
1323
|
now?: () => number;
|
|
1422
1324
|
}
|
|
1423
1325
|
/**
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
* Two entry points are transactional: `cashuMelted` and `tempoCloseReconciled`
|
|
1428
|
-
* queue their row inside the caller's transaction and throw when they cannot,
|
|
1429
|
-
* so the melt or the reconciled close rolls back with its missing report rather
|
|
1430
|
-
* than landing without one. The x402 settle and the pending snapshots are
|
|
1431
|
-
* fail-safe instead: a report that cannot be built or queued there is logged and
|
|
1432
|
-
* 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.
|
|
1433
1329
|
*/
|
|
1434
1330
|
declare class PayoutReporter implements PayoutTransport {
|
|
1435
1331
|
private readonly dvmId;
|
|
@@ -1446,7 +1342,6 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1446
1342
|
private timer;
|
|
1447
1343
|
private warmup;
|
|
1448
1344
|
constructor(opts: PayoutReporterOpts);
|
|
1449
|
-
/** Boot DDL for the durable batch store, when this reporter owns one. */
|
|
1450
1345
|
init(): Promise<void>;
|
|
1451
1346
|
/**
|
|
1452
1347
|
* Start the snapshot loop. Unref'd, so it never keeps a scale-to-zero
|
|
@@ -1460,23 +1355,13 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1460
1355
|
reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
|
|
1461
1356
|
/** Deliver what is queued now; a transport without the seam waits for its retry loop. */
|
|
1462
1357
|
flush(): Promise<void>;
|
|
1463
|
-
/** The hook `mark-melted` runs inside its transaction: the mark and its payout commit together. */
|
|
1464
1358
|
meltHook(): TransactionalPayoutHook<CashuMeltCompleted>;
|
|
1465
1359
|
/**
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1469
|
-
* belongs to one mint. The amount is the face value melted; the mint's fee
|
|
1470
|
-
* and the Lightning amount received are builder-machine facts this DVM does
|
|
1471
|
-
* not see.
|
|
1472
|
-
*
|
|
1473
|
-
* Queued through `tx` when the caller is inside the transaction that marks
|
|
1474
|
-
* the rows, and a failure is thrown rather than swallowed so that mark rolls
|
|
1475
|
-
* back with it — see {@link TransactionalPayoutHook}. Without `tx` the rows
|
|
1476
|
-
* 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.
|
|
1477
1363
|
*/
|
|
1478
1364
|
cashuMelted(event: CashuMeltCompleted, tx?: RevenueReporterQuerier): Promise<void>;
|
|
1479
|
-
/** The host attaches the session store and ledger readers once they exist. */
|
|
1480
1365
|
attachTempo(reader: TempoPayoutReader): void;
|
|
1481
1366
|
/**
|
|
1482
1367
|
* A Tempo channel settled or closed on chain. `delta` is what this
|
|
@@ -1484,29 +1369,18 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1484
1369
|
* (a close of a fully settled channel) is no payout.
|
|
1485
1370
|
*/
|
|
1486
1371
|
tempoSettled(event: TempoSessionSettled): Promise<void>;
|
|
1487
|
-
/** The hook `reconcile-tempo-drain` runs inside its transaction: the booking and its payout commit together. */
|
|
1488
1372
|
tempoRepairHook(): TransactionalPayoutHook<TempoCloseReconciled>;
|
|
1489
1373
|
/**
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
* figure is the receipt's payee total for the channel.
|
|
1494
|
-
*
|
|
1495
|
-
* Queued through `tx` when the caller is inside the transaction that books
|
|
1496
|
-
* the drain, and thrown rather than swallowed so that booking rolls back
|
|
1497
|
-
* 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.
|
|
1498
1377
|
*/
|
|
1499
1378
|
tempoCloseReconciled(event: TempoCloseReconciled, tx?: RevenueReporterQuerier): Promise<void>;
|
|
1500
|
-
/** The observer the batch-settlement server attaches to and routes its manager through. */
|
|
1501
1379
|
x402Observer(): X402PayoutObserver;
|
|
1502
1380
|
/**
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
* fleet lock the before/after read is consistent. A channel the claim
|
|
1507
|
-
* removed on its way through — a refund's claim-then-delete —
|
|
1508
|
-
* moved whatever it still owed before it went, and that value reaches
|
|
1509
|
-
* `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.
|
|
1510
1384
|
*/
|
|
1511
1385
|
private trackX402Manager;
|
|
1512
1386
|
private recordX402Claim;
|
|
@@ -1540,28 +1414,28 @@ declare class PayoutReporter implements PayoutTransport {
|
|
|
1540
1414
|
* and stored in `pending_dvm_reports.payload` for durable retry.
|
|
1541
1415
|
*/
|
|
1542
1416
|
interface RevenueReportPayload {
|
|
1543
|
-
/**
|
|
1417
|
+
/** Platform DVM record ID (`dvms.id`). Identifies which DVM earned the revenue. */
|
|
1544
1418
|
dvmId: string;
|
|
1545
|
-
/**
|
|
1419
|
+
/** Job identifier used by the platform to de-duplicate revenue rows and link to the job record. */
|
|
1546
1420
|
jobId: string;
|
|
1547
1421
|
/**
|
|
1548
|
-
*
|
|
1422
|
+
* Capability selected by dispatch for this job. Omitted for
|
|
1549
1423
|
* reports from older SDKs and legacy persisted jobs that predate capability
|
|
1550
1424
|
* dispatch; absence must not be rewritten as a capability name.
|
|
1551
1425
|
*/
|
|
1552
1426
|
capability?: string;
|
|
1553
|
-
/**
|
|
1427
|
+
/** Total amount paid by the caller in millisatoshis, across all credits that satisfied the job. */
|
|
1554
1428
|
paidMsats: number;
|
|
1555
|
-
/**
|
|
1429
|
+
/** Cashu mint URL from which the payment tokens were issued. Set only for the `cashu` rail; omitted for all other rails. */
|
|
1556
1430
|
paymentMint?: string;
|
|
1557
1431
|
/**
|
|
1558
|
-
*
|
|
1432
|
+
* Payment rail identifier (`"cashu"`, `"tempo"`, `"x402"`, `"stripe"`).
|
|
1559
1433
|
* Mirrors `PaymentMethod` on the platform; carried as a plain string over the wire
|
|
1560
1434
|
* so the SDK has no platform-type dependency.
|
|
1561
1435
|
*/
|
|
1562
1436
|
rail: string;
|
|
1563
1437
|
/**
|
|
1564
|
-
*
|
|
1438
|
+
* Settlement reference for the credit: EVM tx hash for `x402`, credential
|
|
1565
1439
|
* challenge ID for `tempo`, `X-Cashu-Request-Id` UUID for `cashu`. Required by the
|
|
1566
1440
|
* platform revenue ledger for `tempo`/`x402` rails (recordJobRevenue throws without it);
|
|
1567
1441
|
* nullable for `cashu`.
|
|
@@ -1574,20 +1448,20 @@ interface RevenueReportPayload {
|
|
|
1574
1448
|
*/
|
|
1575
1449
|
nativeAmount?: number;
|
|
1576
1450
|
/**
|
|
1577
|
-
*
|
|
1451
|
+
* Asset tag paired with `nativeAmount`: `"sats"`, `"usdc"`, `"usdc.e"`, or
|
|
1578
1452
|
* `"usd-cents"`. Both fields must be present for the platform to write a non-cashu
|
|
1579
1453
|
* revenue row.
|
|
1580
1454
|
*/
|
|
1581
1455
|
nativeAsset?: string;
|
|
1582
1456
|
/**
|
|
1583
|
-
*
|
|
1457
|
+
* Cashu flow discriminator. Set to `"p2pk_accumulator"` when the cashu
|
|
1584
1458
|
* accumulator path satisfied the job; omitted for legacy cashu. Written into
|
|
1585
1459
|
* `revenue_events.metadata.cashu_flow` by the platform so ops can split per-call cashu
|
|
1586
1460
|
* rows by source.
|
|
1587
1461
|
*/
|
|
1588
1462
|
cashuFlow?: string;
|
|
1589
1463
|
/**
|
|
1590
|
-
*
|
|
1464
|
+
* What the job cost the **builder** to serve, as declared by the
|
|
1591
1465
|
* handler through `ctx.cost()` — the other half of the
|
|
1592
1466
|
* margin the dashboard states. `amountMicro` is 1e-6 of `currency`, which
|
|
1593
1467
|
* is the currency the builder incurred the cost in and need not be the
|
|
@@ -1609,28 +1483,28 @@ interface RevenueReportPayload {
|
|
|
1609
1483
|
currency: string;
|
|
1610
1484
|
};
|
|
1611
1485
|
/**
|
|
1612
|
-
*
|
|
1486
|
+
* Credit this job's payment drew against. Present on
|
|
1613
1487
|
* every job a ledger-backed payment satisfied; its presence is what tells
|
|
1614
1488
|
* the platform the row is a **draw-keyed** revenue event rather than a
|
|
1615
1489
|
* pre-credits per-call one.
|
|
1616
1490
|
*/
|
|
1617
1491
|
creditId?: string;
|
|
1618
|
-
/**
|
|
1492
|
+
/** The draw this job settled — one draw, one revenue row. */
|
|
1619
1493
|
drawId?: string;
|
|
1620
|
-
/**
|
|
1494
|
+
/** The draw's fiat amount, 1e-6 of `creditCurrency`. The exact liability offset. */
|
|
1621
1495
|
drawAmountMicro?: number;
|
|
1622
|
-
/**
|
|
1496
|
+
/** Currency the credit is denominated in (lowercase ISO, e.g. `usd`). */
|
|
1623
1497
|
creditCurrency?: string;
|
|
1624
1498
|
/**
|
|
1625
|
-
*
|
|
1499
|
+
* Settlement instant (epoch ms) — draws are revenue *when they
|
|
1626
1500
|
* settle*, so this dates the row. Omitted for pre-credits rows, which the
|
|
1627
1501
|
* platform dates at record time as before.
|
|
1628
1502
|
*/
|
|
1629
1503
|
settledAt?: number;
|
|
1630
|
-
/**
|
|
1504
|
+
/** The funding's own rail reference, for reconciling a draw back to its deposit. */
|
|
1631
1505
|
fundingRef?: string;
|
|
1632
1506
|
/**
|
|
1633
|
-
*
|
|
1507
|
+
* Revenue class when this row is not ordinary service revenue.
|
|
1634
1508
|
* `short_pay_forfeit` marks money kept from an underpayment that bought no
|
|
1635
1509
|
* job so analytics can exclude it.
|
|
1636
1510
|
*/
|
|
@@ -1663,16 +1537,16 @@ interface JobTerminalReportPayload {
|
|
|
1663
1537
|
* delivery order.
|
|
1664
1538
|
*/
|
|
1665
1539
|
interface JobCostReportPayload {
|
|
1666
|
-
/**
|
|
1540
|
+
/** Platform DVM record ID (`dvms.id`); bound to the authenticating token. */
|
|
1667
1541
|
dvmId: string;
|
|
1668
|
-
/**
|
|
1542
|
+
/** Job identifier and the report's platform-side idempotency key. */
|
|
1669
1543
|
jobId: string;
|
|
1670
|
-
/**
|
|
1544
|
+
/** Capability selected by dispatch; absent only on legacy persisted jobs. */
|
|
1671
1545
|
capability?: string;
|
|
1672
|
-
/**
|
|
1546
|
+
/** Terminal outcome that explains why this event carries no revenue. */
|
|
1673
1547
|
outcome: "completed" | "failed" | "cancelled";
|
|
1674
1548
|
/**
|
|
1675
|
-
*
|
|
1549
|
+
* Number of declarations incorporated into `cost`; the
|
|
1676
1550
|
* platform keeps the payload with the highest revision for this job.
|
|
1677
1551
|
*/
|
|
1678
1552
|
costRevision: number;
|
|
@@ -1695,34 +1569,34 @@ interface JobCostReportPayload {
|
|
|
1695
1569
|
* redeliver freely.
|
|
1696
1570
|
*/
|
|
1697
1571
|
interface CreditDepositPayload {
|
|
1698
|
-
/**
|
|
1572
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1699
1573
|
dvmId: string;
|
|
1700
1574
|
creditId: string;
|
|
1701
|
-
/**
|
|
1575
|
+
/** Canonical owner of the funded credit. */
|
|
1702
1576
|
callerPubkey: string;
|
|
1703
|
-
/**
|
|
1577
|
+
/** The rail's settlement reference — the deposit's idempotency key. */
|
|
1704
1578
|
fundingId: string;
|
|
1705
1579
|
/** Payment rail (`"cashu"`, `"tempo"`, `"x402"`). */
|
|
1706
1580
|
rail: string;
|
|
1707
|
-
/**
|
|
1581
|
+
/** Rail value received, in millisatoshis. */
|
|
1708
1582
|
paidMsats: number;
|
|
1709
1583
|
/** Cashu only. Full received proof face before its receiver input fee. */
|
|
1710
1584
|
grossPaidMsats?: number;
|
|
1711
1585
|
/** Cashu only. Standalone NUT-02 fee reserved by the received proofs. */
|
|
1712
1586
|
cashuInputFeeMsats?: number;
|
|
1713
|
-
/**
|
|
1587
|
+
/** Rail-native atomic units; omitted for cashu (derived from `paidMsats`). */
|
|
1714
1588
|
nativeAmount?: number;
|
|
1715
|
-
/**
|
|
1589
|
+
/** Asset tag paired with `nativeAmount`. */
|
|
1716
1590
|
nativeAsset?: string;
|
|
1717
|
-
/**
|
|
1591
|
+
/** Cashu mint URL the funding proofs were issued from. */
|
|
1718
1592
|
paymentMint?: string;
|
|
1719
|
-
/**
|
|
1593
|
+
/** Fiat micro credited to the ledger — the liability this deposit creates. */
|
|
1720
1594
|
amountMicro: number;
|
|
1721
|
-
/**
|
|
1595
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1722
1596
|
creditCurrency: string;
|
|
1723
|
-
/**
|
|
1597
|
+
/** When the funding committed (epoch ms). */
|
|
1724
1598
|
fundedAt: number;
|
|
1725
|
-
/**
|
|
1599
|
+
/** Current credit expiry after this funding committed (epoch ms). */
|
|
1726
1600
|
expiryMs: number;
|
|
1727
1601
|
/**
|
|
1728
1602
|
* Tempo session funding only: the TIP-1034 channel this deposit
|
|
@@ -1783,59 +1657,45 @@ interface CreditDepositPayload {
|
|
|
1783
1657
|
* redeliver freely.
|
|
1784
1658
|
*/
|
|
1785
1659
|
interface CreditDrawReleasePayload {
|
|
1786
|
-
/**
|
|
1660
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1787
1661
|
dvmId: string;
|
|
1788
1662
|
creditId: string;
|
|
1789
|
-
/**
|
|
1663
|
+
/** Released draw; the platform idempotency key with DVM and credit. */
|
|
1790
1664
|
drawId: string;
|
|
1791
|
-
/**
|
|
1665
|
+
/** Fiat micro restored to availability. */
|
|
1792
1666
|
amountMicro: number;
|
|
1793
|
-
/**
|
|
1667
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1794
1668
|
creditCurrency: string;
|
|
1795
|
-
/**
|
|
1669
|
+
/** Per-credit gap-free sequence assigned when the draw was placed. */
|
|
1796
1670
|
ledgerSeq: number;
|
|
1797
|
-
/**
|
|
1671
|
+
/** When the hold reached `released` (epoch ms). */
|
|
1798
1672
|
releasedAt: number;
|
|
1799
1673
|
}
|
|
1800
1674
|
type CreditDrainRepairKind = "x402_settlement_reconciled" | "tempo_close_reconciled";
|
|
1801
1675
|
/**
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1804
|
-
* the caller
|
|
1805
|
-
* so outstanding liability nets out to `deposited − drawn − drained`; never
|
|
1806
|
-
* summed into revenue, and never a revenue row — no job was sold.
|
|
1807
|
-
*
|
|
1808
|
-
* Reported only on the drain's **terminal** transition (`picked_up` for cashu,
|
|
1809
|
-
* `sent` for the other rails), because that is where the money has verifiably
|
|
1810
|
-
* left. `requested` and `parked` have not paid the caller yet.
|
|
1811
|
-
*
|
|
1812
|
-
* Idempotent on `(dvmId, creditId, drainId)`, so the shared retry loop can
|
|
1813
|
-
* redeliver freely.
|
|
1814
|
-
*
|
|
1815
|
-
* Fiat micro only, with no `paidMsats` sibling: `credit_drains` holds no rail
|
|
1816
|
-
* value (the sats figure is derived at read time from a live rate), so there
|
|
1817
|
-
* would be nothing truthful to put in one — and the liability arithmetic this
|
|
1818
|
-
* 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).
|
|
1819
1679
|
*/
|
|
1820
1680
|
interface CreditDrainPayload {
|
|
1821
|
-
/**
|
|
1681
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1822
1682
|
dvmId: string;
|
|
1823
1683
|
creditId: string;
|
|
1824
|
-
/**
|
|
1684
|
+
/** Canonical owner of the reclaimed credit. */
|
|
1825
1685
|
callerPubkey: string;
|
|
1826
|
-
/**
|
|
1686
|
+
/** The drain's own idempotency key. */
|
|
1827
1687
|
drainId: string;
|
|
1828
1688
|
/** Payout rail (`"cashu"`, `"x402"`, `"tempo"`). */
|
|
1829
1689
|
method: string;
|
|
1830
|
-
/**
|
|
1690
|
+
/** Fiat micro reclaimed — the liability this drain extinguishes. */
|
|
1831
1691
|
amountMicro: number;
|
|
1832
|
-
/**
|
|
1692
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1833
1693
|
creditCurrency: string;
|
|
1834
1694
|
/** Terminal state: `"picked_up"` for cashu, `"sent"` for other rails. */
|
|
1835
1695
|
status: string;
|
|
1836
|
-
/**
|
|
1696
|
+
/** When the money left (epoch ms). */
|
|
1837
1697
|
drainedAt: number;
|
|
1838
|
-
/**
|
|
1698
|
+
/** Per-credit gap-free ledger sequence, for reconciling against the DVM's ledger. */
|
|
1839
1699
|
ledgerSeq?: number;
|
|
1840
1700
|
/** Set when an operator repair verb completed this reclaim. */
|
|
1841
1701
|
repaired?: boolean;
|
|
@@ -1893,21 +1753,21 @@ type CreditDrainEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrainPaylo
|
|
|
1893
1753
|
* redeliver freely.
|
|
1894
1754
|
*/
|
|
1895
1755
|
interface CreditExpiryReleasePayload {
|
|
1896
|
-
/**
|
|
1756
|
+
/** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
|
|
1897
1757
|
dvmId: string;
|
|
1898
1758
|
creditId: string;
|
|
1899
|
-
/**
|
|
1759
|
+
/** Per-expiry-episode key; the platform idempotency key with DVM and credit. */
|
|
1900
1760
|
releaseId: string;
|
|
1901
1761
|
callerPubkey: string;
|
|
1902
1762
|
/** Funding rail. `null` on a credit funded before the basis became mandatory. */
|
|
1903
1763
|
rail: string | null;
|
|
1904
|
-
/**
|
|
1764
|
+
/** Fiat micro released — the liability this expiry extinguishes. */
|
|
1905
1765
|
amountMicro: number;
|
|
1906
|
-
/**
|
|
1766
|
+
/** Currency `amountMicro` is 1e-6 of. */
|
|
1907
1767
|
creditCurrency: string;
|
|
1908
|
-
/**
|
|
1768
|
+
/** When the credit's TTL ran out (epoch ms) — when the liability ended. */
|
|
1909
1769
|
expiryMs: number;
|
|
1910
|
-
/**
|
|
1770
|
+
/** When the sweep observed the expiry (epoch ms). */
|
|
1911
1771
|
releasedAt: number;
|
|
1912
1772
|
/** Set only on the reversal report: when the revival funding landed (epoch ms). */
|
|
1913
1773
|
reversedAt?: number | null;
|
|
@@ -1933,21 +1793,21 @@ type CreditExpiryReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditEx
|
|
|
1933
1793
|
* report degrades to log-only rather than blocking the sweep.
|
|
1934
1794
|
*/
|
|
1935
1795
|
interface PaidJobDeathPayload {
|
|
1936
|
-
/**
|
|
1796
|
+
/** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
|
|
1937
1797
|
dvmId: string;
|
|
1938
1798
|
jobId: string;
|
|
1939
1799
|
capability: string;
|
|
1940
|
-
/**
|
|
1800
|
+
/** Amount of the released credit draw in millisatoshis. */
|
|
1941
1801
|
paidMsats: number;
|
|
1942
1802
|
/** Payment rail of the released draw (`"cashu"`, `"tempo"`, `"x402"`). */
|
|
1943
1803
|
rail: string;
|
|
1944
1804
|
/** Reaper reason: dead worker or never-terminal idle job. */
|
|
1945
1805
|
reason: string;
|
|
1946
|
-
/**
|
|
1806
|
+
/** Rail-native released amount, paired with `nativeAsset`. */
|
|
1947
1807
|
nativeAmount?: number;
|
|
1948
|
-
/**
|
|
1808
|
+
/** Asset tag for `nativeAmount` (`"sats"` / `"usdc"` / …). */
|
|
1949
1809
|
nativeAsset?: string;
|
|
1950
|
-
/**
|
|
1810
|
+
/** Cashu mint URL the credit draw's tokens were issued from. */
|
|
1951
1811
|
paymentMint?: string;
|
|
1952
1812
|
}
|
|
1953
1813
|
type RevenueSkippedNoRailReason = "credit_rail_null" | "credit_rail_unrecognized" | "job_rail_missing" | "ledger_not_configured";
|
|
@@ -1959,16 +1819,16 @@ type RevenueSkippedNoRailReason = "credit_rail_null" | "credit_rail_unrecognized
|
|
|
1959
1819
|
* and a failed alert must never block terminal handling or reconciliation.
|
|
1960
1820
|
*/
|
|
1961
1821
|
interface RevenueSkippedNoRailPayload {
|
|
1962
|
-
/**
|
|
1822
|
+
/** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
|
|
1963
1823
|
dvmId: string;
|
|
1964
1824
|
jobId: string;
|
|
1965
1825
|
creditId: string;
|
|
1966
1826
|
drawId: string;
|
|
1967
|
-
/**
|
|
1827
|
+
/** Caller payment captured for the job, in millisatoshis. */
|
|
1968
1828
|
paidMsats: number;
|
|
1969
|
-
/**
|
|
1829
|
+
/** Settled draw amount in 1e-6 of `creditCurrency`. */
|
|
1970
1830
|
drawAmountMicro?: number;
|
|
1971
|
-
/**
|
|
1831
|
+
/** Currency the draw amount is denominated in. */
|
|
1972
1832
|
creditCurrency?: string;
|
|
1973
1833
|
/** Credit funding rail as stored on the draw, or null for legacy rows. */
|
|
1974
1834
|
creditRail: string | null;
|
|
@@ -2084,13 +1944,8 @@ declare class RevenueReporter {
|
|
|
2084
1944
|
/** Join a released draw to the transaction that made the hold terminal. */
|
|
2085
1945
|
enqueueCreditDrawRelease(tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload): Promise<void>;
|
|
2086
1946
|
/**
|
|
2087
|
-
*
|
|
2088
|
-
*
|
|
2089
|
-
* Same discipline as {@link enqueueDeposit} and the same reason: no network
|
|
2090
|
-
* I/O here, only the durable queue row, written through `tx` so the report
|
|
2091
|
-
* and the status CAS commit or roll back together. A drain whose row was
|
|
2092
|
-
* lost between the two would overstate outstanding liability forever —
|
|
2093
|
-
* the mirror image of a lost deposit.
|
|
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.
|
|
2094
1949
|
*/
|
|
2095
1950
|
enqueueCreditDrain(tx: RevenueReporterQuerier, payload: CreditDrainPayload): Promise<void>;
|
|
2096
1951
|
/**
|
|
@@ -2120,14 +1975,8 @@ declare class RevenueReporter {
|
|
|
2120
1975
|
*/
|
|
2121
1976
|
reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
|
|
2122
1977
|
/**
|
|
2123
|
-
*
|
|
2124
|
-
*
|
|
2125
|
-
*
|
|
2126
|
-
* Same discipline as {@link enqueueCreditDrain}: no network I/O, only the
|
|
2127
|
-
* durable queue row written through `tx`. A release whose row was lost after
|
|
2128
|
-
* the sweep committed would understate committed value forever; a lost
|
|
2129
|
-
* reversal would overstate it, which is the worse direction — the builder
|
|
2130
|
-
* 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.
|
|
2131
1980
|
*/
|
|
2132
1981
|
enqueueCreditExpiryRelease(tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload): Promise<void>;
|
|
2133
1982
|
/**
|
|
@@ -2259,7 +2108,6 @@ interface X402SettlementPage {
|
|
|
2259
2108
|
interface X402RelaySubmissionLock {
|
|
2260
2109
|
withRelaySubmissionLock<T>(key: string, operation: () => Promise<T>): Promise<T>;
|
|
2261
2110
|
}
|
|
2262
|
-
/** Construction options for {@link PostgresX402ChannelStorage}. */
|
|
2263
2111
|
interface X402ChannelStorageOpts {
|
|
2264
2112
|
/**
|
|
2265
2113
|
* Override the relay submission lock's wait ceiling, in milliseconds.
|
|
@@ -2360,7 +2208,6 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2360
2208
|
private readonly relayLockTimeoutMs;
|
|
2361
2209
|
private readonly terminalizeUnbackedCredit?;
|
|
2362
2210
|
constructor(pool: Pool, opts?: X402ChannelStorageOpts);
|
|
2363
|
-
/** Create the durable channel table under the SDK-wide DDL lock. */
|
|
2364
2211
|
init(): Promise<void>;
|
|
2365
2212
|
/**
|
|
2366
2213
|
* Read the fleet's durable "claimed funds are awaiting transfer" marker.
|
|
@@ -2492,7 +2339,6 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2492
2339
|
* to finish.
|
|
2493
2340
|
*/
|
|
2494
2341
|
completeSettlement(settlementId: string, resolvedAtMs?: number): Promise<void>;
|
|
2495
|
-
/** Load one channel snapshot. */
|
|
2496
2342
|
get(channelId: string): Promise<Channel | undefined>;
|
|
2497
2343
|
/** List all channel snapshots in stable channel-id order. */
|
|
2498
2344
|
list(): Promise<Channel[]>;
|
|
@@ -2568,7 +2414,6 @@ declare class PostgresX402ChannelStorage implements ChannelStorage {
|
|
|
2568
2414
|
private updateInTransaction;
|
|
2569
2415
|
}
|
|
2570
2416
|
|
|
2571
|
-
/** Durable channel seam used to compose a settled draw with consumed Tempo value. */
|
|
2572
2417
|
interface TempoSessionSettlementStore {
|
|
2573
2418
|
withDrawPlacement<value>(channelId: string, operation: (tx: CreditLedgerQuerier, lifecycle: TempoSessionLifecycle$1) => Promise<value>, tx?: CreditLedgerQuerier): Promise<value>;
|
|
2574
2419
|
withTerminalConsumption<value>(channelId: string, operation: (tx: CreditLedgerQuerier, lifecycle: TempoSessionLifecycle$1) => Promise<{
|
|
@@ -2577,7 +2422,6 @@ interface TempoSessionSettlementStore {
|
|
|
2577
2422
|
consume: boolean;
|
|
2578
2423
|
}>): Promise<value>;
|
|
2579
2424
|
}
|
|
2580
|
-
/** Tempo channel state read while holding its durable row lock. */
|
|
2581
2425
|
interface TempoSessionLifecycle$1 {
|
|
2582
2426
|
finalized: boolean;
|
|
2583
2427
|
closeRequestedAt: bigint;
|
|
@@ -2593,7 +2437,6 @@ interface TempoCreditLossEvidence {
|
|
|
2593
2437
|
consumedNative: bigint;
|
|
2594
2438
|
observedAt?: number;
|
|
2595
2439
|
}
|
|
2596
|
-
/** Immutable audit record for one terminal, unbacked Tempo credit. */
|
|
2597
2440
|
interface TempoCreditLoss {
|
|
2598
2441
|
creditId: string;
|
|
2599
2442
|
channelId: string;
|
|
@@ -2613,7 +2456,6 @@ interface X402CreditLossEvidence {
|
|
|
2613
2456
|
totalClaimedNative: bigint;
|
|
2614
2457
|
observedAt?: number;
|
|
2615
2458
|
}
|
|
2616
|
-
/** Immutable audit record for one terminal, unbacked x402 credit. */
|
|
2617
2459
|
interface X402CreditLoss {
|
|
2618
2460
|
creditId: string;
|
|
2619
2461
|
channelId: string;
|
|
@@ -2626,25 +2468,13 @@ interface X402CreditLoss {
|
|
|
2626
2468
|
observedAt: number;
|
|
2627
2469
|
}
|
|
2628
2470
|
/**
|
|
2629
|
-
*
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
* draws (`pending` at job-accept → `settled` on success / `released` on
|
|
2633
|
-
* failure), and a per-credit gap-free `ledger_seq` for the receipt evidence
|
|
2634
|
-
* chain (`ReceiptCredit` in `lib/receipt.ts`).
|
|
2635
|
-
*
|
|
2636
|
-
* Concurrency contract: **every write to `credit_draws` happens inside a
|
|
2637
|
-
* transaction that first takes `SELECT … FOR UPDATE` on the parent `credits`
|
|
2638
|
-
* row.** The pending-hold SUM that defines available balance is only stable
|
|
2639
|
-
* because of that lock — never add a `credit_draws` write path that skips it.
|
|
2640
|
-
* `ctx.store` / KV is prohibited for credit state: last-write-wins KV on a
|
|
2641
|
-
* 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.
|
|
2642
2474
|
*
|
|
2643
|
-
*
|
|
2644
|
-
*
|
|
2645
|
-
*
|
|
2646
|
-
* column can be added by idempotent migration if per-mount ledgers are ever
|
|
2647
|
-
* 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.
|
|
2648
2478
|
*/
|
|
2649
2479
|
declare class CreditLedger {
|
|
2650
2480
|
private readonly pool;
|
|
@@ -2658,267 +2488,95 @@ declare class CreditLedger {
|
|
|
2658
2488
|
private expiryReleaseOutbox?;
|
|
2659
2489
|
constructor(pool: CreditLedgerPool, tempoSessionStore?: TempoSessionSettlementStore | undefined);
|
|
2660
2490
|
/**
|
|
2661
|
-
*
|
|
2662
|
-
*
|
|
2663
|
-
* Late, because the settlement store is built with the batch-settlement
|
|
2664
|
-
* server, which is built *after* the ledger it reads earned draws from. An
|
|
2665
|
-
* unbound ledger simply does not gate — correct for a DVM with no x402
|
|
2666
|
-
* 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.
|
|
2667
2493
|
*/
|
|
2668
2494
|
useX402SettlementGate(gate: X402RefundSettlementGate): void;
|
|
2669
2495
|
/**
|
|
2670
|
-
*
|
|
2671
|
-
*
|
|
2672
|
-
*
|
|
2673
|
-
* Held on the ledger rather than threaded through {@link fund} for the same
|
|
2674
|
-
* reason as the gate above: the revival half fires from *every* funding path
|
|
2675
|
-
* Cashu commit, x402 exact and channel, Tempo, the Lightning invoice
|
|
2676
|
-
* settle, the implicit N=1 per-call payment — and a seam each of those has to
|
|
2677
|
-
* remember to pass is a seam one of them will eventually forget.
|
|
2678
|
-
*
|
|
2679
|
-
* Unbound (a `dvmctl dev` server, a self-hosted builder, a test host) the
|
|
2680
|
-
* sweep and the reversal still run and still write the ledger's own rows;
|
|
2681
|
-
* only the platform report is skipped. Reporting is bookkeeping and must
|
|
2682
|
-
* 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.
|
|
2683
2499
|
*/
|
|
2684
2500
|
useCreditExpiryReleaseOutbox(outbox: CreditExpiryReleaseOutbox): void;
|
|
2685
|
-
/** Create the `credits` / `credit_draws` tables if absent. Call once at SDK boot. */
|
|
2686
2501
|
init(): Promise<void>;
|
|
2687
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock}. */
|
|
2688
2502
|
private createTables;
|
|
2689
2503
|
/**
|
|
2690
|
-
*
|
|
2691
|
-
*
|
|
2692
|
-
*
|
|
2693
|
-
*
|
|
2694
|
-
*
|
|
2695
|
-
*
|
|
2696
|
-
* The lot is derived from `credits.msats_remaining`, not replayed out of
|
|
2697
|
-
* `credit_fundings`. That is not a shortcut: `credit_fundings` records only
|
|
2698
|
-
* `{amount_micro, rail}` — no instrument amount at all — and is not written
|
|
2699
|
-
* on every funding path, whereas `msats_remaining` **is** the credit's
|
|
2700
|
-
* current unspent rail value, maintained pro rata on every draw since
|
|
2701
|
-
* One synthetic lot pairing it with `balance_micro` therefore
|
|
2702
|
-
* reproduces today's in-kind position exactly, and FIFO over a single lot is
|
|
2703
|
-
* trivially correct. Historic per-funding rates are unrecoverable and would
|
|
2704
|
-
* change nothing: only the remaining position is owed.
|
|
2705
|
-
*
|
|
2706
|
-
* The semantics change is deliberately retroactive — every
|
|
2707
|
-
* current holder is first-party, so there is one regime and no legacy
|
|
2708
|
-
* 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.
|
|
2709
2509
|
*/
|
|
2710
2510
|
private backfillFundingLots;
|
|
2711
2511
|
/**
|
|
2712
|
-
*
|
|
2713
|
-
*
|
|
2714
|
-
*
|
|
2715
|
-
*
|
|
2716
|
-
* and `currency` must match or the fund is refused with a typed error —
|
|
2717
|
-
* without that check a top-up against someone else's `credit_id` would
|
|
2718
|
-
* silently merge two callers' money.
|
|
2719
|
-
*
|
|
2720
|
-
* Pass `tx` (a client inside a caller-owned `BEGIN`) to commit the rail
|
|
2721
|
-
* receive and the ledger credit atomically (spec condition 3 — the
|
|
2722
|
-
* verifier does this). The ledger issues **no** transaction control on `tx`.
|
|
2723
|
-
*
|
|
2724
|
-
* Without `tx` it opens one of its own, because a funding is no longer a
|
|
2725
|
-
* single statement: it upserts the credit, records its funding lot
|
|
2726
|
-
* and reverses any standing expiry release — and that
|
|
2727
|
-
* last leg restores balance and queues a report. A crash between the upsert
|
|
2728
|
-
* and the reversal would leave a revived credit whose release still stands,
|
|
2729
|
-
* which the sweep's own exclusion then makes permanent: `balance_micro > 0`
|
|
2730
|
-
* but a standing release means it is neither drainable nor re-releasable.
|
|
2731
|
-
*
|
|
2732
|
-
* `basis` records the rail value behind the fiat so each draw can
|
|
2733
|
-
* be allocated its share of the rail-native amount actually received. A
|
|
2734
|
-
* top-up on a different rail than the credit's is refused with
|
|
2735
|
-
* `rail_mismatch`: sats and USDC microunits aren't summable, so a blended
|
|
2736
|
-
* credit would have no coherent native basis to allocate from.
|
|
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.
|
|
2737
2516
|
*
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
2741
|
-
*
|
|
2742
|
-
* books nothing and outstanding liability (`deposits − draw revenue`) stays
|
|
2743
|
-
* overstated by the drawn amount forever. `credits.rail` stays nullable
|
|
2744
|
-
* because rows written before this change still exist in live databases:
|
|
2745
|
-
* **write is closed, read is not.** A legacy row adopts a rail from its first
|
|
2746
|
-
* typed top-up via the `COALESCE` below.
|
|
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.
|
|
2747
2521
|
*
|
|
2748
|
-
*
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2751
|
-
* the credit it opened, and a credit opened by a one-shot (a Tempo charge, an
|
|
2752
|
-
* x402 exact authorization) may never adopt one. The x402 half used to admit
|
|
2753
|
-
* `credits.x402_channel_id IS NULL`, so a channel deposit bound itself to a
|
|
2754
|
-
* credit already holding exact-funded value: `earnedNativeForX402Channel`
|
|
2755
|
-
* then read that credit's whole settled-draw history as the channel's
|
|
2756
|
-
* earnings, and the first claim tick swept a deposit nobody had consumed,
|
|
2757
|
-
* while the unspent balance lost every exit (the drain's refund base goes
|
|
2758
|
-
* below `totalClaimed`, and a channel-bound credit refuses every non-x402
|
|
2759
|
-
* drain). `credits_x402_channel_uidx` does not cover this: it guards
|
|
2760
|
-
* one-credit-per-channel, not one-source-per-credit.
|
|
2761
|
-
*
|
|
2762
|
-
* An x402 channel deposit is additionally refused `settlement_pending` while
|
|
2763
|
-
* that channel carries an unresolved refund — see the gate read
|
|
2764
|
-
* below. This method is the authority on that rule, as it is on the binding
|
|
2765
|
-
* 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.
|
|
2766
2525
|
*/
|
|
2767
2526
|
fund(args: CreditFundArgs): Promise<CreditSnapshot>;
|
|
2768
|
-
/**
|
|
2769
|
-
* The funding itself, on whichever handle {@link fund} chose. Every refusal
|
|
2770
|
-
* it raises is the caller's to see unchanged; a self-opened transaction rolls
|
|
2771
|
-
* back around it.
|
|
2772
|
-
*/
|
|
2773
2527
|
private fundOn;
|
|
2774
2528
|
/**
|
|
2775
|
-
*
|
|
2776
|
-
*
|
|
2777
|
-
* release
|
|
2778
|
-
*
|
|
2779
|
-
* A release records the credit's **final** undrawn remainder and zeroes it
|
|
2780
|
-
* (see {@link releaseExpiredCreditLocked}). Funding adds to the balance and
|
|
2781
|
-
* overwrites `expiry_ms` (the caller-ownership contract carry-forward: the credit is a rolling
|
|
2782
|
-
* buffer), so the moment money arrives the recorded remainder is no longer
|
|
2783
|
-
* final: the release stops counting and its micro come back.
|
|
2784
|
-
*
|
|
2785
|
-
* Deliberately unconditional on the incoming expiry rather than gated on
|
|
2786
|
-
* `expiryMs > nowMs`. A release exists only for a credit that had already
|
|
2787
|
-
* lapsed, so any funding that reaches one is money the release did not
|
|
2788
|
-
* account for — and a funding that arrives with an already-past expiry leaves
|
|
2789
|
-
* a credit the very next sweep re-releases at its new, larger remainder. One
|
|
2790
|
-
* rule, self-healing in both directions, and it never needs to read the
|
|
2791
|
-
* pre-funding expiry the upsert has already overwritten.
|
|
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.
|
|
2792
2532
|
*
|
|
2793
|
-
*
|
|
2794
|
-
*
|
|
2795
|
-
* with the money or not at all. A reversal that committed without its report
|
|
2796
|
-
* would be unrecoverable: `WHERE reversed_at IS NULL` means no later pass
|
|
2797
|
-
* re-derives it.
|
|
2798
|
-
*
|
|
2799
|
-
* @returns the credit row as the restore left it, or undefined when there was
|
|
2800
|
-
* nothing to reverse. The caller reads its snapshot off this rather than off
|
|
2801
|
-
* 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.
|
|
2802
2535
|
*/
|
|
2803
2536
|
private reverseExpiryReleases;
|
|
2804
2537
|
/**
|
|
2805
|
-
* Release
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2808
|
-
* **What is released.** The credit's whole `balance_micro`. Expiry ends
|
|
2809
|
-
* spending but never ownership of the record, so the balance stays readable;
|
|
2810
|
-
* what ends is the caller's claim on it, and on the prepaid rails the value
|
|
2811
|
-
* behind it is already in builder custody.
|
|
2812
|
-
*
|
|
2813
|
-
* **Tempo is excluded**, at both of its markers (`tempo_channel_id` and a
|
|
2814
|
-
* `tempo` rail). A Tempo credit is channel-backed and its undrawn value
|
|
2815
|
-
* returns to the *caller* at channel exit, so releasing it would book the
|
|
2816
|
-
* builder money the chain is about to hand back.
|
|
2817
|
-
*
|
|
2818
|
-
* **A credit with a pending hold is skipped, not partially released.** A draw
|
|
2819
|
-
* placed before expiry stays settleable afterwards (`draw`'s replay lookup
|
|
2820
|
-
* runs above the expiry check, so a lost response is still recoverable), so
|
|
2821
|
-
* the remainder is not final while a hold is outstanding. Skipping costs one
|
|
2822
|
-
* sweep interval and keeps the released figure exactly "what nothing bought";
|
|
2823
|
-
* holds do resolve — the orphan-draw watchdog is what guarantees
|
|
2824
|
-
* a stranded one still reaches a terminal state.
|
|
2825
|
-
*
|
|
2826
|
-
* **Idempotent** two ways. `release_id` is derived from the expiry instant,
|
|
2827
|
-
* so a re-sweep after a crash between the insert and its report collides on
|
|
2828
|
-
* the same primary key instead of writing a second row; and the candidate
|
|
2829
|
-
* scan excludes any credit that already has an un-reversed release.
|
|
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.
|
|
2830
2541
|
*
|
|
2831
|
-
*
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
2835
|
-
*
|
|
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.
|
|
2836
2547
|
*/
|
|
2837
2548
|
sweepExpiredCredits(args?: {
|
|
2838
2549
|
nowMs?: number;
|
|
2839
|
-
/** Credits released per pass. Bounds the work a long-idle DVM does at boot. */
|
|
2840
2550
|
limit?: number;
|
|
2841
2551
|
}): Promise<{
|
|
2842
2552
|
released: CreditExpiryRelease[];
|
|
2843
2553
|
}>;
|
|
2844
2554
|
/**
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
2848
|
-
* against, or already released between the scan and the lock.
|
|
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.
|
|
2849
2558
|
*
|
|
2850
|
-
*
|
|
2851
|
-
*
|
|
2852
|
-
* records that the caller's claim ended; it does not *end* it. `requestDrain`
|
|
2853
|
-
* has no expiry check by design (expiry gates new draws, not reclaims), so a
|
|
2854
|
-
* credit whose remainder had been released and reported was still fully
|
|
2855
|
-
* drainable: the DVM would pay out money the platform had already booked as
|
|
2856
|
-
* the builder's, and `deposited - drawn - drained - released` would go
|
|
2857
|
-
* negative on the same micro. With the balance at zero, `requestDrain`'s
|
|
2858
|
-
* existing `availableMicro <= 0` guard refuses with `nothing_to_drain` and no
|
|
2859
|
-
* new check is needed anywhere. {@link reverseExpiryReleases} puts the
|
|
2860
|
-
* balance back when a funding revives the credit.
|
|
2861
|
-
*
|
|
2862
|
-
* `status` deliberately stays `active`: a released credit must remain
|
|
2863
|
-
* fundable, or the revival rule has nothing to revive.
|
|
2864
|
-
*
|
|
2865
|
-
* The zeroed balance also takes the credit out of `BITCOIN_CREDIT_PREDICATE`
|
|
2866
|
-
* (`balance_micro > 0`), so the in-kind sweep floor stops counting sats the
|
|
2867
|
-
* builder no longer owes back — and counts them again after a revival, with
|
|
2868
|
-
* 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.
|
|
2869
2561
|
*/
|
|
2870
2562
|
private releaseExpiredCreditLocked;
|
|
2871
2563
|
/**
|
|
2872
|
-
* Record
|
|
2873
|
-
*
|
|
2874
|
-
*
|
|
2875
|
-
* whatever transaction the rail opened — `withCommitTx` for Cashu,
|
|
2876
|
-
* `ProcessedPaymentStore.withTx` for x402/Tempo, `settleInvoice`'s own
|
|
2877
|
-
* `BEGIN` for Lightning. The funding-atomicity invariant therefore extends
|
|
2878
|
-
* to it structurally rather than by every call site remembering: a lot
|
|
2879
|
-
* cannot exist without its ledger credit, or the credit without its lot.
|
|
2880
|
-
*
|
|
2881
|
-
* Only a **channel-backed** credit writes no lot: it is in-kind already —
|
|
2882
|
-
* the escrow itself retains the unused value — so a lot on one would be
|
|
2883
|
-
* counted as Bitcoin deposit liability the hub does not hold.
|
|
2884
|
-
*
|
|
2885
|
-
* Every other non-channel Bitcoin funding writes one, the implicit N=1
|
|
2886
|
-
* per-call payment included. That one funds and draws the same amount in
|
|
2887
|
-
* this transaction, so it looks like a lot born depleted — but the draw is a
|
|
2888
|
-
* *pending hold*, not a debit, and only a `completed` job settles it
|
|
2889
|
-
* (`JobManager.settlesDraw`). A failed, cancelled or stale-swept job
|
|
2890
|
-
* releases the hold and the whole funded amount becomes reclaimable balance,
|
|
2891
|
-
* so suppressing the lot here would leave routine traffic reclaiming off a
|
|
2892
|
-
* live rate and missing from the sweep floor. One small insert on the hot
|
|
2893
|
-
* path buys the invariant the liability read depends on: open lots cover
|
|
2894
|
-
* `balance_micro` for every Bitcoin credit, always.
|
|
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.
|
|
2895
2567
|
*
|
|
2896
|
-
* A zero-sats basis still
|
|
2897
|
-
*
|
|
2898
|
-
* distinguishable state from "no lot at all" for the reclaim and the
|
|
2899
|
-
* 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.
|
|
2900
2570
|
*/
|
|
2901
2571
|
private recordFundingLot;
|
|
2902
2572
|
/**
|
|
2903
|
-
* Place a
|
|
2904
|
-
*
|
|
2905
|
-
*
|
|
2906
|
-
* `replayed: true` and places no second hold — this is how a caller recovers
|
|
2907
|
-
* a lost response (spec condition 1). A replay whose `amountMicro` or
|
|
2908
|
-
* `jobId` differs from the recorded draw is not a replay but a money bug in
|
|
2909
|
-
* the caller, refused with `draw_conflict`.
|
|
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.
|
|
2910
2576
|
*
|
|
2911
|
-
*
|
|
2912
|
-
*
|
|
2913
|
-
*
|
|
2914
|
-
* expired credit are refused with `credit_expired`; the balance stays
|
|
2915
|
-
* intact and readable (the caller-ownership contract: expiry ends spending, never ownership).
|
|
2916
|
-
*
|
|
2917
|
-
* Pass `tx` (a client inside a caller-owned `BEGIN`) to place the hold in
|
|
2918
|
-
* the same transaction as the rail commit and the `fund` upsert (spec
|
|
2919
|
-
* condition 3 — the implicit N=1 path). The ledger issues no
|
|
2920
|
-
* transaction control on `tx`; the `SELECT … FOR UPDATE` row lock is still
|
|
2921
|
-
* 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.
|
|
2922
2580
|
*/
|
|
2923
2581
|
draw(args: {
|
|
2924
2582
|
creditId: string;
|
|
@@ -2931,63 +2589,23 @@ declare class CreditLedger {
|
|
|
2931
2589
|
nowMs?: number;
|
|
2932
2590
|
}): Promise<DrawResult>;
|
|
2933
2591
|
/**
|
|
2934
|
-
*
|
|
2935
|
-
*
|
|
2936
|
-
*
|
|
2937
|
-
* `undefined` whenever there is nothing to ask — an unbound credit, or a DVM
|
|
2938
|
-
* with no durable settlement store, where a settlement row cannot exist.
|
|
2939
|
-
*
|
|
2940
|
-
* Public since the behavior was introduced, so `/v1/credit`'s pre-payment preflight can ask the
|
|
2941
|
-
* same question `fund` will ask from inside the rail transaction — one read,
|
|
2942
|
-
* 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.
|
|
2943
2595
|
*/
|
|
2944
2596
|
blockingX402Refund(channelId: string | null | undefined, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
|
|
2945
2597
|
/**
|
|
2946
|
-
* Grow
|
|
2947
|
-
*
|
|
2948
|
-
*
|
|
2949
|
-
* so the receipt's `ReceiptCredit` block countersigns the job's full cost
|
|
2950
|
-
* and `revenue_events` still books exactly one row per job.
|
|
2951
|
-
*
|
|
2952
|
-
* `draw()` cannot express this: a same-`draw_id` call with a different
|
|
2953
|
-
* 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.
|
|
2954
2601
|
*
|
|
2955
|
-
*
|
|
2956
|
-
*
|
|
2957
|
-
* **every** pending hold, this draw's own included. That is deliberately the
|
|
2958
|
-
* conservative reading (a fresh draw of `addAmountMicro` would see exactly
|
|
2959
|
-
* the same figure), and it is what keeps the total held after growth inside
|
|
2960
|
-
* 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.
|
|
2961
2604
|
*
|
|
2962
|
-
*
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
2965
|
-
*
|
|
2966
|
-
*
|
|
2967
|
-
* **`ledger_seq` is not re-taken.** The per-credit sequence is gap-free
|
|
2968
|
-
* (`credit_draws_seq_uidx`), so moving this draw forward would strand its
|
|
2969
|
-
* original number. The consequence is deliberate and worth knowing: the
|
|
2970
|
-
* recorded `balance_after_micro` is the post-growth figure stamped at the
|
|
2971
|
-
* *original* seq, so a credit with interleaved sibling activity has a
|
|
2972
|
-
* trajectory that is truthful per draw rather than monotonic across seqs.
|
|
2973
|
-
*
|
|
2974
|
-
* **No expiry check**, matching `settle`/`release`: the job was accepted
|
|
2975
|
-
* pre-expiry and is still running. Refusing here would strand the handler
|
|
2976
|
-
* mid-flight over a clock the caller can't influence.
|
|
2977
|
-
*
|
|
2978
|
-
* **Not idempotent by itself.** Growth carries no key of its own — it
|
|
2979
|
-
* inherits the rail's (the accumulator's `(dvm_id, request_id)` UNIQUE rolls
|
|
2980
|
-
* the whole transaction back on replay; x402/mpp collide on the
|
|
2981
|
-
* `processed_payments` marker). Never call it outside a rail commit.
|
|
2982
|
-
*
|
|
2983
|
-
* **`capMicro` bounds cumulative growth at what the job cumulatively asked,**
|
|
2984
|
-
* and it is the only ceiling that can refuse a top-up: the
|
|
2985
|
-
* available-balance check cannot, because the `fund` a moment earlier in
|
|
2986
|
-
* this same transaction raised the balance by exactly the amount being
|
|
2987
|
-
* drawn. Excess is **granted partially or not at all rather than thrown** —
|
|
2988
|
-
* this runs inside the rail commit, so a throw would roll back cashu proofs
|
|
2989
|
-
* the caller already sent. The refused remainder stays funded balance the
|
|
2990
|
-
* 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.
|
|
2991
2609
|
*/
|
|
2992
2610
|
growDraw(args: {
|
|
2993
2611
|
creditId: string;
|
|
@@ -3051,18 +2669,9 @@ declare class CreditLedger {
|
|
|
3051
2669
|
nowMs?: number;
|
|
3052
2670
|
}): Promise<DrawResolution>;
|
|
3053
2671
|
/**
|
|
3054
|
-
* Record
|
|
3055
|
-
*
|
|
3056
|
-
*
|
|
3057
|
-
* loses with a typed `funding_replayed` rather than crediting twice.
|
|
3058
|
-
*
|
|
3059
|
-
* Call it inside the same `tx` as {@link fund} — the rail commit, the
|
|
3060
|
-
* funding record, and the balance increment must land together or not at
|
|
3061
|
-
* all (the funding-commitment rule). Like `fund`, this issues no transaction
|
|
3062
|
-
* control on `tx`; it is a single statement.
|
|
3063
|
-
*
|
|
3064
|
-
* This does **not** move money on its own. `fund` still does the crediting;
|
|
3065
|
-
* 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.
|
|
3066
2675
|
*/
|
|
3067
2676
|
recordFunding(args: {
|
|
3068
2677
|
creditId: string;
|
|
@@ -3121,7 +2730,6 @@ declare class CreditLedger {
|
|
|
3121
2730
|
expiresAt: number;
|
|
3122
2731
|
nowMs?: number;
|
|
3123
2732
|
}): Promise<CreditInvoiceRecord>;
|
|
3124
|
-
/** Read one invoice (no lock). */
|
|
3125
2733
|
getInvoice(args: {
|
|
3126
2734
|
creditId: string;
|
|
3127
2735
|
fundId: string;
|
|
@@ -3137,30 +2745,17 @@ declare class CreditLedger {
|
|
|
3137
2745
|
limit: number;
|
|
3138
2746
|
}): Promise<CreditInvoiceRecord[]>;
|
|
3139
2747
|
/**
|
|
3140
|
-
*
|
|
3141
|
-
*
|
|
3142
|
-
*
|
|
3143
|
-
*
|
|
3144
|
-
* One transaction covers the funding record, the balance, and the invoice's
|
|
3145
|
-
* own status flip, so the three can never disagree. A crash anywhere inside
|
|
3146
|
-
* leaves the invoice `pending` and the balance untouched: the next
|
|
3147
|
-
* `lookup_invoice` observes the same settled payment and credits it, once.
|
|
3148
|
-
* A replayed check after the commit sees `settled` and moves nothing.
|
|
3149
|
-
*
|
|
3150
|
-
* The lock is on the **invoice** row, not the credit row: the first
|
|
3151
|
-
* Lightning top-up is what opens the credit, so there is frequently no
|
|
3152
|
-
* `credits` row to take `FOR UPDATE` yet. That does not weaken the ledger's
|
|
3153
|
-
* concurrency contract — no `credit_draws` write happens here, and `fund`'s
|
|
3154
|
-
* upsert is a single statement — while still serialising two settlement
|
|
3155
|
-
* 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.
|
|
3156
2752
|
*/
|
|
3157
2753
|
settleInvoice(args: {
|
|
3158
2754
|
creditId: string;
|
|
3159
2755
|
fundId: string;
|
|
3160
2756
|
expiryMs: number;
|
|
3161
2757
|
/**
|
|
3162
|
-
*
|
|
3163
|
-
* by — it is what `fund` writes to `credits.rail`.
|
|
2758
|
+
* Funding rail value pinned when this invoice was issued.
|
|
3164
2759
|
*/
|
|
3165
2760
|
basis: CreditFundingBasis;
|
|
3166
2761
|
/**
|
|
@@ -3184,39 +2779,20 @@ declare class CreditLedger {
|
|
|
3184
2779
|
fundId: string;
|
|
3185
2780
|
}): Promise<CreditInvoiceRecord | undefined>;
|
|
3186
2781
|
/**
|
|
3187
|
-
*
|
|
3188
|
-
*
|
|
3189
|
-
*
|
|
3190
|
-
* expired invoice was never paid and owes nobody anything, whereas a blocked
|
|
3191
|
-
* one has sats sitting in the builder's wallet with no ledger row to put them
|
|
3192
|
-
* on. Only an operator can resolve that, so the row records `reason` and
|
|
3193
|
-
* stops being swept — leaving it `pending` would spend one of the sweep's few
|
|
3194
|
-
* slots re-deriving the same verdict on every request the caller makes, and
|
|
3195
|
-
* three of them would wedge that caller's sweep so a genuinely payable
|
|
3196
|
-
* invoice behind them never credits.
|
|
3197
|
-
*
|
|
3198
|
-
* A CAS on `pending`, like the expiry retirement: a settlement observed
|
|
3199
|
-
* 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.
|
|
3200
2785
|
*/
|
|
3201
2786
|
markInvoiceBlocked(args: {
|
|
3202
2787
|
creditId: string;
|
|
3203
2788
|
fundId: string;
|
|
3204
2789
|
reason: string;
|
|
3205
2790
|
}): Promise<CreditInvoiceRecord | undefined>;
|
|
3206
|
-
/** Read one invoice by the wallet's own identifier for the payment (no lock). */
|
|
3207
2791
|
getInvoiceByPaymentHash(paymentHash: string): Promise<CreditInvoiceRecord | undefined>;
|
|
3208
2792
|
/**
|
|
3209
|
-
*
|
|
3210
|
-
*
|
|
3211
|
-
*
|
|
3212
|
-
* self-clear, so a row the operator declines to act on sits at the head of
|
|
3213
|
-
* the age ordering forever; a fixed first page would starve everything
|
|
3214
|
-
* behind it on every run, which is the shape one page up.
|
|
3215
|
-
*
|
|
3216
|
-
* `includeResolved` widens to the operator-resolved statuses so a run can be
|
|
3217
|
-
* audited after the fact.
|
|
3218
|
-
* Host-wide, like `listPendingDrains`: the ledger has no `dvm_id`, so on a
|
|
3219
|
-
* 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.
|
|
3220
2796
|
*/
|
|
3221
2797
|
listBlockedInvoices(args: {
|
|
3222
2798
|
limit: number;
|
|
@@ -3224,37 +2800,15 @@ declare class CreditLedger {
|
|
|
3224
2800
|
includeResolved?: boolean;
|
|
3225
2801
|
}): Promise<CreditInvoiceRecord[]>;
|
|
3226
2802
|
/**
|
|
3227
|
-
* Apply a blocked invoice
|
|
3228
|
-
*
|
|
3229
|
-
*
|
|
3230
|
-
*
|
|
3231
|
-
* same money doing the same thing a different way: one transaction covering
|
|
3232
|
-
* the funding record, the balance, the invoice's status flip and the deposit
|
|
3233
|
-
* 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.
|
|
3234
2807
|
*
|
|
3235
|
-
*
|
|
3236
|
-
*
|
|
3237
|
-
*
|
|
3238
|
-
*
|
|
3239
|
-
* the same order `settleInvoice` takes, which is what keeps a reconcile and
|
|
3240
|
-
* a concurrent settlement check from deadlocking against each other.
|
|
3241
|
-
* - **The funding lands at `(targetCreditId, paymentHash)`, not the invoice's
|
|
3242
|
-
* own `(credit_id, fund_id)`.** That key is frequently the reason the row
|
|
3243
|
-
* is blocked at all — `funding_replayed_on_cashu` means something else
|
|
3244
|
-
* already holds it — and the operator's most natural target is that very
|
|
3245
|
-
* credit. The payment hash cannot collide with it, and it makes all three
|
|
3246
|
-
* references to this payment agree: `basis.fundingRef`, the deposit's
|
|
3247
|
-
* `funding_id`, and the funding row's `fund_id`.
|
|
3248
|
-
* - **`written_off` is an accepted input status.** A write-off unwound
|
|
3249
|
-
* nothing, so an operator who closed a row by mistake must not need raw SQL
|
|
3250
|
-
* against a money table to reopen it.
|
|
3251
|
-
*
|
|
3252
|
-
* The invoice row's status — never the funding row — is the idempotency
|
|
3253
|
-
* source of truth. `fund_id` is caller-chosen and the payment hash is
|
|
3254
|
-
* disclosed to them, so a caller *can* squat `(theirCredit, thatHash)` with a
|
|
3255
|
-
* top-up on another rail; when they have, `recordFunding` throws
|
|
3256
|
-
* `funding_replayed` and that surfaces as its own loud failure rather than
|
|
3257
|
-
* 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.
|
|
3258
2812
|
*/
|
|
3259
2813
|
reconcileBlockedInvoice(args: {
|
|
3260
2814
|
paymentHash: string;
|
|
@@ -3272,17 +2826,9 @@ declare class CreditLedger {
|
|
|
3272
2826
|
nowMs?: number;
|
|
3273
2827
|
}): Promise<InvoiceReconciliation>;
|
|
3274
2828
|
/**
|
|
3275
|
-
*
|
|
3276
|
-
*
|
|
3277
|
-
*
|
|
3278
|
-
* A CAS on `blocked`, and idempotent: re-running returns the recorded row
|
|
3279
|
-
* rather than overwriting the first note. It cannot capture a `reconciled`
|
|
3280
|
-
* row, because that one moved money.
|
|
3281
|
-
*
|
|
3282
|
-
* `written` reports whether *this* call was the transition. The caller has
|
|
3283
|
-
* no other way to tell — the row reads identically either way — and the
|
|
3284
|
-
* write-off's audit log is the only record of the amount, so a re-emitted
|
|
3285
|
-
* 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.
|
|
3286
2832
|
*/
|
|
3287
2833
|
writeOffBlockedInvoice(args: {
|
|
3288
2834
|
paymentHash: string;
|
|
@@ -3297,14 +2843,12 @@ declare class CreditLedger {
|
|
|
3297
2843
|
getForCaller(callerPubkey: string, opts?: {
|
|
3298
2844
|
nowMs?: number;
|
|
3299
2845
|
}): Promise<CreditSnapshot[]>;
|
|
3300
|
-
/** Read one draw (no lock), with its credit's rail basis joined in. */
|
|
3301
2846
|
getDraw(args: {
|
|
3302
2847
|
creditId: string;
|
|
3303
2848
|
drawId: string;
|
|
3304
2849
|
}): Promise<DrawRecord | undefined>;
|
|
3305
2850
|
/** Read the sole draw bound to a pre-allocated job id; ambiguity fails closed. */
|
|
3306
2851
|
getDrawByJobId(jobId: string): Promise<DrawRecord | undefined>;
|
|
3307
|
-
/** Read every draw linked to a job, including already-resolved rows. */
|
|
3308
2852
|
listDrawsByJobId(jobId: string): Promise<DrawRecord[]>;
|
|
3309
2853
|
/**
|
|
3310
2854
|
* All pending holds on a credit, in `ledger_seq` order (no lock). Read
|
|
@@ -3313,22 +2857,10 @@ declare class CreditLedger {
|
|
|
3313
2857
|
*/
|
|
3314
2858
|
listPendingDraws(creditId: string): Promise<DrawRecord[]>;
|
|
3315
2859
|
/**
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3320
|
-
* resolve — the only release path keys off the `credit_id`/`draw_id` written
|
|
3321
|
-
* on the job row.
|
|
3322
|
-
*
|
|
3323
|
-
* Host-wide like {@link listPendingDrains}, not per-credit: the sweeper has
|
|
3324
|
-
* no candidate credit to start from. Age-bounded so it never sees a draw
|
|
3325
|
-
* whose job row is merely still in flight.
|
|
3326
|
-
*
|
|
3327
|
-
* `limit` is required and `after` keyset-paginates: an aged hold whose job
|
|
3328
|
-
* row *does* exist is legitimately un-sweepable and stays `pending`
|
|
3329
|
-
* indefinitely, so a caller that re-issues the same first page would never
|
|
3330
|
-
* see past a `limit`-sized wall of them. Pass the last row's
|
|
3331
|
-
* {@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.
|
|
3332
2864
|
*/
|
|
3333
2865
|
listStalePendingDraws(args: {
|
|
3334
2866
|
createdBeforeMs: number;
|
|
@@ -3336,38 +2868,18 @@ declare class CreditLedger {
|
|
|
3336
2868
|
after?: StalePendingDrawCursor;
|
|
3337
2869
|
}): Promise<DrawRecord[]>;
|
|
3338
2870
|
/**
|
|
3339
|
-
*
|
|
3340
|
-
*
|
|
3341
|
-
*
|
|
3342
|
-
* ceiling: a channel is claimable up to what its credit's draws
|
|
3343
|
-
* have earned, never up to the deposit that funded it.
|
|
3344
|
-
*
|
|
3345
|
-
* `undefined` when no credit is bound to the channel, which the claim job
|
|
3346
|
-
* treats as "cannot be resolved" and claims nothing for — distinct from a
|
|
3347
|
-
* resolved credit that has earned `0`.
|
|
3348
|
-
*
|
|
3349
|
-
* Deliberately a sum over settled draws rather than `funded − remaining`:
|
|
3350
|
-
* a drain lowers `native_remaining` without earning anything, so the
|
|
3351
|
-
* subtraction would read the caller's own reclaim as revenue. Pending holds
|
|
3352
|
-
* are excluded for the mirror reason — a hold is work in flight, and
|
|
3353
|
-
* 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.
|
|
3354
2874
|
*/
|
|
3355
2875
|
earnedNativeForX402Channel(channelId: string): Promise<number | undefined>;
|
|
3356
2876
|
/**
|
|
3357
|
-
*
|
|
3358
|
-
*
|
|
3359
|
-
*
|
|
3360
|
-
* The operator repair's entry point: a wedged settlement row carries the
|
|
3361
|
-
* channel and an `effect_id`, and this is what turns them back into the
|
|
3362
|
-
* credit id, the caller pubkey, and the drain id the effect encodes. Deriving
|
|
3363
|
-
* the credit id by splitting `effect_id` instead would be wrong — credit ids
|
|
3364
|
-
* 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.
|
|
3365
2879
|
*/
|
|
3366
2880
|
getCreditByX402Channel(channelId: string): Promise<CreditSnapshot | undefined>;
|
|
3367
|
-
/** Retire every active credit backed by a chain-proven lost Tempo channel. */
|
|
3368
2881
|
terminalizeTempoCredits(evidence: TempoCreditLossEvidence, tx?: CreditLedgerQuerier): Promise<TempoCreditLoss[]>;
|
|
3369
2882
|
private terminalizeTempoCreditsLocked;
|
|
3370
|
-
/** List terminal Tempo credit losses, newest observation first. */
|
|
3371
2883
|
listTempoCreditLosses(args?: {
|
|
3372
2884
|
limit?: number;
|
|
3373
2885
|
}): Promise<TempoCreditLoss[]>;
|
|
@@ -3382,36 +2894,21 @@ declare class CreditLedger {
|
|
|
3382
2894
|
*/
|
|
3383
2895
|
terminalizeX402Credit(evidence: X402CreditLossEvidence, tx?: CreditLedgerQuerier): Promise<X402CreditLoss[]>;
|
|
3384
2896
|
private terminalizeX402CreditLocked;
|
|
3385
|
-
/** List terminal x402 credit losses, newest observation first. */
|
|
3386
2897
|
listX402CreditLosses(args?: {
|
|
3387
2898
|
limit?: number;
|
|
3388
2899
|
}): Promise<X402CreditLoss[]>;
|
|
3389
2900
|
/**
|
|
3390
|
-
*
|
|
3391
|
-
*
|
|
3392
|
-
*
|
|
3393
|
-
*
|
|
3394
|
-
* is decremented in the same transaction, and the drain takes the next
|
|
3395
|
-
* gap-free `ledger_seq` — so the balance zeroes exactly once however many
|
|
3396
|
-
* machines race the request.
|
|
3397
|
-
*
|
|
3398
|
-
* Idempotent on `(creditId, drainId)`: a replay returns the recorded drain
|
|
3399
|
-
* (this is also the poll/pickup read). A replay whose `method` or `payout`
|
|
3400
|
-
* differs from the recorded drain is refused with `drain_conflict` —
|
|
3401
|
-
* silently honouring a changed payout would ship the caller's money to a
|
|
3402
|
-
* destination they never signed alongside this `drain_id`.
|
|
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.
|
|
3403
2905
|
*
|
|
3404
|
-
*
|
|
3405
|
-
*
|
|
3406
|
-
*
|
|
3407
|
-
* arithmetic, not a terminal credit state, so `status` stays `active`.
|
|
3408
|
-
*
|
|
3409
|
-
* Deliberately **no expiry check**: draining works before and after
|
|
3410
|
-
* 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.
|
|
3411
2909
|
*/
|
|
3412
2910
|
requestDrain(args: {
|
|
3413
2911
|
creditId: string;
|
|
3414
|
-
/** Client-generated drain id from the signed request body. */
|
|
3415
2912
|
drainId: string;
|
|
3416
2913
|
/** Verified caller pubkey — a drain may only be requested by the owner. */
|
|
3417
2914
|
callerPubkey: string;
|
|
@@ -3424,7 +2921,6 @@ declare class CreditLedger {
|
|
|
3424
2921
|
tx?: CreditLedgerQuerier;
|
|
3425
2922
|
nowMs?: number;
|
|
3426
2923
|
}): Promise<DrainRequestResult>;
|
|
3427
|
-
/** Read one drain (no lock), joined with its credit for currency + owner. */
|
|
3428
2924
|
getDrain(args: {
|
|
3429
2925
|
creditId: string;
|
|
3430
2926
|
drainId: string;
|
|
@@ -3436,25 +2932,11 @@ declare class CreditLedger {
|
|
|
3436
2932
|
*/
|
|
3437
2933
|
listFundingLots(creditId: string): Promise<FundingLot[]>;
|
|
3438
2934
|
/**
|
|
3439
|
-
*
|
|
3440
|
-
*
|
|
3441
|
-
*
|
|
3442
|
-
*
|
|
3443
|
-
*
|
|
3444
|
-
* exchange-rate lookup anywhere: the obligation is in kind, so a rate has
|
|
3445
|
-
* nothing to say about it. Pending job holds are deliberately **not**
|
|
3446
|
-
* subtracted — a hold either settles into revenue or releases back to the
|
|
3447
|
-
* caller, and until it does, treating it as already earned would understate
|
|
3448
|
-
* the floor. The figure is conservative by exactly that amount.
|
|
3449
|
-
*
|
|
3450
|
-
* The two coverage holes ride along rather than being folded in silently, so
|
|
3451
|
-
* a gap reads as a gap: `uncovered` is a credit whose lots fall short of its
|
|
3452
|
-
* balance, `unbacked` is one holding any open micro with no sats basis. A
|
|
3453
|
-
* reclaim on either prices the **whole** balance off a live rate — the
|
|
3454
|
-
* depletion refuses a mixture rather than blending two bases — so both are
|
|
3455
|
-
* reported as the whole balance, and neither contributes to `depositSats`.
|
|
3456
|
-
* A figure that counted the backed half of a mixed credit would be a floor
|
|
3457
|
-
* 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.
|
|
3458
2940
|
*/
|
|
3459
2941
|
bitcoinDepositLiability(): Promise<BitcoinDepositLiability>;
|
|
3460
2942
|
/**
|
|
@@ -3467,40 +2949,15 @@ declare class CreditLedger {
|
|
|
3467
2949
|
*/
|
|
3468
2950
|
listPendingDrains(): Promise<CreditDrainRecord[]>;
|
|
3469
2951
|
/**
|
|
3470
|
-
*
|
|
3471
|
-
*
|
|
3472
|
-
*
|
|
3473
|
-
*
|
|
3474
|
-
* never self-clears, so a caller that re-issues the same first page would
|
|
3475
|
-
* never see past a `limit`-sized wall of them. The keyset is
|
|
3476
|
-
* `(created_at, credit_id, drain_id)` — `created_at` is immutable, so no
|
|
3477
|
-
* concurrent write can push an unexamined row past the cursor. It is a
|
|
3478
|
-
* BIGINT of epoch milliseconds rather than a `TIMESTAMPTZ`, so the
|
|
3479
|
-
* microsecond-vs-millisecond cursor trap that bit `listSettlements` cannot
|
|
3480
|
-
* arise here: the value round-trips as the integer it already is.
|
|
3481
|
-
*
|
|
3482
|
-
* `createdBeforeMs` is the staleness floor. A healthy cooperative close
|
|
3483
|
-
* moves the drain to `sent` inside the request that opened it, so anything
|
|
3484
|
-
* younger is in flight rather than stuck.
|
|
3485
|
-
*
|
|
3486
|
-
* The method filter is `'tempo'`, and it is the one line here worth a second
|
|
3487
|
-
* look: `init()` migrates the legacy `'mpp'` spelling away and
|
|
3488
|
-
* `DrainMethod` no longer carries it, so a query naming it matches nothing a
|
|
3489
|
-
* DVM has ever written. It named it anyway until rename sweep and
|
|
3490
|
-
* live rung caught it independently — the whole operator queue
|
|
3491
|
-
* read empty in production while every route test stayed green, because
|
|
3492
|
-
* those run on `MemoryCreditLedger`, which filters on the typed value. The
|
|
3493
|
-
* two implementations of this one predicate must be read together.
|
|
3494
|
-
*
|
|
3495
|
-
* A written-off row is excluded unless `includeResolved`: the operator has
|
|
3496
|
-
* already decided about it, and leaving it on the live queue would re-serve
|
|
3497
|
-
* 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.
|
|
3498
2956
|
*/
|
|
3499
2957
|
listChannelDrains(args: {
|
|
3500
2958
|
limit: number;
|
|
3501
2959
|
after?: ChannelDrainCursor;
|
|
3502
2960
|
createdBeforeMs: number;
|
|
3503
|
-
/** Which channel binding to walk. Today only Tempo has a repair. */
|
|
3504
2961
|
rail: "tempo";
|
|
3505
2962
|
includeResolved?: boolean;
|
|
3506
2963
|
}): Promise<CreditDrainRecord[]>;
|
|
@@ -3606,11 +3063,9 @@ declare class CreditLedger {
|
|
|
3606
3063
|
nowMs?: number;
|
|
3607
3064
|
}): Promise<DrainTransitionResult>;
|
|
3608
3065
|
/**
|
|
3609
|
-
* Append
|
|
3610
|
-
*
|
|
3611
|
-
*
|
|
3612
|
-
* transition is the source of truth; the receipt is the proof the caller
|
|
3613
|
-
* 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.
|
|
3614
3069
|
*/
|
|
3615
3070
|
appendDrainReceipt(args: {
|
|
3616
3071
|
creditId: string;
|
|
@@ -3619,31 +3074,13 @@ declare class CreditLedger {
|
|
|
3619
3074
|
tx?: CreditLedgerQuerier;
|
|
3620
3075
|
}): Promise<void>;
|
|
3621
3076
|
private readDrain;
|
|
3622
|
-
/** Re-read the credit join fields for a freshly-updated drain row. */
|
|
3623
3077
|
private rejoin;
|
|
3624
|
-
/** Shared refusal tail for the CAS transitions: idempotent replay or typed error. */
|
|
3625
3078
|
private refuseTransition;
|
|
3626
3079
|
/**
|
|
3627
|
-
*
|
|
3628
|
-
*
|
|
3629
|
-
*
|
|
3630
|
-
*
|
|
3631
|
-
* lots have to shrink with it or the deposit-liability rollup would keep
|
|
3632
|
-
* counting a balance that is gone.
|
|
3633
|
-
*
|
|
3634
|
-
* `owedSats` is `null` where {@link isInKindDepletion} refuses — the lots
|
|
3635
|
-
* are short of the balance, or the covering lots carry no sats basis (a
|
|
3636
|
-
* legacy credit whose funding recorded no sats basis. That row is priced off a live rate by the
|
|
3637
|
-
* admin surface instead, which is what *every* row did before this change,
|
|
3638
|
-
* so the fallback is the old behaviour rather than a new failure mode.
|
|
3639
|
-
*
|
|
3640
|
-
* What it publishes is **net of the delivery reserve**: handing a
|
|
3641
|
-
* refund over costs a mint fee and, when the notes have to be minted just in
|
|
3642
|
-
* time, a Lightning hop. The caller carries that cost per and a
|
|
3643
|
-
* flat reserve is how they carry it without the figure moving — an
|
|
3644
|
-
* actual-fee true-up could only be applied after the promise was made. The
|
|
3645
|
-
* debits stay gross because they are denominated in micro and are what
|
|
3646
|
-
* `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.
|
|
3647
3084
|
*/
|
|
3648
3085
|
private reclaimInKind;
|
|
3649
3086
|
/**
|
|
@@ -3663,7 +3100,6 @@ declare class CreditLedger {
|
|
|
3663
3100
|
* as every other balance move is.
|
|
3664
3101
|
*/
|
|
3665
3102
|
private applyLotDebits;
|
|
3666
|
-
/** Shared settle/release state machine — see the public JSDoc on each verb. */
|
|
3667
3103
|
private resolveDraw;
|
|
3668
3104
|
private resolveDrawLocked;
|
|
3669
3105
|
/**
|
|
@@ -3672,7 +3108,6 @@ declare class CreditLedger {
|
|
|
3672
3108
|
* success (harmless for read-only replay paths), rolls back on throw.
|
|
3673
3109
|
*/
|
|
3674
3110
|
private withCreditLock;
|
|
3675
|
-
/** `SELECT … FOR UPDATE` the credit row on `q` (which must be inside a transaction). */
|
|
3676
3111
|
private lockCreditRow;
|
|
3677
3112
|
private readDraw;
|
|
3678
3113
|
/**
|
|
@@ -3687,13 +3122,9 @@ declare class CreditLedger {
|
|
|
3687
3122
|
private readX402CreditLosses;
|
|
3688
3123
|
}
|
|
3689
3124
|
/**
|
|
3690
|
-
*
|
|
3691
|
-
*
|
|
3692
|
-
* the
|
|
3693
|
-
* tests / pool-less `createDVMServer` setups, mirroring the `MemoryJobStore`
|
|
3694
|
-
* precedent). The `tx` handles on `fund`/`draw` are meaningful only on the
|
|
3695
|
-
* Postgres implementation; the memory ledger ignores them — its operations
|
|
3696
|
-
* 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.
|
|
3697
3128
|
*/
|
|
3698
3129
|
interface CreditLedgerLike {
|
|
3699
3130
|
/**
|
|
@@ -3882,9 +3313,7 @@ interface CreditLedgerLike {
|
|
|
3882
3313
|
creditId: string;
|
|
3883
3314
|
drawId: string;
|
|
3884
3315
|
}): Promise<DrawRecord | undefined>;
|
|
3885
|
-
/** Read the sole draw linked to a stable pre-payment job id. */
|
|
3886
3316
|
getDrawByJobId(jobId: string): Promise<DrawRecord | undefined>;
|
|
3887
|
-
/** Read every draw linked to a job so retention can resolve all holds before redaction. */
|
|
3888
3317
|
listDrawsByJobId(jobId: string): Promise<DrawRecord[]>;
|
|
3889
3318
|
listPendingDraws(creditId: string): Promise<DrawRecord[]>;
|
|
3890
3319
|
listStalePendingDraws(args: {
|
|
@@ -3919,7 +3348,6 @@ interface CreditLedgerLike {
|
|
|
3919
3348
|
listPendingDrains(): Promise<CreditDrainRecord[]>;
|
|
3920
3349
|
/** See {@link CreditLedger.bitcoinDepositLiability} — the in-kind deposit floor. */
|
|
3921
3350
|
bitcoinDepositLiability(): Promise<BitcoinDepositLiability>;
|
|
3922
|
-
/** One credit's undepleted funding lots, oldest first. */
|
|
3923
3351
|
listFundingLots(creditId: string): Promise<FundingLot[]>;
|
|
3924
3352
|
listChannelDrains(args: {
|
|
3925
3353
|
limit: number;
|
|
@@ -3981,14 +3409,12 @@ interface CreditLedgerLike {
|
|
|
3981
3409
|
interface CreditLedgerQuerier {
|
|
3982
3410
|
query: Pool["query"];
|
|
3983
3411
|
}
|
|
3984
|
-
/** Reporter outbox joined to a draw release's own transaction. */
|
|
3985
3412
|
interface CreditDrawReleaseOutbox {
|
|
3986
3413
|
/** Platform DVM record ID bound into the report. */
|
|
3987
3414
|
dvmId: string;
|
|
3988
3415
|
/** Reporter-owned insert executed through the ledger transaction. */
|
|
3989
3416
|
enqueue: CreditDrawReleaseEnqueue;
|
|
3990
3417
|
}
|
|
3991
|
-
/** Reporter seam for credit-expiry releases and the revivals that reverse them. */
|
|
3992
3418
|
interface CreditExpiryReleaseOutbox {
|
|
3993
3419
|
/** Platform DVM record ID bound into the report. */
|
|
3994
3420
|
dvmId: string;
|
|
@@ -3996,16 +3422,15 @@ interface CreditExpiryReleaseOutbox {
|
|
|
3996
3422
|
enqueue: CreditExpiryReleaseEnqueue;
|
|
3997
3423
|
}
|
|
3998
3424
|
/**
|
|
3999
|
-
* One
|
|
4000
|
-
*
|
|
4001
|
-
*
|
|
4002
|
-
* `reversedAt` is set when a later funding revived the credit, at which point
|
|
4003
|
-
* the release stops counting against committed value — the row stays as the
|
|
4004
|
-
* 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.
|
|
4005
3428
|
*/
|
|
4006
3429
|
interface CreditExpiryRelease {
|
|
4007
3430
|
creditId: string;
|
|
4008
|
-
/**
|
|
3431
|
+
/**
|
|
3432
|
+
* Key derived from expiry instant and the release sequence counted under the credit lock.
|
|
3433
|
+
*/
|
|
4009
3434
|
releaseId: string;
|
|
4010
3435
|
callerPubkey: string;
|
|
4011
3436
|
currency: string;
|
|
@@ -4013,31 +3438,29 @@ interface CreditExpiryRelease {
|
|
|
4013
3438
|
rail: string | null;
|
|
4014
3439
|
/** Fiat micro released, 1e-6 of `currency`. */
|
|
4015
3440
|
amountMicro: number;
|
|
4016
|
-
/**
|
|
3441
|
+
/**
|
|
3442
|
+
* Nominal credit expiry, ms since epoch; the claim is released only when the sweep commits.
|
|
3443
|
+
*/
|
|
4017
3444
|
expiryMs: number;
|
|
4018
|
-
/**
|
|
3445
|
+
/**
|
|
3446
|
+
* When the sweep recorded this release, ms since epoch.
|
|
3447
|
+
*/
|
|
4019
3448
|
releasedAt: number;
|
|
4020
3449
|
/** When a funding revived the credit and undid this release. */
|
|
4021
3450
|
reversedAt: number | null;
|
|
4022
3451
|
}
|
|
4023
|
-
/** Pool-shaped handle (`query` + `connect`). Required for the locked write paths. */
|
|
4024
3452
|
type CreditLedgerPool = Pick<Pool, "query" | "connect">;
|
|
4025
3453
|
/**
|
|
4026
|
-
*
|
|
4027
|
-
*
|
|
4028
|
-
*
|
|
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.
|
|
4029
3457
|
*/
|
|
4030
3458
|
type CreditStatus = "active" | "unbacked";
|
|
4031
3459
|
/** Draw lifecycle: two-phase hold → terminal settle/release. */
|
|
4032
3460
|
type DrawStatus = "pending" | "settled" | "released";
|
|
4033
3461
|
/**
|
|
4034
|
-
*
|
|
4035
|
-
*
|
|
4036
|
-
* Direct Lightning payout is deliberately absent: a
|
|
4037
|
-
* first-party deployment holds no send-capable Lightning credential by design
|
|
4038
|
-
* (the funding NWC is receive-only), so P2PK-locked Cashu notes are
|
|
4039
|
-
* the only non-channel Bitcoin reclaim path. Adding `lightning` back means
|
|
4040
|
-
* 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.
|
|
4041
3464
|
*/
|
|
4042
3465
|
type DrainMethod = "cashu" | "x402" | "tempo";
|
|
4043
3466
|
/**
|
|
@@ -4076,12 +3499,20 @@ interface BitcoinDepositLiability {
|
|
|
4076
3499
|
}[];
|
|
4077
3500
|
/** Credits holding balance no funding lot backs — a defect, not a liability figure. */
|
|
4078
3501
|
uncoveredCredits: number;
|
|
4079
|
-
/**
|
|
4080
|
-
|
|
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
|
+
}[];
|
|
4081
3508
|
/** Credits holding any open micro with no sats basis, so their reclaim prices off a live rate. */
|
|
4082
3509
|
unbackedCredits: number;
|
|
4083
|
-
/**
|
|
4084
|
-
|
|
3510
|
+
/** Whole balances of basisless credits, by ledger currency. */
|
|
3511
|
+
unbackedByCurrency: {
|
|
3512
|
+
currency: string;
|
|
3513
|
+
micro: number;
|
|
3514
|
+
credits: number;
|
|
3515
|
+
}[];
|
|
4085
3516
|
}
|
|
4086
3517
|
/**
|
|
4087
3518
|
* What it cost to put a just-in-time-minted refund in the caller's hands
|
|
@@ -4119,16 +3550,9 @@ interface CreditDrainRecord {
|
|
|
4119
3550
|
currency: string;
|
|
4120
3551
|
method: DrainMethod;
|
|
4121
3552
|
/**
|
|
4122
|
-
*
|
|
4123
|
-
*
|
|
4124
|
-
*
|
|
4125
|
-
* The method alone does not say who settles a reclaim: an `mpp` or `x402`
|
|
4126
|
-
* drain on a channel-bound credit is closed by the channel itself, and a
|
|
4127
|
-
* `pending` one is routinely mid-reconciliation rather than owed by anybody
|
|
4128
|
-
* `completeTempoDrain` leaves every ambiguous close pending on purpose for
|
|
4129
|
-
* `recoverTempoSessionClose` to finish on the caller's next poll. Carrying
|
|
4130
|
-
* the binding on the record is what lets a reader tell that apart from a
|
|
4131
|
-
* one-payment reclaim only a person can settle.
|
|
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.
|
|
4132
3556
|
*/
|
|
4133
3557
|
channelId: string | null;
|
|
4134
3558
|
/** Method-specific payout target as signed by the caller. */
|
|
@@ -4143,12 +3567,11 @@ interface CreditDrainRecord {
|
|
|
4143
3567
|
*/
|
|
4144
3568
|
drainedNative: number | null;
|
|
4145
3569
|
/**
|
|
4146
|
-
*
|
|
4147
|
-
*
|
|
4148
|
-
*
|
|
4149
|
-
*
|
|
4150
|
-
*
|
|
4151
|
-
* 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.
|
|
4152
3575
|
*/
|
|
4153
3576
|
owedSats: number | null;
|
|
4154
3577
|
/**
|
|
@@ -4174,7 +3597,10 @@ interface CreditDrainRecord {
|
|
|
4174
3597
|
* already holding.
|
|
4175
3598
|
*/
|
|
4176
3599
|
fulfilment: DrainFulfilment | null;
|
|
4177
|
-
/**
|
|
3600
|
+
/**
|
|
3601
|
+
* Opaque supplied reclaim-event observations in append order; consumers shape-check and
|
|
3602
|
+
* authenticate them.
|
|
3603
|
+
*/
|
|
4178
3604
|
receipts: Record<string, unknown>[];
|
|
4179
3605
|
createdAt: number;
|
|
4180
3606
|
parkedAt: number | null;
|
|
@@ -4245,9 +3671,8 @@ interface DrainRequestResult {
|
|
|
4245
3671
|
replayed: boolean;
|
|
4246
3672
|
}
|
|
4247
3673
|
/**
|
|
4248
|
-
* Read
|
|
4249
|
-
*
|
|
4250
|
-
* 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.
|
|
4251
3676
|
*/
|
|
4252
3677
|
interface CreditSnapshot {
|
|
4253
3678
|
creditId: string;
|
|
@@ -4255,7 +3680,9 @@ interface CreditSnapshot {
|
|
|
4255
3680
|
currency: string;
|
|
4256
3681
|
/** Funded balance, 1e-6 currency units. Pending holds are NOT subtracted. */
|
|
4257
3682
|
balanceMicro: number;
|
|
4258
|
-
/**
|
|
3683
|
+
/**
|
|
3684
|
+
* Balance minus pending holds, or zero while backing is unbacked.
|
|
3685
|
+
*/
|
|
4259
3686
|
availableMicro: number;
|
|
4260
3687
|
/**
|
|
4261
3688
|
* `availableMicro` in the funding rail's own units — the ceiling any
|
|
@@ -4266,7 +3693,9 @@ interface CreditSnapshot {
|
|
|
4266
3693
|
expiryMs: number;
|
|
4267
3694
|
expired: boolean;
|
|
4268
3695
|
status: CreditStatus;
|
|
4269
|
-
/**
|
|
3696
|
+
/**
|
|
3697
|
+
* High-water sequence shared by draw placements and drains.
|
|
3698
|
+
*/
|
|
4270
3699
|
lastLedgerSeq: number;
|
|
4271
3700
|
createdAt: number;
|
|
4272
3701
|
/**
|
|
@@ -4281,19 +3710,17 @@ interface CreditSnapshot {
|
|
|
4281
3710
|
/** Reusable x402 channel bound to this credit, if one funded it. */
|
|
4282
3711
|
x402ChannelId: string | null;
|
|
4283
3712
|
}
|
|
4284
|
-
/**
|
|
4285
|
-
* Result of a draw — carries the receipt tuple the countersignature
|
|
4286
|
-
* consumes (`ReceiptCredit`: `draw_id`, `amount`, `balance_after`,
|
|
4287
|
-
* `ledger_seq`). `balanceAfterMicro` and `ledgerSeq` are the values recorded
|
|
4288
|
-
* when the draw was first placed; a replay returns them unchanged.
|
|
4289
|
-
*/
|
|
4290
3713
|
interface DrawResult {
|
|
4291
3714
|
creditId: string;
|
|
4292
3715
|
drawId: string;
|
|
4293
3716
|
amountMicro: number;
|
|
4294
|
-
/**
|
|
3717
|
+
/**
|
|
3718
|
+
* Recorded balance after placement or latest growth, at the draw’s original sequence.
|
|
3719
|
+
*/
|
|
4295
3720
|
balanceAfterMicro: number;
|
|
4296
|
-
/**
|
|
3721
|
+
/**
|
|
3722
|
+
* Per-credit gap-free sequence shared with drains; growth keeps the original value.
|
|
3723
|
+
*/
|
|
4297
3724
|
ledgerSeq: number;
|
|
4298
3725
|
status: DrawStatus;
|
|
4299
3726
|
/** True when this call was an idempotent replay of an already-recorded state. */
|
|
@@ -4304,14 +3731,9 @@ interface DrawResult {
|
|
|
4304
3731
|
resolvedAt: number | null;
|
|
4305
3732
|
}
|
|
4306
3733
|
/**
|
|
4307
|
-
*
|
|
4308
|
-
*
|
|
4309
|
-
*
|
|
4310
|
-
* Allocated at draw time as `remaining × amountMicro / availableMicro`, where
|
|
4311
|
-
* both remainders are net of pending holds. The draw that consumes a credit's
|
|
4312
|
-
* last micro therefore takes ratio 1 and the exact remainder, so the draws of
|
|
4313
|
-
* a credit sum to precisely the value funded — no rounding residue, and no
|
|
4314
|
-
* 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.
|
|
4315
3737
|
*/
|
|
4316
3738
|
interface DrawRailValue {
|
|
4317
3739
|
/** Millisatoshis of rail value allocated to this draw. */
|
|
@@ -4364,7 +3786,6 @@ interface DrawResolution extends DrawResult {
|
|
|
4364
3786
|
/** Settlement instant — the timestamp a draw-keyed revenue event is dated by. */
|
|
4365
3787
|
resolvedAt: number;
|
|
4366
3788
|
}
|
|
4367
|
-
/** Full persisted draw row, camel-cased. */
|
|
4368
3789
|
interface DrawRecord extends Omit<DrawResult, "replayed"> {
|
|
4369
3790
|
jobId: string | null;
|
|
4370
3791
|
createdAt: number;
|
|
@@ -4434,9 +3855,11 @@ interface CreditFundingBasis {
|
|
|
4434
3855
|
maxClaimableAmount: string;
|
|
4435
3856
|
};
|
|
4436
3857
|
}
|
|
4437
|
-
/** What {@link CreditLedger.fund} takes. Named so the private body can share the shape. */
|
|
4438
3858
|
interface CreditFundArgs {
|
|
4439
|
-
/**
|
|
3859
|
+
/**
|
|
3860
|
+
* Omit to mint a credit ID; provide to add funding to that credit. Replay prevention is the
|
|
3861
|
+
* caller’s responsibility.
|
|
3862
|
+
*/
|
|
4440
3863
|
creditId?: string;
|
|
4441
3864
|
callerPubkey: string;
|
|
4442
3865
|
/** The DVM's pricing currency; `amountMicro` is 1e-6 of it. */
|
|
@@ -4444,9 +3867,13 @@ interface CreditFundArgs {
|
|
|
4444
3867
|
amountMicro: number;
|
|
4445
3868
|
/** Absolute expiry, ms since epoch. Overwrites the previous value on top-up. */
|
|
4446
3869
|
expiryMs: number;
|
|
4447
|
-
/**
|
|
3870
|
+
/**
|
|
3871
|
+
* Rail value contributed by this funding; required on writes.
|
|
3872
|
+
*/
|
|
4448
3873
|
basis: CreditFundingBasis;
|
|
4449
|
-
/**
|
|
3874
|
+
/**
|
|
3875
|
+
* External transaction handle; caller owns BEGIN, COMMIT and ROLLBACK.
|
|
3876
|
+
*/
|
|
4450
3877
|
tx?: CreditLedgerQuerier;
|
|
4451
3878
|
nowMs?: number;
|
|
4452
3879
|
}
|
|
@@ -4466,7 +3893,6 @@ declare function allocateDrawValue(args: {
|
|
|
4466
3893
|
drawMsats: number;
|
|
4467
3894
|
drawNative: number | null;
|
|
4468
3895
|
};
|
|
4469
|
-
/** Full persisted `credit_fundings` row, camel-cased. */
|
|
4470
3896
|
interface FundingRecord {
|
|
4471
3897
|
creditId: string;
|
|
4472
3898
|
fundId: string;
|
|
@@ -4498,7 +3924,6 @@ interface FundingRecord {
|
|
|
4498
3924
|
* human had to intervene.
|
|
4499
3925
|
*/
|
|
4500
3926
|
type CreditInvoiceStatus = "pending" | "settled" | "expired" | "blocked" | "reconciled" | "written_off";
|
|
4501
|
-
/** Full persisted `credit_invoices` row, camel-cased. */
|
|
4502
3927
|
interface CreditInvoiceRecord {
|
|
4503
3928
|
creditId: string;
|
|
4504
3929
|
fundId: string;
|
|
@@ -4531,7 +3956,6 @@ interface CreditInvoiceRecord {
|
|
|
4531
3956
|
/** When the payment landed at the wallet — never the instant an operator acted on it. */
|
|
4532
3957
|
settledAt: number | null;
|
|
4533
3958
|
}
|
|
4534
|
-
/** Outcome of applying an observed settlement to the ledger. */
|
|
4535
3959
|
interface InvoiceSettlement {
|
|
4536
3960
|
invoice: CreditInvoiceRecord;
|
|
4537
3961
|
/** The `credit_fundings` row; absent only if a legacy row somehow lacks one. */
|
|
@@ -4541,10 +3965,8 @@ interface InvoiceSettlement {
|
|
|
4541
3965
|
replayed: boolean;
|
|
4542
3966
|
}
|
|
4543
3967
|
/**
|
|
4544
|
-
*
|
|
4545
|
-
*
|
|
4546
|
-
* `credit` describe the **target** credit, not the one the invoice was minted
|
|
4547
|
-
* 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.
|
|
4548
3970
|
*/
|
|
4549
3971
|
interface InvoiceReconciliation {
|
|
4550
3972
|
invoice: CreditInvoiceRecord;
|
|
@@ -4553,7 +3975,6 @@ interface InvoiceReconciliation {
|
|
|
4553
3975
|
/** True when this reconcile had already been applied — nothing moved. */
|
|
4554
3976
|
replayed: boolean;
|
|
4555
3977
|
}
|
|
4556
|
-
/** Outcome of `writeOffBlockedInvoice` — the recorded row, plus whether this call wrote it. */
|
|
4557
3978
|
interface InvoiceWriteOff {
|
|
4558
3979
|
invoice: CreditInvoiceRecord;
|
|
4559
3980
|
/** False when the invoice was already written off, so nothing changed. */
|
|
@@ -4565,31 +3986,13 @@ interface BlockedInvoiceCursor {
|
|
|
4565
3986
|
creditId: string;
|
|
4566
3987
|
fundId: string;
|
|
4567
3988
|
}
|
|
4568
|
-
/** Typed failure codes for {@link CreditLedgerError}. */
|
|
4569
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";
|
|
4570
3990
|
/**
|
|
4571
|
-
*
|
|
4572
|
-
*
|
|
4573
|
-
*
|
|
4574
|
-
* booked; the copy a caller relays to a human differs entirely between them.
|
|
4575
|
-
* A tag rather than a message substring because that is what the copy used to
|
|
4576
|
-
* key on, and both of the x402 commit leg's conflicts missed it by a word —
|
|
4577
|
-
* silently, since the fallback is a well-formed answer about a different
|
|
4578
|
-
* refusal.
|
|
4579
|
-
*
|
|
4580
|
-
* The three are not one shape with three causes. `pending_draws` and
|
|
4581
|
-
* `balance_moved` roll back with nothing on any chain and clear on their own;
|
|
4582
|
-
* `refund_floor` is the opposite — the cooperative refund reached the chain and
|
|
4583
|
-
* paid short, so the caller's money moved and only the operator repair resolves
|
|
4584
|
-
* it. A route's `retryable` and `drain_registered` follow from which
|
|
4585
|
-
* one it is, so the tag has to reach it.
|
|
4586
|
-
*
|
|
4587
|
-
* Absent means the duplicate-payout conflict `PAYMENT_ERROR_COPY.drain_conflict`
|
|
4588
|
-
* was written for, which is also the conservative default for a conflict nobody
|
|
4589
|
-
* 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.
|
|
4590
3994
|
*/
|
|
4591
3995
|
type DrainConflictReason = "pending_draws" | "balance_moved" | "refund_floor";
|
|
4592
|
-
/** Structured context on a {@link CreditLedgerError}, per code. */
|
|
4593
3996
|
interface CreditLedgerErrorDetails {
|
|
4594
3997
|
/** On `drain_conflict`: which conflict it is, so a route can answer it without matching on prose. */
|
|
4595
3998
|
drainConflict?: DrainConflictReason;
|
|
@@ -4689,7 +4092,8 @@ interface TempoSessionLifecycle {
|
|
|
4689
4092
|
*
|
|
4690
4093
|
* Every mutation takes an advisory transaction lock keyed by the channel
|
|
4691
4094
|
* before locking its row with `FOR UPDATE`, applies mppx's synchronous
|
|
4692
|
-
* 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
|
|
4693
4097
|
* covers a channel's first write, when there is no row for `FOR UPDATE` to
|
|
4694
4098
|
* lock. Together these are the cross-machine linearization point for voucher,
|
|
4695
4099
|
* top-up, spend, and close state; mppx's process-local fallback is
|
|
@@ -4717,13 +4121,10 @@ declare class PostgresTempoSessionStore implements Store.AtomicStore {
|
|
|
4717
4121
|
private readonly pool;
|
|
4718
4122
|
private readonly acceptance;
|
|
4719
4123
|
constructor(pool: Pool);
|
|
4720
|
-
/** Create the channel table under the SDK-wide migration lock. */
|
|
4721
4124
|
init(): Promise<void>;
|
|
4722
|
-
/** Read one channel snapshot. */
|
|
4723
4125
|
get(key: string): Promise<unknown>;
|
|
4724
4126
|
/** Replace one channel snapshot. Used by mppx only for non-RMW maintenance. */
|
|
4725
4127
|
put(key: string, value: unknown): Promise<void>;
|
|
4726
|
-
/** Delete one channel snapshot. */
|
|
4727
4128
|
delete(key: string): Promise<void>;
|
|
4728
4129
|
/** Atomic read-modify-write using a row lock shared by every SDK replica. */
|
|
4729
4130
|
update<result>(key: string, fn: (current: unknown) => Store.Change<unknown, result>): Promise<result>;
|
|
@@ -5103,7 +4504,6 @@ declare const _testing: {
|
|
|
5103
4504
|
attachTempoSessionRuntime: typeof attachTempoSessionRuntime;
|
|
5104
4505
|
};
|
|
5105
4506
|
|
|
5106
|
-
/** Envelope fields the verifier validates around the signed payload. */
|
|
5107
4507
|
interface CanonicalEnvelope {
|
|
5108
4508
|
pubkey: string;
|
|
5109
4509
|
signature: string;
|
|
@@ -5115,21 +4515,20 @@ interface CreateSignedRequestVerifierOpts {
|
|
|
5115
4515
|
/** Fixed audience/operation for a custom signed route or standalone verifier. */
|
|
5116
4516
|
domain?: SignedRequestDomain;
|
|
5117
4517
|
/**
|
|
5118
|
-
* Permitted clock drift in seconds
|
|
5119
|
-
* cast's `SIGNATURE_DRIFT_SECONDS`).
|
|
4518
|
+
* Permitted clock drift in seconds; default 300.
|
|
5120
4519
|
*/
|
|
5121
4520
|
driftSeconds?: number;
|
|
5122
4521
|
/**
|
|
5123
|
-
*
|
|
5124
|
-
*
|
|
5125
|
-
* 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.
|
|
5126
4524
|
*/
|
|
5127
4525
|
envelopeFields?: string[];
|
|
5128
4526
|
/**
|
|
5129
4527
|
* Field names removed from the input before `schema` validates it — and
|
|
5130
4528
|
* **kept** in the canonical bytes the signature covers. For protocol-reserved
|
|
5131
4529
|
* keys the caller signs but the schema never declares: the credit
|
|
5132
|
-
* envelope riding a capability schema.
|
|
4530
|
+
* envelope riding a capability schema. No additional fields are ignored by default;
|
|
4531
|
+
* auth_statement is always excluded from schema validation.
|
|
5133
4532
|
*
|
|
5134
4533
|
* This is the one place where "what is validated" and "what is signed"
|
|
5135
4534
|
* legitimately differ. Don't reach for it to paper over a schema mismatch —
|
|
@@ -5144,7 +4543,6 @@ interface CreateSignedRequestVerifierOpts {
|
|
|
5144
4543
|
/** Current Unix-seconds source. Defaults to `() => Math.floor(Date.now() / 1000)`. */
|
|
5145
4544
|
now?: () => number;
|
|
5146
4545
|
}
|
|
5147
|
-
/** Minimal replay-store contract — same shape as cast's `ReplayStore`. */
|
|
5148
4546
|
interface SignedRequestReplayStore {
|
|
5149
4547
|
/**
|
|
5150
4548
|
* True iff (pubkey, timestamp, nonce) was already seen within the retention
|
|
@@ -5161,20 +4559,9 @@ interface SignedRequestSignOpts {
|
|
|
5161
4559
|
nonce?: string;
|
|
5162
4560
|
}
|
|
5163
4561
|
/**
|
|
5164
|
-
*
|
|
5165
|
-
*
|
|
5166
|
-
*
|
|
5167
|
-
* created, not on every request entry:
|
|
5168
|
-
*
|
|
5169
|
-
* 1. {@link checkAuth} runs schema → drift → signature at request entry on
|
|
5170
|
-
* both `/v1/quote` and `/v1/job`. Sync; no replay-store touch.
|
|
5171
|
-
* 2. {@link recordReplay} commits `(pubkey, timestamp, nonce)` to the replay
|
|
5172
|
-
* store. Called from `/v1/job` only, after upfront payment is verified
|
|
5173
|
-
* and immediately before `jobManager.createJob`.
|
|
5174
|
-
*
|
|
5175
|
-
* The unpaid `/v1/job` warmup probe (price discovery → 402) thus no longer
|
|
5176
|
-
* burns the nonce — clients can re-submit the same envelope with a real
|
|
5177
|
-
* 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.
|
|
5178
4565
|
*/
|
|
5179
4566
|
interface SignedRequestVerifier<T> {
|
|
5180
4567
|
/**
|
|
@@ -5208,20 +4595,11 @@ interface SignedRequestVerifier<T> {
|
|
|
5208
4595
|
*/
|
|
5209
4596
|
signRequest(privkey: Uint8Array, body: Record<string, unknown>, opts?: SignedRequestSignOpts, domain?: SignedRequestDomain): T & CanonicalEnvelope;
|
|
5210
4597
|
}
|
|
5211
|
-
/** Failure mode taxonomy returned from `verify()`. */
|
|
5212
4598
|
type SignedRequestFailure = "schema_invalid" | "timestamp_drift" | "signature_invalid" | "replay_detected";
|
|
5213
4599
|
/**
|
|
5214
|
-
*
|
|
5215
|
-
*
|
|
5216
|
-
*
|
|
5217
|
-
* The single definition of "which part of a schema failure is the envelope's":
|
|
5218
|
-
* the post-parse shape check in `parse()` iterates it, and `authErrorBody`'s
|
|
5219
|
-
* classifier reads its keys to decide whether a `schema_invalid` throw
|
|
5220
|
-
* is the caller's *input* being wrong (disclosable: the schema is public on
|
|
5221
|
-
* `/v1/info`) or their *signing* being wrong (folded into `signature_invalid`,
|
|
5222
|
-
* one answer for every key and signature failure). `satisfies` pins it to
|
|
5223
|
-
* {@link CanonicalEnvelope}, so a fifth envelope field can't be added to the
|
|
5224
|
-
* 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.
|
|
5225
4603
|
*/
|
|
5226
4604
|
declare const SIGNED_ENVELOPE_TYPES: {
|
|
5227
4605
|
readonly pubkey: "string";
|
|
@@ -5230,9 +4608,7 @@ declare const SIGNED_ENVELOPE_TYPES: {
|
|
|
5230
4608
|
readonly nonce: "string";
|
|
5231
4609
|
readonly auth_statement: "object";
|
|
5232
4610
|
};
|
|
5233
|
-
/** The envelope field names — {@link SIGNED_ENVELOPE_TYPES}' keys. */
|
|
5234
4611
|
declare const SIGNED_ENVELOPE_FIELDS: readonly (keyof CanonicalEnvelope)[];
|
|
5235
|
-
/** Structured error from `verify()` — `sub_reason` is the failure kind. */
|
|
5236
4612
|
declare class SignedRequestError extends Error {
|
|
5237
4613
|
readonly sub_reason: SignedRequestFailure;
|
|
5238
4614
|
/**
|
|
@@ -5259,38 +4635,15 @@ declare class SignedRequestError extends Error {
|
|
|
5259
4635
|
issues?: readonly z.core.$ZodIssue[] | undefined);
|
|
5260
4636
|
}
|
|
5261
4637
|
/**
|
|
5262
|
-
*
|
|
5263
|
-
*
|
|
5264
|
-
* Generalises cast's `canonicalSignedPayload` + `verifyCanonicalSignedRequest`
|
|
5265
|
-
* into a reusable primitive — `(schema, opts) -> { verify, canonicalBytes, signRequest }`.
|
|
5266
|
-
* Both halves derive their canonical bytes through the same function so any
|
|
5267
|
-
* 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.
|
|
5268
4640
|
*/
|
|
5269
4641
|
declare function createSignedRequestVerifier<T = unknown>(schema: ZodLike<T>, opts?: CreateSignedRequestVerifierOpts): SignedRequestVerifier<T>;
|
|
5270
4642
|
/**
|
|
5271
|
-
*
|
|
5272
|
-
*
|
|
5273
|
-
*
|
|
5274
|
-
*
|
|
5275
|
-
* re-verify. They used to disagree — quote checked the raw `body.data`, submit
|
|
5276
|
-
* checked `JobManager.parseInput`'s Zod output — so the two agreed only where
|
|
5277
|
-
* the parse happened to be an identity on the caller's payload. A nested
|
|
5278
|
-
* `.default()`, a `.transform()`, a `z.coerce.*`, or any key the caller sent
|
|
5279
|
-
* that the schema doesn't declare made the quote pass and the paid submit 401
|
|
5280
|
-
* `signature_invalid`, blaming the signature for a schema shape the caller
|
|
5281
|
-
* never saw.
|
|
5282
|
-
*
|
|
5283
|
-
* Raw is what `public SDK documentation` specifies ("take the body, remove the
|
|
5284
|
-
* `signature` field, and canonicalise"), what the CLI signs, and what the SDK
|
|
5285
|
-
* persists as `record.input` — so the parsed form has no claim here. The
|
|
5286
|
-
* capability schema still validates what the handler receives; it just doesn't
|
|
5287
|
-
* get to rewrite the bytes the signature covers. It also means the credit
|
|
5288
|
-
* envelope needs no re-merge: it never left.
|
|
5289
|
-
*
|
|
5290
|
-
* `body.data` wins when present; otherwise the JSON-encoded `body.input` form
|
|
5291
|
-
* is parsed. Anything else — no input at all, a non-JSON legacy string — yields
|
|
5292
|
-
* `{}`, which carries no envelope and so fails the auth check, exactly as
|
|
5293
|
-
* 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.
|
|
5294
4647
|
*/
|
|
5295
4648
|
declare function signedRequestInput(body: {
|
|
5296
4649
|
input?: string;
|
|
@@ -5315,55 +4668,15 @@ declare function signedRequestInput(body: {
|
|
|
5315
4668
|
declare function createDefaultReplayStore(): SignedRequestReplayStore;
|
|
5316
4669
|
|
|
5317
4670
|
/**
|
|
5318
|
-
*
|
|
5319
|
-
*
|
|
5320
|
-
*
|
|
5321
|
-
* 1. {@link checkAuth} fires at request entry on `/v1/quote` and `/v1/job`,
|
|
5322
|
-
* before payment verification. Schema / drift / signature checks only —
|
|
5323
|
-
* no replay-store touch. Unsigned / malformed / stale / wrong-sig
|
|
5324
|
-
* envelopes reject with HTTP 401 with no Cashu round-trip and no pending
|
|
5325
|
-
* revenue row.
|
|
5326
|
-
* 2. {@link recordReplay} fires on `/v1/job` only, after upfront payment is
|
|
5327
|
-
* verified and immediately before `jobManager.createJob`. Commits the
|
|
5328
|
-
* `(pubkey, timestamp, nonce)` tuple to the replay store; throws 401
|
|
5329
|
-
* `replay_detected` on duplicate. The unpaid 402-discovery hop does not
|
|
5330
|
-
* burn the nonce, so clients can re-submit with payment.
|
|
5331
|
-
*
|
|
5332
|
-
* On `replay_detected` after payment was already accepted, the SDK responds
|
|
5333
|
-
* 401 fail-closed uniformly across cashu / mppx / x402 — no rail-specific
|
|
5334
|
-
* refund or compensation logic. The Cashu accept path
|
|
5335
|
-
* commits proofs into the accumulator wallet inside `verifyUpfrontPayment`
|
|
5336
|
-
* before `recordReplay` runs. Unlocked Cashu is rejected upstream, so the
|
|
5337
|
-
* accumulator is the only production path and no proofs are held to
|
|
5338
|
-
* refund; mppx / x402 credentials are single-use and already consumed by
|
|
5339
|
-
* the facilitator.
|
|
5340
|
-
*
|
|
5341
|
-
* The SDK picks the active capability's `input` schema and dispatches the
|
|
5342
|
-
* `checkAuth` call against it — schemes themselves take no schema parameter,
|
|
5343
|
-
* so one declaration covers every capability under a multi-capability DVM.
|
|
5344
|
-
*
|
|
5345
|
-
* A scheme validates the input it is handed, and on `/v1/job` that input
|
|
5346
|
-
* carries the credit envelope (`credit_id` / `draw_id` / `fund`) because the
|
|
5347
|
-
* signature covers it. Those keys are the protocol's, not the capability's, so
|
|
5348
|
-
* a scheme must not fail a schema that doesn't declare them — `secp256k1Auth`
|
|
5349
|
-
* removes them for the schema check only, keeping the signing bytes verbatim
|
|
5350
|
-
* A custom scheme owes callers the same tolerance, or every strict
|
|
5351
|
-
* 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.
|
|
5352
4673
|
*/
|
|
4674
|
+
type ReplayStoreBackend = "postgres" | "memory" | "user-supplied" | "disabled";
|
|
5353
4675
|
/**
|
|
5354
|
-
*
|
|
5355
|
-
*
|
|
5356
|
-
*
|
|
5357
|
-
* - `postgres` — SDK auto-wired a `PostgresReplayStore` against the host's
|
|
5358
|
-
* pool. Cross-machine safe.
|
|
5359
|
-
* - `memory` — SDK fell back to the in-memory FIFO. Per-process; unsafe for
|
|
5360
|
-
* multi-machine deploys. `createDVMHost` emits a warning (or throws under
|
|
5361
|
-
* `DVMKIT_FAIL_FAST=true`) when this kicks in outside `devMode`.
|
|
5362
|
-
* - `user-supplied` — user passed an explicit `replayStore` at factory time.
|
|
5363
|
-
* SDK doesn't override.
|
|
5364
|
-
* - `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.
|
|
5365
4679
|
*/
|
|
5366
|
-
type ReplayStoreBackend = "postgres" | "memory" | "user-supplied" | "disabled";
|
|
5367
4680
|
interface DVMAuthScheme {
|
|
5368
4681
|
/** Wire-level identifier surfaced on `/v1/info#auth`. */
|
|
5369
4682
|
readonly id: typeof SIGNED_REQUEST_AUTH_ID;
|
|
@@ -5373,13 +4686,9 @@ interface DVMAuthScheme {
|
|
|
5373
4686
|
envelope: T & CanonicalEnvelope;
|
|
5374
4687
|
};
|
|
5375
4688
|
/**
|
|
5376
|
-
*
|
|
5377
|
-
*
|
|
5378
|
-
*
|
|
5379
|
-
* otherwise be trusted as authentic, and a compromised row could feed an
|
|
5380
|
-
* attacker-supplied pubkey/envelope into the handler. Throws
|
|
5381
|
-
* {@link SignedRequestError} with `sub_reason: "signature_invalid"` or
|
|
5382
|
-
* `"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.
|
|
5383
4692
|
*/
|
|
5384
4693
|
recheckSignature<T>(schema: ZodLike<T>, input: unknown, domain?: SignedRequestDomain): void;
|
|
5385
4694
|
/** Commit the envelope's `(pubkey, timestamp, nonce)` to the replay store. Throws on duplicate. */
|
|
@@ -5397,17 +4706,9 @@ interface DVMAuthScheme {
|
|
|
5397
4706
|
}
|
|
5398
4707
|
interface Secp256k1AuthOpts {
|
|
5399
4708
|
/**
|
|
5400
|
-
*
|
|
5401
|
-
*
|
|
5402
|
-
*
|
|
5403
|
-
* window, 100k cap) only when no pool exists — emitting a warning (or
|
|
5404
|
-
* throwing under `DVMKIT_FAIL_FAST=true`) outside `devMode` so multi-machine
|
|
5405
|
-
* deploys can't silently inherit the per-process default.
|
|
5406
|
-
*
|
|
5407
|
-
* Pass a custom `SignedRequestReplayStore` here only to override that
|
|
5408
|
-
* default (e.g. a different cross-machine backend), or pass `null` to
|
|
5409
|
-
* disable replay tracking entirely. Any explicit value — including `null` —
|
|
5410
|
-
* 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.
|
|
5411
4712
|
*/
|
|
5412
4713
|
replayStore?: SignedRequestReplayStore | null;
|
|
5413
4714
|
/** Permitted clock drift in seconds. Defaults to 300 (5 minutes). */
|
|
@@ -5416,30 +4717,16 @@ interface Secp256k1AuthOpts {
|
|
|
5416
4717
|
now?: () => number;
|
|
5417
4718
|
}
|
|
5418
4719
|
/**
|
|
5419
|
-
*
|
|
5420
|
-
*
|
|
5421
|
-
*
|
|
5422
|
-
* capability's `/v1/job` and `/v1/quote` on a signed request envelope
|
|
5423
|
-
* (`pubkey` / `signature` / `timestamp` / `nonce`) over the canonical JSON
|
|
5424
|
-
* form of the capability's input. The SDK calls {@link DVMAuthScheme.checkAuth}
|
|
5425
|
-
* at request entry on both endpoints, and {@link DVMAuthScheme.recordReplay}
|
|
5426
|
-
* on `/v1/job` only — after upfront payment is verified, just before the job
|
|
5427
|
-
* is created. The 402-discovery hop on `/v1/job` therefore does not consume
|
|
5428
|
-
* the replay nonce; the same signed envelope is accepted on the paid resubmit.
|
|
5429
|
-
*
|
|
5430
|
-
* One scheme instance covers every capability under the DVM — internally
|
|
5431
|
-
* the SDK caches a per-schema verifier so each capability's Zod schema is
|
|
5432
|
-
* 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.
|
|
5433
4723
|
*/
|
|
5434
4724
|
declare function secp256k1Auth(opts?: Secp256k1AuthOpts): DVMAuthScheme;
|
|
5435
4725
|
|
|
5436
4726
|
/** Structural type matching any Zod schema. Avoids a hard dependency on zod. */
|
|
5437
4727
|
interface ZodLike<T = unknown> {
|
|
5438
|
-
/** Internal Zod metadata (used for duck-type detection). */
|
|
5439
4728
|
_zod: unknown;
|
|
5440
|
-
/** Parse and validate data, throwing on failure. */
|
|
5441
4729
|
parse(data: unknown): T;
|
|
5442
|
-
/** Parse and validate data, returning a result object instead of throwing. */
|
|
5443
4730
|
safeParse(data: unknown): {
|
|
5444
4731
|
success: true;
|
|
5445
4732
|
data: T;
|
|
@@ -5448,28 +4735,13 @@ interface ZodLike<T = unknown> {
|
|
|
5448
4735
|
error: unknown;
|
|
5449
4736
|
};
|
|
5450
4737
|
}
|
|
5451
|
-
/** Extract the output type from a ZodLike schema, or default to string. */
|
|
5452
4738
|
type InputType<T> = T extends ZodLike<infer O> ? O : string;
|
|
5453
4739
|
/**
|
|
5454
|
-
*
|
|
5455
|
-
*
|
|
5456
|
-
* Surfaces the Zod schema's output type so a drifted example produces a
|
|
5457
|
-
* TypeScript error at the `configureDVM` call site. Falls back to
|
|
5458
|
-
* `Record<string, unknown>` when `InputSchema` is undefined or a plain JSON
|
|
5459
|
-
* Schema object (which can't validate at type-level; the runtime
|
|
5460
|
-
* `validateExampleAgainstInput` in `configure.ts` covers schema-less paths).
|
|
5461
|
-
*
|
|
5462
|
-
* Approximation: `ZodLike<T>` exposes only one type param. True `z.input`
|
|
5463
|
-
* distinguishes pre-coercion input from post-coercion output for schemas
|
|
5464
|
-
* that use `.transform()` or `z.coerce.*`. None of the first-party DVMs do
|
|
5465
|
-
* that on their input schemas today. Revisit — extend `ZodLike` to a
|
|
5466
|
-
* two-param shape `ZodLike<Output, Input = Output>` — if a builder ever
|
|
5467
|
-
* 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.
|
|
5468
4742
|
*/
|
|
5469
4743
|
type ExampleType<InputSchema> = [InputSchema] extends [ZodLike<infer O>] ? O : Record<string, unknown>;
|
|
5470
|
-
/** Runtime check for whether a value is a Zod schema. */
|
|
5471
4744
|
declare function isZodSchema(value: unknown): value is ZodLike;
|
|
5472
|
-
/** Structured logging interface injected via ctx.log. */
|
|
5473
4745
|
interface Logger {
|
|
5474
4746
|
debug(message: string, data?: Record<string, unknown>): void;
|
|
5475
4747
|
info(message: string, data?: Record<string, unknown>): void;
|
|
@@ -5495,7 +4767,6 @@ interface PromptOpts {
|
|
|
5495
4767
|
schema?: Record<string, unknown>;
|
|
5496
4768
|
}
|
|
5497
4769
|
interface SDKPaymentRequestOpts {
|
|
5498
|
-
/** Cashu mints accepted for this payment. */
|
|
5499
4770
|
mints?: string[];
|
|
5500
4771
|
/** Minimum P2PK locktime in seconds. */
|
|
5501
4772
|
minLocktimeSeconds?: number;
|
|
@@ -5548,11 +4819,8 @@ interface JobCost {
|
|
|
5548
4819
|
/** ISO 4217 lowercase, e.g. `"usd"`. */
|
|
5549
4820
|
currency: Currency;
|
|
5550
4821
|
}
|
|
5551
|
-
/** Extended job context provided by the SDK runtime to DVM handlers. */
|
|
5552
4822
|
interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
5553
|
-
/** Server-assigned job ID. */
|
|
5554
4823
|
readonly jobId: string;
|
|
5555
|
-
/** Tags from the DVM descriptor (what this DVM handles). */
|
|
5556
4824
|
readonly tags: string[];
|
|
5557
4825
|
/** Job input — raw string or parsed/validated object when an input schema is defined. */
|
|
5558
4826
|
readonly input: Input;
|
|
@@ -5572,24 +4840,17 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5572
4840
|
pubkey: string;
|
|
5573
4841
|
envelope: CanonicalEnvelope & Record<string, unknown>;
|
|
5574
4842
|
};
|
|
5575
|
-
/** Send a progress text message to the client. */
|
|
5576
4843
|
text(message: string): void;
|
|
5577
|
-
/** Send an artifact (output data) to the client. */
|
|
5578
4844
|
artifact(content: ArtifactContent): void;
|
|
5579
4845
|
/** Send a raw typed message (escape hatch). */
|
|
5580
4846
|
sendMessage(type: MessageType, content: Record<string, unknown>): void;
|
|
5581
4847
|
/** Ask the client a question. Resolves when the client responds. */
|
|
5582
4848
|
prompt(id: string, text: string, opts?: PromptOpts): Promise<ResponseContent>;
|
|
5583
4849
|
/**
|
|
5584
|
-
* Request payment
|
|
5585
|
-
*
|
|
5586
|
-
*
|
|
5587
|
-
*
|
|
5588
|
-
* send-time via its centralised fx fetcher. v1 supports `currency: "usd"`
|
|
5589
|
-
* only; other currencies throw `UnsupportedCurrencyError`.
|
|
5590
|
-
*
|
|
5591
|
-
* The legacy `string` form (`"$0.05"`) was removed in under the
|
|
5592
|
-
* 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.
|
|
5593
4854
|
*/
|
|
5594
4855
|
requestPayment(amount: number | {
|
|
5595
4856
|
amount: number;
|
|
@@ -5673,7 +4934,7 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5673
4934
|
step<T>(id: string, fn: () => Promise<T>): Promise<T>;
|
|
5674
4935
|
/** Per-job typed state, auto-loaded and auto-persisted. */
|
|
5675
4936
|
state: State;
|
|
5676
|
-
/** Persistent
|
|
4937
|
+
/** Persistent storage shared across jobs. */
|
|
5677
4938
|
store: KVStore;
|
|
5678
4939
|
/**
|
|
5679
4940
|
* Instrumented fetch — emits `dvm.fetch` spans with redacted URLs, and
|
|
@@ -5690,17 +4951,14 @@ interface SDKJobContext<State = Record<string, unknown>, Input = string> {
|
|
|
5690
4951
|
* / `ctx.fail` are no-ops: the job stays `cancelled`.
|
|
5691
4952
|
*/
|
|
5692
4953
|
readonly signal: AbortSignal;
|
|
5693
|
-
/** Environment variables configured by the operator. */
|
|
5694
4954
|
env: Record<string, string>;
|
|
5695
4955
|
log: Logger;
|
|
5696
4956
|
}
|
|
5697
|
-
/** Context provided to the onQuote handler. */
|
|
5698
4957
|
interface QuoteContext<QuoteData = Record<string, unknown>> {
|
|
5699
|
-
/**
|
|
4958
|
+
/** Quote data, parsed by the configured Zod schema when present. */
|
|
5700
4959
|
data: QuoteData;
|
|
5701
|
-
/**
|
|
4960
|
+
/** The client's raw input string, when supplied. */
|
|
5702
4961
|
input?: string;
|
|
5703
|
-
/** Job parameters from the client, if provided. */
|
|
5704
4962
|
params?: Record<string, string>;
|
|
5705
4963
|
/**
|
|
5706
4964
|
* Operator environment — same object threaded into `JobContext.env`.
|
|
@@ -5822,14 +5080,10 @@ interface QuoteResult {
|
|
|
5822
5080
|
max_units?: number;
|
|
5823
5081
|
};
|
|
5824
5082
|
}
|
|
5825
|
-
/** Quote handler configuration with a Zod schema for structured quote data. */
|
|
5826
5083
|
interface QuoteConfig<QuoteData = Record<string, unknown>> {
|
|
5827
|
-
/** Schema describing the data clients must provide for a quote. */
|
|
5828
5084
|
schema: ZodLike<QuoteData> | Record<string, unknown>;
|
|
5829
|
-
/** Compute a price based on the structured quote data. */
|
|
5830
5085
|
handler(ctx: QuoteContext<QuoteData>): Promise<QuoteResult>;
|
|
5831
5086
|
}
|
|
5832
|
-
/** Incoming message shape for the onMessage catch-all handler. */
|
|
5833
5087
|
interface IncomingMessage {
|
|
5834
5088
|
/** Message type (e.g. "response", "payment", "approval", "cancel"). */
|
|
5835
5089
|
type: MessageType;
|
|
@@ -5846,7 +5100,6 @@ interface IncomingMessage {
|
|
|
5846
5100
|
* the SDK converts at request time.
|
|
5847
5101
|
*/
|
|
5848
5102
|
type PriceValue = string;
|
|
5849
|
-
/** Accepted payment rail identifier. */
|
|
5850
5103
|
type PaymentMethod = "cashu" | "x402" | "tempo";
|
|
5851
5104
|
/**
|
|
5852
5105
|
* Rails that may fund a prepaid **credit**.
|
|
@@ -6263,11 +5516,9 @@ interface DVMDescriptor<State = Record<string, unknown>, InputSchema extends Zod
|
|
|
6263
5516
|
readonly auth?: DVMAuthScheme;
|
|
6264
5517
|
}
|
|
6265
5518
|
|
|
6266
|
-
/** Serializable record of a completed step. */
|
|
6267
5519
|
interface StepRecord {
|
|
6268
5520
|
/** Step identifier (must be unique within a job). */
|
|
6269
5521
|
id: string;
|
|
6270
|
-
/** Cached return value of the step function. */
|
|
6271
5522
|
value: unknown;
|
|
6272
5523
|
/** Cost declarations made while the step body ran, replayed on cache hits. */
|
|
6273
5524
|
costs?: JobCost[];
|
|
@@ -6283,14 +5534,10 @@ interface StepRecord {
|
|
|
6283
5534
|
declare class StepCache {
|
|
6284
5535
|
private cache;
|
|
6285
5536
|
has(id: string): boolean;
|
|
6286
|
-
/** Get a cached step result. Throws if not present. */
|
|
6287
5537
|
get(id: string): unknown;
|
|
6288
|
-
/** Get a cached step record, including replay metadata. Throws if not present. */
|
|
6289
5538
|
getRecord(id: string): StepRecord;
|
|
6290
5539
|
set(id: string, value: unknown, costs?: readonly JobCost[]): void;
|
|
6291
|
-
/** Export all cached steps for persistence. */
|
|
6292
5540
|
serialize(): StepRecord[];
|
|
6293
|
-
/** Restore a StepCache from persisted records. */
|
|
6294
5541
|
static deserialize(records: StepRecord[]): StepCache;
|
|
6295
5542
|
}
|
|
6296
5543
|
|