@dvmkit/sdk 0.3.4-rc.10 → 0.5.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{chunk-2KGG5ORW.js → chunk-7ZUKL4IV.js} +3 -3
  2. package/dist/{chunk-KXZUCCEY.js → chunk-A4YFWZ7R.js} +4 -55
  3. package/dist/{chunk-C2AGARLM.js → chunk-B2IROPQF.js} +450 -83
  4. package/dist/{chunk-MKI6OVW4.js → chunk-CZULM42S.js} +0 -51
  5. package/dist/{chunk-UZC4A4DM.js → chunk-EYTHFFFU.js} +2489 -213
  6. package/dist/{chunk-M4QDXD2A.js → chunk-FYRR2UEZ.js} +1 -1
  7. package/dist/{chunk-4UZN2GP5.js → chunk-GAOAYHZA.js} +209 -33
  8. package/dist/{chunk-YYQG4R7T.js → chunk-GFY5FRX3.js} +6 -4
  9. package/dist/{chunk-Z4BNLUZF.js → chunk-IWQHUSMO.js} +3 -105
  10. package/dist/{chunk-4J32DTLT.js → chunk-IXR5RFK7.js} +82 -4
  11. package/dist/{chunk-2SD2W2BG.js → chunk-JH3SX3LY.js} +252 -26
  12. package/dist/chunk-KA6VN5FK.js +245 -0
  13. package/dist/{chunk-YRDA7EWN.js → chunk-LE74UEV7.js} +61 -16
  14. package/dist/chunk-LIRAHP7B.js +53 -0
  15. package/dist/{chunk-MG67KXU7.js → chunk-MZRJR2CW.js} +4 -2
  16. package/dist/chunk-OFVV37XC.js +57 -0
  17. package/dist/{chunk-A4CM2BD3.js → chunk-PAIM66YA.js} +91 -2
  18. package/dist/{chunk-JDFLRNAZ.js → chunk-PB4FYZET.js} +67 -7
  19. package/dist/{chunk-BQ2NMWKE.js → chunk-PLG4XER6.js} +1 -1
  20. package/dist/chunk-RLTSCKRI.js +109 -0
  21. package/dist/{chunk-VAMSJRJT.js → chunk-TUA7PYH7.js} +27 -105
  22. package/dist/{chunk-O3QDFPKM.js → chunk-TZ2WKCDD.js} +1 -1
  23. package/dist/{chunk-Z3EHBW2G.js → chunk-UOCZXO2Z.js} +7 -5
  24. package/dist/{chunk-EAMZZ2B4.js → chunk-X7EZ5CLI.js} +59 -0
  25. package/dist/{chunk-CE7XEHYC.js → chunk-Z4KLECAC.js} +1 -1
  26. package/dist/{chunk-7X6UDVCQ.js → chunk-Z4ZT5LNT.js} +66 -2
  27. package/dist/{credit-ledger-A35WH2TI.js → credit-ledger-MI2H7NEC.js} +3 -1
  28. package/dist/{credit-menu-0-UtJ3hh.d.ts → credit-menu-CAfqe8IY.d.ts} +133 -7
  29. package/dist/{fx-DhyT1Cjh.d.ts → fx-D9nsN3n4.d.ts} +1 -1
  30. package/dist/index.d.ts +6 -6
  31. package/dist/index.js +4 -3
  32. package/dist/internal/caller.d.ts +80 -13
  33. package/dist/internal/caller.js +56 -54
  34. package/dist/internal/index.js +1 -1
  35. package/dist/internal/server.d.ts +11 -85
  36. package/dist/internal/server.js +21 -18
  37. package/dist/{job-store-D3CcZp_g.d.ts → job-store-Bx-VFRjg.d.ts} +1 -1
  38. package/dist/{lightning-backend-BhsyrBLa.d.ts → lightning-backend-BAx2Y8pP.d.ts} +1 -1
  39. package/dist/{memory-credit-ledger-2LBZVMKJ.js → memory-credit-ledger-W2BAUYJP.js} +4 -2
  40. package/dist/{mpp-setup-7CGBKNUP.js → mpp-setup-RF5FXGGV.js} +5 -1
  41. package/dist/{payout-reporter-ZMOA37GN.js → payout-reporter-3Y2U55ZX.js} +1 -1
  42. package/dist/{postgres-job-store-MSD2HZOI.js → postgres-job-store-VLXLWXUQ.js} +1 -1
  43. package/dist/{revenue-reporter-EKJOGQYR.js → revenue-reporter-MAERPYGK.js} +1 -1
  44. package/dist/server/index.d.ts +14 -10
  45. package/dist/server/index.js +57 -19
  46. package/dist/{step-cache-v2yhyhSZ.d.ts → step-cache-QG_h5-3G.d.ts} +1769 -1549
  47. package/dist/{tempo-charge-store-4WX7FPOV.js → tempo-charge-store-Q3D7KYMH.js} +1 -1
  48. package/dist/tempo-lifecycle-ZBBLFBMH.js +15 -0
  49. package/dist/{tempo-session-store-LCSDHB7G.js → tempo-session-store-ZRNCOX7P.js} +1 -1
  50. package/dist/{tempo-wallet-RCGBBAJH.js → tempo-wallet-B5XXV3YH.js} +4 -3
  51. package/dist/testing/index.d.ts +3 -3
  52. package/dist/{usd-Cu9G6xv2.d.ts → usd-EwRqCPF-.d.ts} +1 -1
  53. package/dist/{x402-S47Q77UE.js → x402-R6HPBY5D.js} +18 -14
  54. package/package.json +1 -1
  55. package/dist/chunk-YHN3WMMF.js +0 -122
  56. package/dist/tempo-lifecycle-TJ62A7UX.js +0 -13
@@ -2,9 +2,9 @@ import { MiddlewareHandler, Context, Hono } from 'hono';
2
2
  import { Challenge, Credential, Store, Method, Receipt } from 'mppx';
3
3
  import { FacilitatorClient } from '@x402/core/server';
4
4
  import { AuthorizerSigner, ChannelStorage, AutoSettlementConfig, Channel, ChannelUpdateResult } from '@x402/evm/batch-settlement/server';
5
- import { Account, Client, Hex } from 'viem';
6
5
  import { Pool, PoolClient } from 'pg';
7
6
  import { PaymentPayload as PaymentPayload$1, PaymentRequirements as PaymentRequirements$1, SettleResponse as SettleResponse$1 } from '@x402/core/types';
7
+ import { Account, Client, Hex } from 'viem';
8
8
  import { z } from 'zod';
9
9
 
10
10
  /**
@@ -111,7 +111,7 @@ interface X402FacilitatorAuth {
111
111
  * Provider-side x402 configuration — where to receive stablecoin payments,
112
112
  * how the dvmkit DVM should advertise the rail on its 402 response.
113
113
  */
114
- interface X402Config {
114
+ interface X402Config$1 {
115
115
  payTo: string;
116
116
  /** CAIP-2 chain id (`eip155:8453` for Base). */
117
117
  network: string;
@@ -320,7 +320,7 @@ interface PaymentRequiredV2 {
320
320
  type PaymentRequired = X402ResponseBody | PaymentRequiredV2;
321
321
  /** Inputs for `buildPaymentRequirements`. */
322
322
  interface BuildPaymentRequirementsOpts {
323
- x402Config: X402Config;
323
+ x402Config: X402Config$1;
324
324
  requiredUsdcMicro: bigint;
325
325
  resource: string;
326
326
  description?: string;
@@ -699,1719 +699,1899 @@ declare function clientCompatibilityMiddleware(): MiddlewareHandler<ClientCompat
699
699
  declare const requireClientCompatibility: ClientCompatibilityGate;
700
700
  declare function clientUpgradeRequired(c: Context, compatibility: ClientCompatibility, requirement: ClientCompatibilityRequirement): Response;
701
701
 
702
- type JsonValue = string | number | boolean | null | JsonValue[] | {
703
- [key: string]: JsonValue;
704
- };
705
- /**
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.
709
- */
710
- declare function canonicalize(value: JsonValue): string;
702
+ interface HostedIntent {
703
+ admissionId?: string;
704
+ jobId?: string;
705
+ creditId?: string;
706
+ fundId?: string;
707
+ callerPubkey?: string;
708
+ fundingCommitment?: string;
709
+ paymentSeq?: number;
710
+ paymentArtifactHash?: string;
711
+ challengeId?: string;
712
+ invoicePaymentHash?: string;
713
+ invoiceRepairPaymentHash?: string;
714
+ amountMicro?: number;
715
+ }
716
+ interface HostedFundingReservation {
717
+ admissionId: string;
718
+ creditId: string;
719
+ fundId: string;
720
+ callerPubkey: string;
721
+ commitment: string;
722
+ paymentArtifactHash: string;
723
+ }
724
+ type Querier = Pick<PoolClient, "query">;
725
+ /** The control row is the common transaction lock for hosted obligation creation and closure. */
726
+ declare class HostingCleanupFence {
727
+ private readonly pool;
728
+ readonly dvmId: string;
729
+ readonly scope: string;
730
+ constructor(pool: Pool, dvmId: string, scope: string);
731
+ assertNewIntent(tx: Querier, intent?: HostedIntent): Promise<void>;
732
+ withNewIntent<T>(intent: HostedIntent | undefined, operation: (tx: PoolClient) => Promise<T>): Promise<T>;
733
+ assertFundingReservation(tx: Querier, reservation: HostedFundingReservation): Promise<void>;
734
+ fundingAdmission(tx: Querier, creditId: string, fundId: string, callerPubkey: string): Promise<string>;
735
+ withNewIntentOn<T>(db: Pool, intent: HostedIntent | undefined, operation: (tx: PoolClient) => Promise<T>): Promise<T>;
736
+ }
737
+
738
+ /** Query surface shared by the pool and an open Postgres transaction. */
739
+ type X402ChannelQuerier = Pick<Pool, "query">;
711
740
  /**
712
- * UTF-8 encoding of the canonical JSON form.
741
+ * Durable phase of an irreversible x402 facilitator settlement.
742
+ *
743
+ * `written_off` is the operator's terminal: a settlement whose
744
+ * ledger leg cannot be completed, reviewed and closed so it leaves the repair
745
+ * queue without pretending it booked. It is deliberately reversible — a later
746
+ * reconcile still accepts it, because it moved nothing.
713
747
  */
714
- declare function canonicaliseForSigning(value: JsonValue): Uint8Array;
715
-
716
- type ReceiptOutcome = "completed" | "failed" | "cancelled";
717
- interface ReceiptPayment {
718
- /** Total credited millisats. `0` on free jobs — they still get receipts. */
719
- msats: number;
748
+ type X402SettlementStatus = "prepared" | "submitting" | "settled" | "complete" | "written_off";
749
+ /** Recovery record binding one chain settlement to one credit-ledger effect. */
750
+ interface X402SettlementIntent {
751
+ settlementId: string;
752
+ channelId: string;
753
+ operation: "fund" | "refund";
754
+ effectId: string;
755
+ paymentId: string;
756
+ payload: PaymentPayload$1;
757
+ requirements: PaymentRequirements$1;
758
+ channelBefore: Channel;
720
759
  /**
721
- * Rail of the most recent successful credit; `null` when nothing was paid.
722
- * `lightning` appears on a draw against a credit the caller funded by
723
- * invoice — it is a funding rail, never an attached-proof one.
760
+ * The channel as it stood when the settlement was handed to the facilitator —
761
+ * after the refund's mandatory on-chain refresh, which {@link channelBefore}
762
+ * precedes.
763
+ *
764
+ * The facilitator sizes a cooperative refund from *live* state
765
+ * (`balance − chargedCumulativeAmount`) and signs it against the *live*
766
+ * `refundNonce`, so this is the snapshot chain evidence has to be derived
767
+ * from. `channelBefore` is a pre-verify mirror kept for the voucher restore,
768
+ * and whenever it was stale a refund that settled read as one that never
769
+ * happened. Absent on rows an older build wrote, and on any row that never
770
+ * reached `submitting`.
724
771
  */
725
- rail: "cashu" | "x402" | "tempo" | "lightning" | null;
726
- /** Rail-native upfront amount (sats / USDC microunits), `null` when absent. */
727
- native_amount: number | null;
728
- native_asset: string | null;
729
- /** Settlement reference: EVM tx hash (x402), mppx challenge id, or the Cashu request id. */
730
- tx_hash: string | null;
731
- /** Cashu mint the proofs were drawn on, `null` off the Cashu rail. */
732
- mint: string | null;
772
+ channelAtSubmission?: Channel;
773
+ submissionBlock?: string;
774
+ pendingId?: string;
775
+ status: X402SettlementStatus;
776
+ response?: SettleResponse$1;
777
+ /** Epoch ms the row was first prepared. */
778
+ createdAt: number;
779
+ /** Epoch ms of the last status write — what the repair queue ages against. */
780
+ updatedAt: number;
781
+ /** Operator's reason, recorded by {@link PostgresX402ChannelStorage.writeOffSettlement}. */
782
+ writeOffNote?: string;
783
+ /** Epoch ms an operator reconciled or wrote this settlement off. */
784
+ resolvedAt?: number;
733
785
  }
786
+ /** The caller-supplied half of a settlement row — everything the store does not stamp itself. */
787
+ type X402SettlementIdentity = Omit<X402SettlementIntent, "status" | "response" | "createdAt" | "updatedAt" | "writeOffNote" | "resolvedAt" | "channelAtSubmission">;
734
788
  /**
735
- * Credit-draw countersignature block. Present on every job whose payment funded
736
- * or drew the credit
737
- * ledger — including implicit N=1 per-job payments, which fund and draw a
738
- * single-use credit in the same request. A caller holding a DVM-signed
739
- * balance trajectory plus its own caller-signed draw intents can prove
740
- * misbehaviour from artifacts both sides already possess; that only works
741
- * because the countersignature rides the receipt.
789
+ * Keyset position in the repair queue's `(created_at, settlement_id)` ordering.
742
790
  *
743
- * Absence is signature-compatible: `canonicalize` omits absent keys, so a
744
- * ledger-less (or free) job's receipt signs identically to a legacy
745
- * receipt.
791
+ * `createdAt` is Postgres's own rendering of the `TIMESTAMPTZ`, carried as text
792
+ * and handed straight back as one — never a `Date`. `created_at` has microsecond
793
+ * resolution and `Date` has millisecond, so a cursor that round-tripped through
794
+ * epoch ms would land *before* the row it was taken from and re-serve it on
795
+ * every page, which at `limit: 1` never advances at all.
746
796
  */
747
- interface ReceiptCredit {
748
- credit_id: string;
749
- /** This draw's identifier — matches the caller-signed draw intent. */
750
- draw_id: string;
751
- /** Amount drawn, in the credit's denomination. */
752
- amount: number;
753
- balance_after: number;
754
- /** Per-credit monotonic draw counter. */
755
- ledger_seq: number;
797
+ interface X402SettlementCursor {
798
+ createdAt: string;
799
+ settlementId: string;
800
+ }
801
+ /** One page of the repair queue, plus the position to resume from. */
802
+ interface X402SettlementPage {
803
+ settlements: X402SettlementIntent[];
804
+ /** Absent when this page was not full — there is nothing behind it. */
805
+ nextAfter?: X402SettlementCursor;
756
806
  }
757
807
  /**
758
- * A DVM-signed proof of what a job did and what it cost.
808
+ * Fleet-wide serialisation of the self-relay's chain submissions.
759
809
  *
760
- * Signed once at the terminal transition by the per-DVM receipt key, whose
761
- * authority chains to the builder identity key through the deploy-time
762
- * attestation (`/v1/info#builder`, `attestation.receipt_pubkey`). Served
763
- * identically on the synchronous `POST /v1/job` 200, on `GET /v1/job/:id`,
764
- * and on every later re-read — the bytes are persisted, not recomputed.
810
+ * Narrow on purpose, and implemented by {@link PostgresX402ChannelStorage}: the
811
+ * relay asks for one thing — exclusive use of its gas EOA for the length of one
812
+ * submission — and must not acquire the ability to touch a channel or a
813
+ * settlement row. Handing it this instead of the host `Pool` is what keeps the
814
+ * acquire off the pool the channel transaction is already holding a client from.
765
815
  */
766
- interface JobReceipt {
767
- v: 1;
768
- job_id: string;
769
- /** Immutable DVM identity bound into the builder attestation. */
770
- dvm_id: string;
771
- /** DVM slug, as bound in the builder attestation. */
772
- dvm: string;
773
- capability: string;
774
- outcome: ReceiptOutcome;
775
- /** Signed Unix millisecond lifecycle times; absent on legacy receipts. */
776
- timing?: {
777
- accepted_at_ms: number;
778
- terminal_at_ms: number;
779
- caller_wait_ms: number;
780
- };
781
- /** SDK-derived attribution on failure/cancellation; absent on completion and legacy receipts. */
782
- ended_by?: "caller" | "provider";
783
- /** Terminal reason on `failed`/`cancelled` (e.g. `worker_died_mid_job`); `null` on success. */
784
- reason: string | null;
785
- /** Per-DVM monotonic terminal counter. Gaps expose receipt suppression. */
786
- seq: number;
787
- /** Unix seconds at which the receipt was signed. */
788
- issued_at: number;
789
- /** Caller's x-only pubkey when signed-request auth was used; `null` otherwise. */
790
- requester_pubkey: string | null;
791
- paid: ReceiptPayment;
816
+ interface X402RelaySubmissionLock {
817
+ withRelaySubmissionLock<T>(key: string, operation: () => Promise<T>): Promise<T>;
818
+ }
819
+ interface X402ChannelStorageOpts {
820
+ /** Original hosted chain identity, persisted only on a channel's first write. */
821
+ hostedNetwork?: string;
792
822
  /**
793
- * {@link computeResultHash} over the delivered result; `null` when nothing
794
- * was delivered. The summary is part of the hash only on `completed` — see
795
- * {@link computeResultHash}.
823
+ * Override the relay submission lock's wait ceiling, in milliseconds.
824
+ *
825
+ * A test seam: the production ceiling is tens of seconds, which no test can
826
+ * wait out, and the refusal path is exactly what needs pinning.
796
827
  */
797
- result_hash: string | null;
798
- /** x-only secp256k1 pubkey that signed this receipt (64 hex chars). */
799
- receipt_pubkey: string;
800
- /** BIP-340 Schnorr signature over the canonical receipt minus `signature` (128 hex chars). */
801
- signature: string;
828
+ relaySubmissionLockTimeoutMs?: number;
802
829
  /**
803
- * Credit-draw countersignature — present when the job's payment
804
- * funded/drew the credit ledger. See {@link ReceiptCredit}. `balance_after`
805
- * reflects the terminal resolution: the recorded draw trajectory on a
806
- * settled (completed) job, the restored available balance on a released
807
- * (failed/cancelled) one.
830
+ * Retire ledger credit when a fresh chain observation proves its channel
831
+ * has no unclaimed backing. Runs under the channel transaction so the
832
+ * channel-first, credit-second lock order stays fleet-wide.
808
833
  */
809
- credit?: ReceiptCredit;
834
+ terminalizeUnbackedCredit?: (evidence: {
835
+ channelId: string;
836
+ channelBalanceNative: bigint;
837
+ totalClaimedNative: bigint;
838
+ observedAt: number;
839
+ }, tx: X402ChannelQuerier) => Promise<unknown>;
840
+ }
841
+ /** Best-effort identification of the backend holding the relay submission lock. */
842
+ interface X402RelayLockHolder {
843
+ pid: number;
844
+ applicationName?: string;
845
+ clientAddr?: string;
846
+ state?: string;
847
+ /** Seconds since the holder's current statement started. */
848
+ heldSeconds?: number;
810
849
  }
811
- type UnsignedJobReceipt = Omit<JobReceipt, "signature">;
812
- type DrainReceiptEvent = "requested" | "parked" | "picked_up" | "sent" | "released";
813
850
  /**
814
- * A DVM-signed proof of one reclaim event. One receipt per
815
- * lifecycle transition — `requested` (the balance debited into the drain
816
- * liability), `parked` (Cashu notes locked to the caller's refund key,
817
- * parked for pickup), `picked_up` (the caller collected them), `sent` (a
818
- * Lightning/x402/mpp send-back settled).
851
+ * The fleet-wide relay submission lock was still held when the wait ceiling expired.
819
852
  *
820
- * The evidence pairing: the caller holds the drain *request* it signed (the
821
- * `/v1/credit` envelope), the DVM countersigns each state the drain reaches.
822
- * A `requested` receipt with no later `parked`/`sent` is a signed IOU whose
823
- * refusal is provable from artifacts both sides already hold — reclaim is
824
- * enforced by reputation with cryptographic evidence, not new crypto.
853
+ * Typed, and `retryable`, because it is an availability outcome rather than a
854
+ * fault in the payment: a fleet-mate whose RPC is retrying holds the lock, and
855
+ * the same request works once it lets go. It refuses rather than proceeding —
856
+ * two machines submitting from one EOA collide on the nonce — and it refuses
857
+ * rather than blocking, which is the defect: the old acquire had no
858
+ * `lock_timeout`, so a wedged holder pinned a host-pool client and a channel
859
+ * advisory lock for as long as it took.
860
+ */
861
+ declare class X402RelaySubmissionLockError extends Error {
862
+ readonly lockKey: string;
863
+ readonly timeoutMs: number;
864
+ readonly holder?: X402RelayLockHolder | undefined;
865
+ readonly code = "x402_relay_submission_lock_timeout";
866
+ readonly retryable = true;
867
+ constructor(lockKey: string, timeoutMs: number, holder?: X402RelayLockHolder | undefined);
868
+ }
869
+ /** Outcome of {@link PostgresX402ChannelStorage.writeOffSettlement} — `written` is the transition bit. */
870
+ interface X402SettlementWriteOff {
871
+ intent: X402SettlementIntent;
872
+ written: boolean;
873
+ }
874
+ /** The refund standing between a channel and the credit it backs. */
875
+ interface X402UnresolvedRefund {
876
+ settlementId: string;
877
+ /** `${creditId}:${drainId}` — the drain this settlement belongs to. */
878
+ effectId: string;
879
+ status: X402SettlementStatus;
880
+ }
881
+ /**
882
+ * The credit ledger's read into durable x402 settlement state.
825
883
  *
826
- * Signed by the same per-DVM receipt key as {@link JobReceipt}, over the
827
- * same canonicalisation, so one attestation chain covers both.
884
+ * Narrow on purpose: the ledger asks one question — is this channel's refund
885
+ * still unresolved — and must not acquire the ability to move a settlement.
886
+ * Implemented by {@link PostgresX402ChannelStorage.pendingRefundSettlement}.
828
887
  */
829
- interface DrainReceipt {
830
- v: 1;
831
- /** Discriminator separating this from a `JobReceipt` in mixed stores. */
832
- kind: "credit_drain";
833
- credit_id: string;
834
- /** This drain's identifier — matches the caller-signed drain request. */
835
- drain_id: string;
836
- event: DrainReceiptEvent;
837
- /** Payout rail: `cashu` | `x402` | `tempo`. */
838
- method: string;
839
- /** Amount reclaimed, micro-units of the credit's currency. */
840
- amount_micro: number;
841
- /**
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.
844
- */
845
- balance_after_micro: number;
846
- /** Per-credit monotonic sequence the drain consumed, shared with draws. */
847
- ledger_seq: number;
848
- /** Unix seconds at which this receipt was signed. */
849
- issued_at: number;
850
- /** The credit owner's x-only pubkey — who the money is owed to. */
851
- caller_pubkey: string;
852
- /** Immutable DVM identity bound into the builder attestation. */
853
- dvm_id: string;
854
- /** DVM slug, as bound in the builder attestation. */
855
- dvm: string;
856
- /** x-only secp256k1 pubkey that signed this receipt (64 hex chars). */
857
- receipt_pubkey: string;
858
- /** BIP-340 Schnorr signature over the canonical receipt minus `signature`. */
859
- signature: string;
860
- }
861
- type UnsignedDrainReceipt = Omit<DrainReceipt, "signature">;
862
- /**
863
- * A DVM-signed proof that one payment funded a prepaid credit.
864
- * The balance and sequence are the values fixed when the funding committed;
865
- * retries return this original artifact even when the credit moved later.
866
- */
867
- interface FundingReceipt {
868
- v: 1;
869
- /** Discriminator separating funding evidence from job and drain receipts. */
870
- kind: "credit_funding";
871
- credit_id: string;
872
- /** Caller fund id, or the attached rail payment id for fund-and-draw. */
873
- fund_id: string;
874
- /** Amount credited, in micro-units of the credit currency. */
875
- funded_micro: number;
876
- /** Available balance immediately after this funding operation committed. */
877
- balance_after_micro: number;
878
- /** Per-credit high-water sequence at that same point. */
879
- ledger_seq: number;
880
- /** Unix seconds at which the funding artifact was first issued. */
881
- issued_at: number;
882
- /** The credit owner's x-only request-signing pubkey. */
883
- caller_pubkey: string;
884
- /** Immutable DVM identity bound into the builder attestation. */
885
- dvm_id: string;
886
- /** DVM slug, as bound in the builder attestation. */
887
- dvm: string;
888
- receipt_pubkey: string;
889
- /** BIP-340 Schnorr signature over the canonical receipt minus `signature`. */
890
- signature: string;
891
- }
892
- type UnsignedFundingReceipt = Omit<FundingReceipt, "signature">;
893
- /**
894
- * Hash the delivered result so a receipt binds to *what* was returned, not
895
- * just that something was.
896
- *
897
- * `sha256(canonicalize({ summary, artifact_hashes }))`, where each artifact
898
- * hash is `sha256(canonicalize(artifactContent))` in emission order (`[]`
899
- * when the job emitted none). Hashing the artifact *content object* rather
900
- * than trusting `content.sha256` — which is optional and which `ctx.artifact`
901
- * never fills in — keeps the hash recomputable by any consumer from exactly
902
- * the messages it received.
903
- *
904
- * **Pass `summary` only for a `completed` job.** On `failed`/`cancelled` the
905
- * SDK's durable terminal writes the terminal *reason* into the job's summary
906
- * column, so a verifier recomputing a non-completed receipt must pass
907
- * `undefined` here and read the reason off `receipt.reason` instead. The
908
- * issuer does the same (`resultHashFor` in `sdk/server/receipt-issuer.ts`).
909
- */
910
- declare function computeResultHash(summary: string | undefined, artifactContents: JsonValue[]): string;
911
- declare function signReceipt(unsigned: UnsignedJobReceipt, secretHex: string): JobReceipt;
912
- /**
913
- * Verify a receipt's self-consistency: the BIP-340 signature over the
914
- * canonical payload (minus `signature`) under the receipt's own
915
- * `receipt_pubkey`. Returns `false` rather than throwing on malformed input.
916
- *
917
- * This is only the inner link. A consumer that wants provenance must also
918
- * check `receipt_pubkey` against `/v1/info#builder`'s attested
919
- * `receipt_pubkey` and verify that attestation under the builder pubkey
920
- * (`verifyAttestation` in `builder-identity.ts`).
921
- */
922
- declare function verifyReceipt(receipt: JobReceipt): boolean;
923
- /**
924
- * Structural guard on a receipt that arrived from outside this process — off a
925
- * `/v1/job` response, or back out of the hand-editable `~/.dvm/receipts.jsonl`.
926
- *
927
- * Checks only that the load-bearing fields are the right *kind* of thing; it
928
- * deliberately does not judge the receipt. A well-formed receipt whose
929
- * signature doesn't hold is `invalid` and the caller must see it said so. A
930
- * body missing one of these fields is a different animal: consumers dereference
931
- * them unconditionally (`receiptDisplay`'s `paid.msats`, the attestation link's
932
- * `receipt_pubkey`), so an unguarded one throws `TypeError` and surfaces as an
933
- * `internal` exit — and it could never have verified anyway, since the
934
- * signature covers the whole canonical body. Refusing it costs no proof.
935
- *
936
- * One guard for both directions on purpose: what can't come in over the wire
937
- * must not be able to come back out of the file.
938
- *
939
- * Release coupling: adding or requiring a field here changes both the SDK's
940
- * emitted wire shape and the caller CLI's accepted shape. Name the first-party
941
- * fleet redeploy as a prerequisite in the release note before publishing the
942
- * stricter caller; see `public compatibility guide`.
943
- */
944
- declare function isSignedJobReceipt(value: unknown): value is JobReceipt;
945
- /** Sign an unsigned drain receipt — same canonicalisation as {@link signReceipt}. */
946
- declare function signDrainReceipt(unsigned: UnsignedDrainReceipt, secretHex: string): DrainReceipt;
947
- /** Sign an unsigned funding receipt using the common receipt canonicalisation. */
948
- declare function signFundingReceipt(unsigned: UnsignedFundingReceipt, secretHex: string): FundingReceipt;
949
- declare function isFundingReceipt(value: unknown): value is FundingReceipt;
950
- declare function verifyFundingReceipt(receipt: FundingReceipt): boolean;
951
- /**
952
- * Structural guard on a drain receipt from outside this process — off a
953
- * `/v1/credit` drain response, or back out of `~/.dvm/credits.json`.
954
- *
955
- * Same contract and same reasoning as {@link isSignedJobReceipt}: both homes
956
- * type these `unknown[]` because neither the wire nor a hand-editable file is
957
- * evidence of shape, and the chain walk dereferences `receipt_pubkey`
958
- * unconditionally. A body missing one of these fields could never have carried
959
- * a valid signature anyway, so refusing it costs no proof.
960
- * Required-field changes follow the fleet-before-caller release note linked
961
- * from {@link isSignedJobReceipt}.
962
- */
963
- declare function isDrainReceipt(value: unknown): value is DrainReceipt;
964
- /**
965
- * Verify a drain receipt's self-consistency, mirroring {@link verifyReceipt}
966
- * the same provenance caveat applies: check `receipt_pubkey` against the
967
- * DVM's attested `receipt_pubkey` for the outer link.
968
- */
969
- declare function verifyDrainReceipt(receipt: DrainReceipt): boolean;
970
-
971
- /**
972
- * Cashu and Lightning credits owe unused sats back at each deposit's funding
973
- * rate. Keep the sats/micro basis per funding lot: a pooled ratio loses the
974
- * basis of top-ups made at different rates. Settled draws and reclaims share
975
- * these lots so their total equals the sats funded.
976
- */
977
- /**
978
- * The rails whose credits carry a Bitcoin deposit this ledger owes back in
979
- * kind — Cashu and Lightning funding, both of which leave sats in the
980
- * provider's own custody.
981
- *
982
- * Declared as its own list rather than derived by subtracting the stablecoin
983
- * rails: a channel-backed credit is in-kind *structurally* (the escrow retains
984
- * the unused value), so it needs no lots at all, and a rail added to
985
- * {@link FundingMethod} must be a decision here rather than a default.
986
- */
987
- declare const NON_CHANNEL_BITCOIN_RAILS: readonly ["cashu", "lightning"];
988
- type NonChannelBitcoinRail = (typeof NON_CHANNEL_BITCOIN_RAILS)[number];
989
- declare function isNonChannelBitcoinRail(rail: string | null | undefined): rail is NonChannelBitcoinRail;
990
- /**
991
- * One funding event's deposit: the sats that arrived and the credit micro they
992
- * bought, with however much of that micro is still unspent.
993
- *
994
- * `remainingMicro` is depleted FIFO by settled draws and by reclaims, so the
995
- * sum of a credit's lots tracks `credits.balance_micro` exactly — with one
996
- * deliberate exception: an expiry release zeroes the balance and
997
- * leaves the lots standing, because a revival restores that balance and its
998
- * in-kind basis has to still be there. A released credit is excluded from the
999
- * sweep floor by `BITCOIN_CREDIT_PREDICATE`'s own `balance_micro > 0`, so the
1000
- * over-coverage is never counted as sats the builder owes back.
1001
- */
1002
- interface FundingLot {
1003
- lotId: string;
1004
- creditId: string;
1005
- rail: NonChannelBitcoinRail;
1006
- /** Sats this funding delivered. `0` on a credit funded before a rail basis was recorded. */
1007
- satsFunded: number;
1008
- /** Credit micro this funding bought — the lot's denominator, never depleted. */
1009
- creditedMicro: number;
1010
- remainingMicro: number;
1011
- /** Rail settlement reference, provenance only. */
1012
- fundingRef: string | null;
1013
- createdAt: number;
1014
- }
1015
- interface LotDebit {
1016
- lotId: string;
1017
- micro: number;
1018
- /** Decrease in this lot's floored sats obligation after taking the micro debit. */
1019
- sats: number;
1020
- }
1021
- interface LotDepletion {
1022
- debits: LotDebit[];
1023
- satsOwed: number;
1024
- /** Micro the lots could not cover. Non-zero means the credit's lots are short. */
1025
- uncoveredMicro: number;
1026
- /** Of the micro taken, how much came out of a lot carrying a real sats basis. */
1027
- backedMicro: number;
888
+ interface X402RefundSettlementGate {
889
+ pendingRefundSettlement(channelId: string, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
1028
890
  }
1029
891
  /**
1030
- * Deplete lots in FIFO order, supplied by fifoOrder. Price each debit as
1031
- * lotSats(before) - lotSats(after), not the independently floored slice: small
1032
- * draws then telescope to the full deposit instead of losing up to one sat
1033
- * per draw. Fractional remaining sats stay with the provider.
1034
- */
1035
- declare function depleteLots(lots: FundingLot[], amountMicro: number): LotDepletion;
1036
- /**
1037
- * What a settling draw's fiat debit was worth in kind, in millisatoshis
1038
- * `null` where the lots cannot price it.
1039
- *
1040
- * This is the single sats authority for a non-channel Bitcoin credit. The
1041
- * settle already depletes the lots the draw consumed, so the figure costs no
1042
- * extra read: it is `depletion.satsOwed` in the ledger's own msat unit, and
1043
- * stamping it on `credit_draws.draw_msats` is what makes
1044
- * `Σ settled draw sats + reclaim sats == Σ sats funded` close per credit.
1045
- *
1046
- * `null` follows {@link isInKindDepletion} exactly, so a settle and a reclaim
1047
- * fall back together: lots short of the balance, or covering lots with no sats
1048
- * basis, keep the pooled pro-rata figure rather than blending a lot rate with
1049
- * a blended one. Mixed coverage counts as unpriceable for the same reason it
1050
- * does on the reclaim.
1051
- */
1052
- declare function inKindDrawMsats(depletion: LotDepletion, amountMicro: number): number | null;
1053
- /**
1054
- * What these lots owe in kind if every remaining micro were reclaimed now —
1055
- * the deposit-liability figure, and the floor the hub must never be swept
1056
- * below.
1057
- */
1058
- declare function lotOwedSats(lots: FundingLot[]): number;
1059
- /**
1060
- * Refund delivery uses a fixed eight-sat reserve so owed_sats cannot shrink
1061
- * after it is promised. Extra delivery cost is absorbed by the provider;
1062
- * unused reserve stays with the provider, like fractional-sat dust.
1063
- */
1064
- declare const DRAIN_DELIVERY_RESERVE_SATS = 8;
1065
- /**
1066
- * The figure a reclaim publishes: its in-kind gross less
1067
- * {@link DRAIN_DELIVERY_RESERVE_SATS}, floored at zero.
892
+ * Postgres implementation of x402's server-side {@link ChannelStorage}.
1068
893
  *
1069
- * Zero is a refusal rather than a payout — a balance worth no more than what
1070
- * it costs to send is `drain_below_dust`, since parking a zero-value token
1071
- * would hold the servicer's queue open forever.
1072
- */
1073
- declare function netOwedSats(grossSats: number): number;
1074
- /**
1075
- * The order lots deplete in: oldest first, `lotId` breaking a tie.
894
+ * `updateChannel` takes an advisory transaction lock before it reads the row,
895
+ * so the read/callback/write sequence is atomic even for a first deposit whose
896
+ * row does not exist yet. {@link withChannelTransaction} exposes the same
897
+ * transaction to the credit ledger: the fixed lock order is channel first,
898
+ * credit second, and no credit-ledger path calls back into this store.
1076
899
  *
1077
- * A tiebreak is not decoration — lot ids are surrogates rather than a
1078
- * per-credit sequence (`fund` takes no credit lock, so two concurrent
1079
- * fundings cannot cooperate on a counter), and two fundings can land on the
1080
- * same millisecond. Without it the FIFO order is whatever the planner
1081
- * returned, and a reclaim's figure would depend on it.
1082
- */
1083
- declare function fifoOrder(left: FundingLot, right: FundingLot): number;
1084
- /**
1085
- * Missing lot coverage or a zero legacy sats basis requires pooled pricing.
1086
- * Mixed backed/unbacked lots also fall back: combining lot and live rates
1087
- * would claim a value neither basis supports.
1088
- */
1089
- declare function isInKindDepletion(depletion: LotDepletion, amountMicro: number): boolean;
1090
-
1091
- /** The rails a payout lands on. Lightning never pays out: credits fund straight into the receive wallet. */
1092
- type PayoutRail = "cashu" | "x402" | "tempo";
1093
- /**
1094
- * Transfers aggregate payments: a Cashu melt, x402 batch to pay_to, or Tempo recipient
1095
- * settlement/close.
1096
- */
1097
- type PayoutKind = "melt" | "batch" | "close" | "settle";
1098
- type PayoutRepairKind = "x402_settlement_reconciled" | "lightning_invoice_reconciled" | "tempo_close_reconciled";
1099
- /**
1100
- * One landed movement into builder custody. Platform retries deduplicate by (dvmId,
1101
- * payoutId); repaired redelivery upgrades that movement rather than adding another.
900
+ * Two pools, and the split is load-bearing. The settlement markers
901
+ * must write *outside* the ledger transaction, which means a second connection
902
+ * taken while the transaction's client is still checked out — on the shared
903
+ * host pool that is a self-deadlock the moment concurrent deposits reach its
904
+ * `max`, with no error and no timeout because every waiter is also a holder.
905
+ * They therefore run on a small dedicated pool that acquires nothing itself,
906
+ * so nothing can ever queue behind them. The rule this rests on: a client held
907
+ * from one pool may only ever be used to acquire from a pool that is terminal.
908
+ * {@link withFleetSettlementLock} and {@link withRelaySubmissionLock} are the
909
+ * other two nesting sites and are bounded a different way — see their docs.
1102
910
  */
1103
- interface PayoutReportPayload {
911
+ declare class PostgresX402ChannelStorage implements ChannelStorage {
912
+ private readonly pool;
913
+ private hostingCleanupFence?;
914
+ useHostingCleanupFence(fence: HostingCleanupFence): void;
915
+ assertHostingFundingReservation(reservation: HostedFundingReservation | undefined): Promise<void>;
916
+ private readonly transaction;
917
+ private readonly hostedFunding;
918
+ private readonly refundBase;
919
+ private settlementPool?;
920
+ private fleetLock;
921
+ private fleetLockQueueDepth;
922
+ private readonly relayLockTimeoutMs;
923
+ private readonly terminalizeUnbackedCredit?;
924
+ private readonly hostedNetwork?;
925
+ constructor(pool: Pool, opts?: X402ChannelStorageOpts);
926
+ init(): Promise<void>;
927
+ /**
928
+ * Read the fleet's durable "claimed funds are awaiting transfer" marker.
929
+ *
930
+ * Upstream keeps this in process memory, which a multi-machine fleet cannot
931
+ * share: the machine that won a claim tick is rarely the one that wins the
932
+ * next settle tick. An unset scope reads false, so a receiver that has never
933
+ * claimed never submits a settlement.
934
+ *
935
+ * Both accessors run on the settlement pool: every caller reads or writes the
936
+ * marker while {@link withFleetSettlementLock} holds a host-pool client, which
937
+ * is the nesting the split exists to keep off a non-terminal pool.
938
+ */
939
+ isSettlePending(scope: string): Promise<boolean>;
940
+ /** Record whether a claim's funds still need a `settle(receiver, token)` transfer. */
941
+ setSettlePending(scope: string, pending: boolean): Promise<void>;
942
+ /**
943
+ * Look up the durable settlement state for one credit-ledger effect.
944
+ *
945
+ * On the dedicated settlement pool like every other marker, and for a sharper
946
+ * reason than they had: this and {@link prepareSettlement} now run *inside*
947
+ * the channel transaction, which holds a host-pool client for its
948
+ * whole duration. Taking a second client from that same pool is the
949
+ * self-deadlock fixed — at pool max every client is a holder and a
950
+ * waiter — and moving these calls under the transaction is exactly what would
951
+ * reintroduce it.
952
+ */
953
+ getSettlement(operation: "fund" | "refund", effectId: string): Promise<X402SettlementIntent | undefined>;
1104
954
  /**
1105
- * Platform record ID; must match the bearer token’s DVM.
955
+ * The refund on this channel that is neither finished nor abandoned, if there
956
+ * is one — the credit ledger's spend gate.
957
+ *
958
+ * `statuses` is the caller's, because the two gates disagree by design: a
959
+ * draw is blocked by {@link X402_SPEND_BLOCKING_SETTLEMENT_STATUSES}, a fresh
960
+ * drain only by {@link X402_WEDGED_SETTLEMENT_STATUSES}.
961
+ *
962
+ * On the dedicated settlement pool, and that is what makes the gate callable
963
+ * at all: a draw routinely runs inside an open channel transaction holding a
964
+ * host-pool client, and taking a second one from that pool is the
965
+ * self-deadlock. The oldest blocking row wins, so a channel that somehow
966
+ * accumulated two names the one an operator should reach for first.
967
+ *
968
+ * Projects three columns rather than decoding a whole {@link
969
+ * X402SettlementIntent}: the decoder refuses a legacy row that carries
970
+ * no reconciliation snapshot, and a gate that throws on one would take every
971
+ * draw on that channel down with it — when the row is exactly the kind that
972
+ * ought to block them.
1106
973
  */
1107
- dvmId: string;
1108
- /** Required. The DVM's own id for the movement — `cashu:melt:<quote>`, `x402:batch:<uuid>`, `tempo:<tx>`. */
1109
- payoutId: string;
1110
- rail: PayoutRail;
1111
- kind: PayoutKind;
974
+ pendingRefundSettlement(channelId: string, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
1112
975
  /**
1113
- * Positive rail-native atomic units: sats or USDC micro.
976
+ * Read one settlement by its own identifier — the handle an operator holds
977
+ * Same dedicated pool as {@link getSettlement}, for the same
978
+ * reason: the repair reads this while a channel transaction may be open.
1114
979
  */
1115
- nativeAmount: number;
1116
- nativeAsset: "sats" | "usdc";
1117
- /** Where it landed; `null` for a melt, whose Lightning destination the platform never records. */
1118
- recipient: string | null;
980
+ getSettlementById(settlementId: string): Promise<X402SettlementIntent | undefined>;
981
+ /** Lazily move a legacy settlement row onto its authorization/effect-scoped replay key. */
982
+ normalizeLegacyPaymentId(settlementId: string, operation: "fund" | "refund", legacyPaymentId: string, paymentId: string): Promise<X402SettlementIntent>;
1119
983
  /**
1120
- * Descriptive mechanism references, never an arithmetic basis.
984
+ * The operator's repair queue: settlements stuck between "chain paid" and
985
+ * "ledger booked", oldest write first.
986
+ *
987
+ * Keyset-paginated on `(created_at, settlement_id)` for the reason the
988
+ * blocked-invoice queue is: a wedged row never self-clears, so a fixed first
989
+ * page of rows the operator has declined to act on would starve everything
990
+ * behind it on every run, forever.
991
+ *
992
+ * The keyset is on `created_at`, which never moves, while `updatedBeforeMs`
993
+ * filters on `updated_at`, which does — so a row an operator touches mid-walk
994
+ * can leave the queue but can never jump the cursor and be skipped. The cursor
995
+ * itself is Postgres's text rendering of that timestamp, handed back verbatim;
996
+ * see {@link X402SettlementCursor} for why a `Date` cannot carry it.
997
+ *
998
+ * `updatedBeforeMs` is the staleness floor. A healthy settlement passes
999
+ * through `submitting` and `settled` inside one request, so anything younger
1000
+ * than the floor is in flight rather than wedged and must not be offered as
1001
+ * repairable work.
1121
1002
  */
1122
- refs: Record<string, unknown>;
1123
- /** Required. When the money landed (epoch ms). */
1124
- landedAt: number;
1125
- repaired?: boolean;
1126
- /** Required iff `repaired`. */
1127
- repairKind?: PayoutRepairKind;
1128
- }
1129
- interface PayoutWedge {
1130
- id: string;
1003
+ listSettlements(args: {
1004
+ statuses: X402SettlementStatus[];
1005
+ updatedBeforeMs: number;
1006
+ limit: number;
1007
+ after?: X402SettlementCursor;
1008
+ }): Promise<X402SettlementPage>;
1131
1009
  /**
1132
- * `settle`: an x402 batch whose transfer keeps failing (auto-retried).
1133
- * `refund`: an x402 cooperative refund wedged between chain and ledger —
1134
- * caller money, not in the pool. `close`: a Tempo close wedged the same way.
1135
- * `melt`: cashu rows whose builder-side melt failed.
1010
+ * Close a wedged settlement without booking anything.
1011
+ *
1012
+ * A single-statement CAS on the wedged statuses, on the settlement pool —
1013
+ * there is no ledger effect to be atomic with, which is the whole point:
1014
+ * the write-off records that an operator reviewed this settlement and is not
1015
+ * completing it, and no dvmkit table moves. Idempotent, and the first note
1016
+ * wins; `written` is the transition bit the caller logs on, so the one place
1017
+ * the amount is ever recorded gets exactly one entry.
1018
+ *
1019
+ * Reversible by construction: a later reconcile calls
1020
+ * {@link recordSettlementResponse}, whose `status <> 'complete'` CAS lifts a
1021
+ * written-off row back to `settled`.
1136
1022
  */
1137
- kind: "settle" | "refund" | "close" | "melt";
1138
- /** Epoch ms the wedge was first observed. */
1139
- since: number;
1140
- /** Retry count where the retry is automatic, else null. */
1141
- attempts: number | null;
1142
- /** Epoch ms of the next automatic retry, else null. */
1143
- nextRetry: number | null;
1144
- lastError: string | null;
1145
- /** Rail-native amount held up, when known. */
1146
- native?: number;
1147
- }
1148
- interface PayoutPool {
1149
- id: string;
1150
- native: number;
1151
- label?: string;
1152
- }
1153
- /**
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.
1156
- */
1157
- interface PayoutPendingPayload {
1158
- dvmId: string;
1159
- rail: PayoutRail;
1160
- /** Everything on this rail still moving toward the builder, in atomic units. */
1161
- poolNative: number;
1162
- nativeAsset: "sats" | "usdc";
1163
- items: number;
1164
- pools: PayoutPool[];
1165
- wedged: PayoutWedge[];
1166
- /** This DVM's clock when the snapshot was taken (epoch ms). */
1167
- snapshotAt: number;
1168
- }
1169
- interface PayoutTransport {
1023
+ writeOffSettlement(settlementId: string, note: string | undefined, nowMs: number): Promise<X402SettlementWriteOff | undefined>;
1024
+ /** Persist the reconciliation identity, refreshing a prepared retry's transient reservation. */
1025
+ prepareSettlement(intent: X402SettlementIdentity, hostingFunding?: HostedFundingReservation): Promise<X402SettlementIntent>;
1170
1026
  /**
1171
- * Queue a payout through the caller's open transaction, so the row and the
1172
- * fact it reports commit or roll back together; without `tx` it is queued on
1173
- * its own. Durable once committed — the retry loop delivers it, or
1174
- * {@link PayoutTransport.drainPending} when a caller wants it now.
1027
+ * Mark that the next operation is the ambiguous external-call boundary.
1028
+ *
1029
+ * `channelAtSubmission` is the live channel the facilitator is about to size
1030
+ * and sign the settlement against — bookmarked here, with the
1031
+ * block height, because these two are the only description of that instant
1032
+ * that survives the call.
1175
1033
  */
1176
- enqueuePayout(payload: PayoutReportPayload, tx?: RevenueReporterQuerier): Promise<void>;
1177
- /** Queue a payout on its own and try to deliver it at once. */
1178
- reportPayout(payload: PayoutReportPayload): Promise<void>;
1179
- /** Best-effort: one POST, replaced by the next snapshot. */
1180
- reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
1181
- /** Deliver what is queued now rather than on the next retry tick. */
1182
- drainPending?(): Promise<void>;
1183
- }
1184
- /**
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.
1187
- */
1188
- interface TransactionalPayoutHook<E> {
1189
- enqueue(event: E, tx: RevenueReporterQuerier): Promise<void>;
1190
- committed(): void;
1034
+ markSettlementSubmitting(settlementId: string, submissionBlock: string, channelAtSubmission?: Channel): Promise<void>;
1035
+ /** Return an explicitly failed facilitator submission to the safe retry phase. */
1036
+ markSettlementRetryable(settlementId: string): Promise<void>;
1037
+ /**
1038
+ * Record a successful facilitator response independently of the enclosing
1039
+ * ledger transaction.
1040
+ *
1041
+ * The write-off columns are cleared, not carried: this CAS is what lifts a
1042
+ * `written_off` row back to `settled` (which is what makes the write-off
1043
+ * reversible), and a row that is no longer written off must not still be
1044
+ * carrying the note and resolution timestamp of a decision that has been
1045
+ * reversed. On the live path the columns are already `NULL` and this is a
1046
+ * no-op.
1047
+ */
1048
+ recordSettlementResponse(settlementId: string, response: SettleResponse$1): Promise<void>;
1049
+ /**
1050
+ * Mark the external settlement and ledger effect complete in the active transaction.
1051
+ *
1052
+ * `resolvedAtMs` is set only by the operator repair, so an audit
1053
+ * read can tell a settlement that completed on its own from one a human had
1054
+ * to finish.
1055
+ */
1056
+ completeSettlement(settlementId: string, resolvedAtMs?: number): Promise<void>;
1057
+ get(channelId: string): Promise<Channel | undefined>;
1058
+ /** List all channel snapshots in stable channel-id order. */
1059
+ list(): Promise<Channel[]>;
1060
+ /** Atomic backend-level read/modify/write for one channel. */
1061
+ updateChannel(channelId: string, update: (current: Channel | undefined) => Channel | undefined): Promise<ChannelUpdateResult>;
1062
+ /**
1063
+ * Run channel state and its ledger effect under the channel lock.
1064
+ *
1065
+ * Upstream's voucher-only settle updates `chargedCumulativeAmount` through
1066
+ * `updateChannel`. Keeping that update and `commitMarkedFunding` in this one
1067
+ * transaction means a ledger refusal rolls the voucher state back. External
1068
+ * deposit/refund responses are recorded separately before this transaction
1069
+ * commits, leaving a durable reconciliation row if COMMIT fails.
1070
+ */
1071
+ /**
1072
+ * Sanction one cooperative-refund base for the updates inside `operation`.
1073
+ *
1074
+ * The store cannot derive the base — it comes from the credit ledger — so
1075
+ * this is how the settlement layer says "this exact decrease is the one the
1076
+ * payer's verified refund voucher authorises". Outside this scope a decrease
1077
+ * of `chargedCumulativeAmount` is refused flat, which is what keeps the
1078
+ * arbitrary-decrease guard meaningful.
1079
+ */
1080
+ withSanctionedRefundBase<T>(base: string, operation: () => Promise<T>): Promise<T>;
1081
+ withChannelTransaction<T>(channelId: string, operation: (tx: PoolClient) => Promise<T>, hostingFunding?: HostedFundingReservation): Promise<T>;
1082
+ /** Durable effect bookkeeping reached while a caller already holds a host-pool client. */
1083
+ withSettlementTransaction<T>(operation: (tx: PoolClient) => Promise<T>): Promise<T>;
1084
+ /**
1085
+ * Run one fleet-wide settlement operation, optionally waiting for the current owner.
1086
+ *
1087
+ * The lock is session-scoped, so its client stays checked out for the whole
1088
+ * operation — and `operation()` (upstream's claim/settle) re-enters the pool
1089
+ * through {@link updateChannel}. Two bounds keep that from wedging the pool
1090
+ * local callers are serialized in-process, so this holds at most
1091
+ * one client per process no matter how many requests arrive, and a waiting
1092
+ * acquire carries a `lock_timeout` so a wedged fleet-mate can't pin that
1093
+ * client — and the caller's request — indefinitely. A timeout reports the
1094
+ * lock as not acquired, which is what every caller already handles.
1095
+ *
1096
+ * The queue depth is reserved synchronously, before the promise chain can
1097
+ * yield. That makes the non-waiting form a true local try-lock even while the
1098
+ * first caller is still acquiring its connection: an overlapping request
1099
+ * returns `false` instead of queuing behind the facilitator operation.
1100
+ */
1101
+ withFleetSettlementLock(operation: () => Promise<unknown>, wait?: boolean): Promise<boolean>;
1102
+ /**
1103
+ * Hold the fleet-wide self-relay submission lock for one chain submission.
1104
+ *
1105
+ * The session lock keeps one per-DVM gas EOA to one in-flight submission
1106
+ * across the whole fleet — two machines submitting at once collide on the
1107
+ * nonce. It is reached from `resource.settlePayment` inside
1108
+ * {@link withChannelTransaction} and from upstream's claim/settle inside
1109
+ * {@link withFleetSettlementLock}, so on both paths a host-pool client is
1110
+ * already checked out: it therefore acquires from the terminal settlement
1111
+ * pool, which is the rule in this class's doc, and never from the host pool
1112
+ * the caller is holding.
1113
+ *
1114
+ * The wait is bounded by `lock_timeout` and a timeout **refuses** rather than
1115
+ * proceeding unlocked, which is where this differs from
1116
+ * {@link withFleetSettlementLock}: a fleet lock nobody got means the claim is
1117
+ * someone else's turn, while a submission lock nobody got means the caller
1118
+ * must not submit at all. The refusal is typed and retryable, and names the
1119
+ * holder when Postgres will tell us who it is.
1120
+ *
1121
+ * The relay's own in-process tail serialises callers before they reach here,
1122
+ * so one process pins at most one settlement-pool client no matter how many
1123
+ * deposits arrive.
1124
+ */
1125
+ withRelaySubmissionLock<T>(key: string, operation: () => Promise<T>): Promise<T>;
1126
+ /** Release the dedicated settlement pool; the host-owned pool is left alone. */
1127
+ close(): Promise<void>;
1128
+ private settlementWriter;
1129
+ private settlementPoolRef;
1130
+ private runUnderFleetLock;
1131
+ private updateInTransaction;
1191
1132
  }
1192
- interface CashuMeltCompleted {
1193
- rows: {
1194
- id: string;
1195
- mintUrl: string;
1196
- proofAmount: number;
1197
- }[];
1198
- meltQuoteId: string;
1199
- paymentPreimage: string;
1133
+
1134
+ type Query = Pick<Pool | PoolClient, "query">;
1135
+ type Rail = "x402" | "tempo";
1136
+ type Operation = "claim" | "refund" | "settle" | "close";
1137
+ type State = "reserved" | "observed" | "report_queued" | "delivered" | "no_report";
1138
+ interface ObligationRow {
1139
+ id: string;
1140
+ source_key: string;
1141
+ dvm_id: string;
1142
+ scope: string;
1143
+ instance_id: string;
1144
+ boot_epoch: string;
1145
+ rail: Rail;
1146
+ operation: Operation;
1147
+ network: string;
1148
+ contract: string;
1149
+ channel_id: string;
1150
+ original: Record<string, unknown>;
1151
+ state: State;
1152
+ tx_hash: string | null;
1153
+ block_number: string | null;
1154
+ block_hash: string | null;
1155
+ delta_native: string | null;
1156
+ payout_id: string | null;
1157
+ report_id: string | null;
1158
+ report_payload: Record<string, unknown> | null;
1159
+ }
1160
+ interface HostedEffectReservation {
1161
+ id: string;
1162
+ replayed: boolean;
1200
1163
  }
1201
- interface TempoSessionSettled {
1164
+ interface HostedObservedEffect {
1165
+ id: string;
1202
1166
  txHash: string;
1203
- channelId: string;
1204
- trigger: "settle" | "close" | "scheduled";
1205
- /** Cumulative amount settled to the payee on this channel, atomic units. */
1206
- amount: bigint;
1207
- /** Newly settled to the payee by this transaction, atomic units. */
1208
- delta: bigint;
1167
+ blockNumber: string;
1168
+ blockHash: string;
1169
+ deltaNative: string;
1209
1170
  }
1210
- interface TempoCloseReconciled {
1171
+ interface HostedRecoverableEffect {
1172
+ id: string;
1173
+ rail: Rail;
1174
+ operation: Operation;
1211
1175
  channelId: string;
1212
1176
  txHash: string;
1213
- /** Captured by the payee — this DVM — per the receipt, atomic units as a decimal string. */
1214
- settledToPayee: string;
1177
+ batchId?: string;
1178
+ reportId: string | null;
1179
+ blockHash: string | null;
1180
+ deltaNative: string | null;
1215
1181
  }
1216
- interface X402TrackedChannel {
1217
- channelId: string;
1218
- chargedCumulativeAmount: string;
1219
- totalClaimed: string;
1220
- }
1221
- interface X402WedgedRefund {
1222
- settlementId: string;
1223
- /** Epoch ms the settlement was first prepared. */
1224
- createdAt: number;
1225
- native?: number;
1182
+ interface X402Config {
1183
+ network: string;
1184
+ rpcUrl?: string;
1185
+ receiver: string;
1186
+ token: string;
1226
1187
  }
1227
- interface X402PayoutContext {
1228
- /** `${network}|${payTo}|${token}` — the scope the settle-pending marker is keyed on. */
1229
- scope: string;
1230
- payTo: string;
1188
+ interface TempoConfig {
1231
1189
  network: string;
1232
- storage: {
1233
- list(): Promise<X402TrackedChannel[]>;
1234
- };
1235
- /** The scheduler's settle cadence — what a wedge's `nextRetry` is derived from. */
1236
- settleIntervalMs: number;
1237
- listWedgedRefunds?: () => Promise<X402WedgedRefund[]>;
1190
+ rpcUrl?: string;
1191
+ escrow: string;
1192
+ payee: string;
1193
+ token: string;
1238
1194
  }
1239
- interface X402TrackedManager {
1240
- claim(...args: never[]): Promise<{
1241
- vouchers: number;
1242
- transaction: string;
1243
- }[]>;
1244
- settle(): Promise<{
1245
- transaction: string;
1246
- }>;
1195
+ /** Private, server-installed record of economic effects whose report may outlive the rail call. */
1196
+ declare class HostedSettlementReportObligations {
1197
+ private readonly pool;
1198
+ private readonly dvmId;
1199
+ private readonly scope;
1200
+ private x402?;
1201
+ private tempo?;
1202
+ private x402SettlementStore?;
1203
+ constructor(pool: Pool, dvmId: string, scope: string);
1204
+ configureX402(config: X402Config): void;
1205
+ attachX402SettlementStore(store: PostgresX402ChannelStorage): void;
1206
+ private x402Transaction;
1207
+ configureTempo(config: TempoConfig): void;
1208
+ init(): Promise<void>;
1209
+ reserveX402(channelId: string, operation: "claim" | "refund", expected: {
1210
+ charged?: string;
1211
+ signature?: string;
1212
+ refundNonce?: string;
1213
+ refundAmount?: string;
1214
+ voucherSignature?: string;
1215
+ signedMax?: string;
1216
+ authorizationPayload: Record<string, unknown>;
1217
+ authorizationRequirements: {
1218
+ scheme: string;
1219
+ network: string;
1220
+ asset: string;
1221
+ payTo: string;
1222
+ amount: string;
1223
+ };
1224
+ }): Promise<HostedEffectReservation | null>;
1225
+ reserveTempo(channelId: string, operation: "settle" | "close", drainId?: string): Promise<HostedEffectReservation>;
1226
+ findPendingTempo(channelId: string, operation: "settle" | "close"): Promise<ObligationRow | undefined>;
1227
+ submittedIds(rail: Rail, operation: Operation, txHash: string, batchId?: string): Promise<string[]>;
1228
+ refundClaimDelta(txHash: string, channelId: string): Promise<bigint>;
1229
+ assertReservedTempoClose(id: string, channelId: string): Promise<void>;
1230
+ originalTempoCloseSettled(id: string, channelId: string, txHash: string): Promise<bigint>;
1231
+ settlementId(batchId: string, txHash: string): Promise<string>;
1232
+ reserveOpenX402Settlement(): Promise<HostedEffectReservation>;
1233
+ private ensureOpenX402Batch;
1234
+ recordSubmitted(id: string, txHash: string): Promise<void>;
1235
+ recoveryCandidates(): Promise<HostedRecoverableEffect[]>;
1236
+ recoverOriginalX402Refunds(): Promise<Array<{
1237
+ id: string;
1238
+ error: unknown;
1239
+ }>>;
1240
+ reconcileX402Batches(): Promise<Array<{
1241
+ id: string;
1242
+ error: unknown;
1243
+ }>>;
1244
+ queueReport(ids: string[], payoutId: string, reportId: string, payload: Record<string, unknown>, tx: Query): Promise<void>;
1245
+ hasOriginalReport(ids: string[], payoutId: string, payload: Record<string, unknown>, tx: Query): Promise<boolean>;
1246
+ assertReportReady(reportId: string, payload: Record<string, unknown>, query?: Query): Promise<void>;
1247
+ acknowledgeDelivered(reportId: string, tx: Query, requireLinked?: boolean): Promise<void>;
1248
+ observeCanonical(id: string): Promise<HostedObservedEffect>;
1249
+ private lockRow;
1250
+ unresolvedCount(query?: Query): Promise<number>;
1251
+ hasCompleteHistory(query?: Query): Promise<boolean>;
1252
+ private lockIdentity;
1253
+ private insert;
1247
1254
  }
1255
+
1256
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
1257
+ [key: string]: JsonValue;
1258
+ };
1248
1259
  /**
1249
- * The batch-settlement server's view of the reporter: attach once with the
1250
- * scope, then route every claim and settle through the tracked manager so
1251
- * the open batch is kept and the settle emits the payout.
1260
+ * Object keys sort recursively in UTF-16 code-unit order; arrays preserve order.
1261
+ * Undefined object properties are omitted. Unsupported scalars, including
1262
+ * non-finite numbers, throw TypeError. Callers strip unsigned fields themselves.
1252
1263
  */
1253
- interface X402PayoutObserver {
1254
- attach(ctx: X402PayoutContext): void;
1255
- trackManager<M extends X402TrackedManager>(manager: M): M;
1256
- /** A settle that landed outside the tracked manager — the manual claim-and-settle's own retry loop. */
1257
- recordSettle(transaction: string): Promise<void>;
1258
- /** A settle attempt that failed outside the tracked manager. One call per attempt a builder would count as one. */
1259
- recordSettleFailure(error: unknown): Promise<void>;
1260
- }
1261
- interface TempoPayoutReader {
1262
- recipient?: string;
1263
- /** CAIP-2 network for the refs, when known. */
1264
- network?: string;
1265
- listActive?: (limit: number, cursor?: {
1266
- updatedAt: number;
1267
- key: string;
1268
- }) => Promise<{
1269
- key: string;
1270
- state: Record<string, unknown>;
1271
- updatedAt: number;
1272
- }[]>;
1273
- listChannelDrains?: (args: {
1274
- limit: number;
1275
- after?: ChannelDrainCursor;
1276
- createdBeforeMs: number;
1277
- rail: "tempo";
1278
- }) => Promise<CreditDrainRecord[]>;
1279
- }
1264
+ declare function canonicalize(value: JsonValue): string;
1280
1265
  /**
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.
1266
+ * UTF-8 encoding of the canonical JSON form.
1283
1267
  */
1284
- interface X402OpenBatch {
1285
- scope: string;
1286
- batchId: string;
1287
- claimedNative: bigint;
1288
- voucherCount: number;
1289
- claims: number;
1290
- openedAt: number;
1291
- attempts: number;
1292
- firstFailedAt: number | null;
1293
- lastError: string | null;
1294
- nextRetryAt: number | null;
1295
- }
1296
- interface X402BatchStore {
1297
- get(scope: string): Promise<X402OpenBatch | undefined>;
1298
- put(batch: X402OpenBatch): Promise<void>;
1299
- delete(scope: string): Promise<void>;
1268
+ declare function canonicaliseForSigning(value: JsonValue): Uint8Array;
1269
+
1270
+ type ReceiptOutcome = "completed" | "failed" | "cancelled";
1271
+ interface ReceiptPayment {
1272
+ /** Total credited millisats. `0` on free jobs — they still get receipts. */
1273
+ msats: number;
1300
1274
  /**
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.
1275
+ * Rail of the most recent successful credit; `null` when nothing was paid.
1276
+ * `lightning` appears on a draw against a credit the caller funded by
1277
+ * invoice — it is a funding rail, never an attached-proof one.
1303
1278
  */
1304
- settle(scope: string, batchId: string, enqueue: (tx?: RevenueReporterQuerier) => Promise<void>): Promise<boolean>;
1279
+ rail: "cashu" | "x402" | "tempo" | "lightning" | null;
1280
+ /** Rail-native upfront amount (sats / USDC microunits), `null` when absent. */
1281
+ native_amount: number | null;
1282
+ native_asset: string | null;
1283
+ /** Settlement reference: EVM tx hash (x402), mppx challenge id, or the Cashu request id. */
1284
+ tx_hash: string | null;
1285
+ /** Cashu mint the proofs were drawn on, `null` off the Cashu rail. */
1286
+ mint: string | null;
1305
1287
  }
1306
- interface PayoutReporterOpts {
1307
- dvmId: string;
1308
- transport: PayoutTransport;
1309
- /**
1310
- * Postgres holds durable x402 batches and supplies Cashu snapshots. Without it, the default
1311
- * batch store is memory and Cashu snapshots are omitted.
1312
- */
1313
- db?: Pool;
1314
- /**
1315
- * Only accumulator hosts create wallet_accumulator; enabling this on another host makes
1316
- * each snapshot query fail.
1317
- */
1318
- cashu?: boolean;
1319
- /** Override the batch store (tests); defaults to Postgres on `db`, memory without. */
1320
- batchStore?: X402BatchStore;
1321
- /** Snapshot cadence, ms. Default 5 minutes. */
1322
- snapshotIntervalMs?: number;
1323
- now?: () => number;
1288
+ /**
1289
+ * Credit-draw countersignature block. Present on every job whose payment funded
1290
+ * or drew the credit
1291
+ * ledger — including implicit N=1 per-job payments, which fund and draw a
1292
+ * single-use credit in the same request. A caller holding a DVM-signed
1293
+ * balance trajectory plus its own caller-signed draw intents can prove
1294
+ * misbehaviour from artifacts both sides already possess; that only works
1295
+ * because the countersignature rides the receipt.
1296
+ *
1297
+ * Absence is signature-compatible: `canonicalize` omits absent keys, so a
1298
+ * ledger-less (or free) job's receipt signs identically to a legacy
1299
+ * receipt.
1300
+ */
1301
+ interface ReceiptCredit {
1302
+ credit_id: string;
1303
+ /** This draw's identifier — matches the caller-signed draw intent. */
1304
+ draw_id: string;
1305
+ /** Amount drawn, in the credit's denomination. */
1306
+ amount: number;
1307
+ balance_after: number;
1308
+ /** Per-credit monotonic draw counter. */
1309
+ ledger_seq: number;
1324
1310
  }
1325
1311
  /**
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.
1312
+ * A DVM-signed proof of what a job did and what it cost.
1313
+ *
1314
+ * Signed once at the terminal transition by the per-DVM receipt key, whose
1315
+ * authority chains to the builder identity key through the deploy-time
1316
+ * attestation (`/v1/info#builder`, `attestation.receipt_pubkey`). Served
1317
+ * identically on the synchronous `POST /v1/job` 200, on `GET /v1/job/:id`,
1318
+ * and on every later re-read — the bytes are persisted, not recomputed.
1329
1319
  */
1330
- declare class PayoutReporter implements PayoutTransport {
1331
- private readonly dvmId;
1332
- private readonly transport;
1333
- private readonly db?;
1334
- private readonly cashu;
1335
- private readonly batches;
1336
- private readonly ownsBatchStore;
1337
- private readonly intervalMs;
1338
- private readonly now;
1339
- private lastX402SnapshotAt;
1340
- private x402?;
1341
- private tempo?;
1342
- private timer;
1343
- private warmup;
1344
- constructor(opts: PayoutReporterOpts);
1345
- init(): Promise<void>;
1346
- /**
1347
- * Start the snapshot loop. Unref'd, so it never keeps a scale-to-zero
1348
- * machine awake; a first snapshot runs shortly after boot so a fresh process
1349
- * reports without waiting a whole interval.
1350
- */
1351
- start(): void;
1352
- stop(): void;
1353
- enqueuePayout(payload: PayoutReportPayload, tx?: RevenueReporterQuerier): Promise<void>;
1354
- reportPayout(payload: PayoutReportPayload): Promise<void>;
1355
- reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
1356
- /** Deliver what is queued now; a transport without the seam waits for its retry loop. */
1357
- flush(): Promise<void>;
1358
- meltHook(): TransactionalPayoutHook<CashuMeltCompleted>;
1359
- /**
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.
1363
- */
1364
- cashuMelted(event: CashuMeltCompleted, tx?: RevenueReporterQuerier): Promise<void>;
1365
- attachTempo(reader: TempoPayoutReader): void;
1366
- /**
1367
- * A Tempo channel settled or closed on chain. `delta` is what this
1368
- * transaction newly paid the recipient; a transaction that paid nothing new
1369
- * (a close of a fully settled channel) is no payout.
1370
- */
1371
- tempoSettled(event: TempoSessionSettled): Promise<void>;
1372
- tempoRepairHook(): TransactionalPayoutHook<TempoCloseReconciled>;
1373
- /**
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.
1377
- */
1378
- tempoCloseReconciled(event: TempoCloseReconciled, tx?: RevenueReporterQuerier): Promise<void>;
1379
- x402Observer(): X402PayoutObserver;
1380
- /**
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.
1384
- */
1385
- private trackX402Manager;
1386
- private recordX402Claim;
1387
- private recordX402Settle;
1388
- private recordX402SettleFailure;
1389
- /** Every rail this reporter can read, each on its own failure boundary. */
1390
- snapshot(): Promise<void>;
1320
+ interface JobReceipt {
1321
+ v: 1;
1322
+ job_id: string;
1323
+ /** Immutable DVM identity bound into the builder attestation. */
1324
+ dvm_id: string;
1325
+ /** DVM slug, as bound in the builder attestation. */
1326
+ dvm: string;
1327
+ capability: string;
1328
+ outcome: ReceiptOutcome;
1329
+ /** Signed Unix millisecond lifecycle times; absent on legacy receipts. */
1330
+ timing?: {
1331
+ accepted_at_ms: number;
1332
+ terminal_at_ms: number;
1333
+ caller_wait_ms: number;
1334
+ };
1335
+ /** SDK-derived attribution on failure/cancellation; absent on completion and legacy receipts. */
1336
+ ended_by?: "caller" | "provider";
1337
+ /** Terminal reason on `failed`/`cancelled` (e.g. `worker_died_mid_job`); `null` on success. */
1338
+ reason: string | null;
1339
+ /** Per-DVM monotonic terminal counter. Gaps expose receipt suppression. */
1340
+ seq: number;
1341
+ /** Unix seconds at which the receipt was signed. */
1342
+ issued_at: number;
1343
+ /** Caller's x-only pubkey when signed-request auth was used; `null` otherwise. */
1344
+ requester_pubkey: string | null;
1345
+ paid: ReceiptPayment;
1391
1346
  /**
1392
- * Cashu: every unmelted proof in the accumulator, per mint. Rows whose
1393
- * builder-side melt failed are still in the pool (they are still at the
1394
- * mint) and listed as wedged until `restart-failed` clears them.
1347
+ * {@link computeResultHash} over the delivered result; `null` when nothing
1348
+ * was delivered. The summary is part of the hash only on `completed` — see
1349
+ * {@link computeResultHash}.
1395
1350
  */
1396
- snapshotCashu(): Promise<void>;
1351
+ result_hash: string | null;
1352
+ /** x-only secp256k1 pubkey that signed this receipt (64 hex chars). */
1353
+ receipt_pubkey: string;
1354
+ /** BIP-340 Schnorr signature over the canonical receipt minus `signature` (128 hex chars). */
1355
+ signature: string;
1397
1356
  /**
1398
- * x402: the open batch (claimed, not yet transferred) plus every channel's
1399
- * unclaimed voucher value; wedged when the settle keeps failing, and the
1400
- * refund settlements stuck between chain and ledger beside it.
1357
+ * Credit-draw countersignature — present when the job's payment
1358
+ * funded/drew the credit ledger. See {@link ReceiptCredit}. `balance_after`
1359
+ * reflects the terminal resolution: the recorded draw trajectory on a
1360
+ * settled (completed) job, the restored available balance on a released
1361
+ * (failed/cancelled) one.
1401
1362
  */
1402
- snapshotX402(): Promise<void>;
1363
+ credit?: ReceiptCredit;
1364
+ }
1365
+ type UnsignedJobReceipt = Omit<JobReceipt, "signature">;
1366
+ type DrainReceiptEvent = "requested" | "parked" | "picked_up" | "sent" | "released";
1367
+ /**
1368
+ * A DVM-signed proof of one reclaim event. One receipt per
1369
+ * lifecycle transition — `requested` (the balance debited into the drain
1370
+ * liability), `parked` (Cashu notes locked to the caller's refund key,
1371
+ * parked for pickup), `picked_up` (the caller collected them), `sent` (a
1372
+ * Lightning/x402/mpp send-back settled).
1373
+ *
1374
+ * The evidence pairing: the caller holds the drain *request* it signed (the
1375
+ * `/v1/credit` envelope), the DVM countersigns each state the drain reaches.
1376
+ * A `requested` receipt with no later `parked`/`sent` is a signed IOU whose
1377
+ * refusal is provable from artifacts both sides already hold — reclaim is
1378
+ * enforced by reputation with cryptographic evidence, not new crypto.
1379
+ *
1380
+ * Signed by the same per-DVM receipt key as {@link JobReceipt}, over the
1381
+ * same canonicalisation, so one attestation chain covers both.
1382
+ */
1383
+ interface DrainReceipt {
1384
+ v: 1;
1385
+ /** Discriminator separating this from a `JobReceipt` in mixed stores. */
1386
+ kind: "credit_drain";
1387
+ credit_id: string;
1388
+ /** This drain's identifier — matches the caller-signed drain request. */
1389
+ drain_id: string;
1390
+ event: DrainReceiptEvent;
1391
+ /** Payout rail: `cashu` | `x402` | `tempo`. */
1392
+ method: string;
1393
+ /** Amount reclaimed, micro-units of the credit's currency. */
1394
+ amount_micro: number;
1403
1395
  /**
1404
- * Tempo: each active channel's spent-but-unsettled balance — earned, not
1405
- * yet paid to the recipient — and the cooperative closes wedged between
1406
- * chain and ledger, aged past the same floor the repair queue
1407
- * uses so an in-flight close is not reported as stuck.
1396
+ * Credit balance left after the drain debit, in micro-units. It includes
1397
+ * pending job holds at request time and is fixed across this drain's event receipts.
1408
1398
  */
1409
- snapshotTempo(): Promise<void>;
1399
+ balance_after_micro: number;
1400
+ /** Per-credit monotonic sequence the drain consumed, shared with draws. */
1401
+ ledger_seq: number;
1402
+ /** Unix seconds at which this receipt was signed. */
1403
+ issued_at: number;
1404
+ /** The credit owner's x-only pubkey — who the money is owed to. */
1405
+ caller_pubkey: string;
1406
+ /** Immutable DVM identity bound into the builder attestation. */
1407
+ dvm_id: string;
1408
+ /** DVM slug, as bound in the builder attestation. */
1409
+ dvm: string;
1410
+ /** x-only secp256k1 pubkey that signed this receipt (64 hex chars). */
1411
+ receipt_pubkey: string;
1412
+ /** BIP-340 Schnorr signature over the canonical receipt minus `signature`. */
1413
+ signature: string;
1414
+ }
1415
+ type UnsignedDrainReceipt = Omit<DrainReceipt, "signature">;
1416
+ /**
1417
+ * A DVM-signed proof that one payment funded a prepaid credit.
1418
+ * The balance and sequence are the values fixed when the funding committed;
1419
+ * retries return this original artifact even when the credit moved later.
1420
+ */
1421
+ interface FundingReceipt {
1422
+ v: 1;
1423
+ /** Discriminator separating funding evidence from job and drain receipts. */
1424
+ kind: "credit_funding";
1425
+ credit_id: string;
1426
+ /** Caller fund id, or the attached rail payment id for fund-and-draw. */
1427
+ fund_id: string;
1428
+ /** Amount credited, in micro-units of the credit currency. */
1429
+ funded_micro: number;
1430
+ /** Available balance immediately after this funding operation committed. */
1431
+ balance_after_micro: number;
1432
+ /** Per-credit high-water sequence at that same point. */
1433
+ ledger_seq: number;
1434
+ /** Unix seconds at which the funding artifact was first issued. */
1435
+ issued_at: number;
1436
+ /** The credit owner's x-only request-signing pubkey. */
1437
+ caller_pubkey: string;
1438
+ /** Immutable DVM identity bound into the builder attestation. */
1439
+ dvm_id: string;
1440
+ /** DVM slug, as bound in the builder attestation. */
1441
+ dvm: string;
1442
+ receipt_pubkey: string;
1443
+ /** BIP-340 Schnorr signature over the canonical receipt minus `signature`. */
1444
+ signature: string;
1445
+ }
1446
+ type UnsignedFundingReceipt = Omit<FundingReceipt, "signature">;
1447
+ /**
1448
+ * Hash the delivered result so a receipt binds to *what* was returned, not
1449
+ * just that something was.
1450
+ *
1451
+ * `sha256(canonicalize({ summary, artifact_hashes }))`, where each artifact
1452
+ * hash is `sha256(canonicalize(artifactContent))` in emission order (`[]`
1453
+ * when the job emitted none). Hashing the artifact *content object* rather
1454
+ * than trusting `content.sha256` — which is optional and which `ctx.artifact`
1455
+ * never fills in — keeps the hash recomputable by any consumer from exactly
1456
+ * the messages it received.
1457
+ *
1458
+ * **Pass `summary` only for a `completed` job.** On `failed`/`cancelled` the
1459
+ * SDK's durable terminal writes the terminal *reason* into the job's summary
1460
+ * column, so a verifier recomputing a non-completed receipt must pass
1461
+ * `undefined` here and read the reason off `receipt.reason` instead. The
1462
+ * issuer does the same (`resultHashFor` in `sdk/server/receipt-issuer.ts`).
1463
+ */
1464
+ declare function computeResultHash(summary: string | undefined, artifactContents: JsonValue[]): string;
1465
+ declare function signReceipt(unsigned: UnsignedJobReceipt, secretHex: string): JobReceipt;
1466
+ /**
1467
+ * Verify a receipt's self-consistency: the BIP-340 signature over the
1468
+ * canonical payload (minus `signature`) under the receipt's own
1469
+ * `receipt_pubkey`. Returns `false` rather than throwing on malformed input.
1470
+ *
1471
+ * This is only the inner link. A consumer that wants provenance must also
1472
+ * check `receipt_pubkey` against `/v1/info#builder`'s attested
1473
+ * `receipt_pubkey` and verify that attestation under the builder pubkey
1474
+ * (`verifyAttestation` in `builder-identity.ts`).
1475
+ */
1476
+ declare function verifyReceipt(receipt: JobReceipt): boolean;
1477
+ /**
1478
+ * Structural guard on a receipt that arrived from outside this process — off a
1479
+ * `/v1/job` response, or back out of the hand-editable `~/.dvm/receipts.jsonl`.
1480
+ *
1481
+ * Checks only that the load-bearing fields are the right *kind* of thing; it
1482
+ * deliberately does not judge the receipt. A well-formed receipt whose
1483
+ * signature doesn't hold is `invalid` and the caller must see it said so. A
1484
+ * body missing one of these fields is a different animal: consumers dereference
1485
+ * them unconditionally (`receiptDisplay`'s `paid.msats`, the attestation link's
1486
+ * `receipt_pubkey`), so an unguarded one throws `TypeError` and surfaces as an
1487
+ * `internal` exit — and it could never have verified anyway, since the
1488
+ * signature covers the whole canonical body. Refusing it costs no proof.
1489
+ *
1490
+ * One guard for both directions on purpose: what can't come in over the wire
1491
+ * must not be able to come back out of the file.
1492
+ *
1493
+ * Release coupling: adding or requiring a field here changes both the SDK's
1494
+ * emitted wire shape and the caller CLI's accepted shape. Name the first-party
1495
+ * fleet redeploy as a prerequisite in the release note before publishing the
1496
+ * stricter caller; see `public compatibility guide`.
1497
+ */
1498
+ declare function isSignedJobReceipt(value: unknown): value is JobReceipt;
1499
+ /** Sign an unsigned drain receipt — same canonicalisation as {@link signReceipt}. */
1500
+ declare function signDrainReceipt(unsigned: UnsignedDrainReceipt, secretHex: string): DrainReceipt;
1501
+ /** Sign an unsigned funding receipt using the common receipt canonicalisation. */
1502
+ declare function signFundingReceipt(unsigned: UnsignedFundingReceipt, secretHex: string): FundingReceipt;
1503
+ declare function isFundingReceipt(value: unknown): value is FundingReceipt;
1504
+ declare function verifyFundingReceipt(receipt: FundingReceipt): boolean;
1505
+ /**
1506
+ * Structural guard on a drain receipt from outside this process — off a
1507
+ * `/v1/credit` drain response, or back out of `~/.dvm/credits.json`.
1508
+ *
1509
+ * Same contract and same reasoning as {@link isSignedJobReceipt}: both homes
1510
+ * type these `unknown[]` because neither the wire nor a hand-editable file is
1511
+ * evidence of shape, and the chain walk dereferences `receipt_pubkey`
1512
+ * unconditionally. A body missing one of these fields could never have carried
1513
+ * a valid signature anyway, so refusing it costs no proof.
1514
+ * Required-field changes follow the fleet-before-caller release note linked
1515
+ * from {@link isSignedJobReceipt}.
1516
+ */
1517
+ declare function isDrainReceipt(value: unknown): value is DrainReceipt;
1518
+ /**
1519
+ * Verify a drain receipt's self-consistency, mirroring {@link verifyReceipt}
1520
+ * the same provenance caveat applies: check `receipt_pubkey` against the
1521
+ * DVM's attested `receipt_pubkey` for the outer link.
1522
+ */
1523
+ declare function verifyDrainReceipt(receipt: DrainReceipt): boolean;
1524
+
1525
+ /**
1526
+ * Cashu and Lightning credits owe unused sats back at each deposit's funding
1527
+ * rate. Keep the sats/micro basis per funding lot: a pooled ratio loses the
1528
+ * basis of top-ups made at different rates. Settled draws and reclaims share
1529
+ * these lots so their total equals the sats funded.
1530
+ */
1531
+ /**
1532
+ * The rails whose credits carry a Bitcoin deposit this ledger owes back in
1533
+ * kind — Cashu and Lightning funding, both of which leave sats in the
1534
+ * provider's own custody.
1535
+ *
1536
+ * Declared as its own list rather than derived by subtracting the stablecoin
1537
+ * rails: a channel-backed credit is in-kind *structurally* (the escrow retains
1538
+ * the unused value), so it needs no lots at all, and a rail added to
1539
+ * {@link FundingMethod} must be a decision here rather than a default.
1540
+ */
1541
+ declare const NON_CHANNEL_BITCOIN_RAILS: readonly ["cashu", "lightning"];
1542
+ type NonChannelBitcoinRail = (typeof NON_CHANNEL_BITCOIN_RAILS)[number];
1543
+ declare function isNonChannelBitcoinRail(rail: string | null | undefined): rail is NonChannelBitcoinRail;
1544
+ /**
1545
+ * One funding event's deposit: the sats that arrived and the credit micro they
1546
+ * bought, with however much of that micro is still unspent.
1547
+ *
1548
+ * `remainingMicro` is depleted FIFO by settled draws and by reclaims, so the
1549
+ * sum of a credit's lots tracks `credits.balance_micro` exactly — with one
1550
+ * deliberate exception: an expiry release zeroes the balance and
1551
+ * leaves the lots standing, because a revival restores that balance and its
1552
+ * in-kind basis has to still be there. A released credit is excluded from the
1553
+ * sweep floor by `BITCOIN_CREDIT_PREDICATE`'s own `balance_micro > 0`, so the
1554
+ * over-coverage is never counted as sats the builder owes back.
1555
+ */
1556
+ interface FundingLot {
1557
+ lotId: string;
1558
+ creditId: string;
1559
+ rail: NonChannelBitcoinRail;
1560
+ /** Sats this funding delivered. `0` on a credit funded before a rail basis was recorded. */
1561
+ satsFunded: number;
1562
+ /** Credit micro this funding bought — the lot's denominator, never depleted. */
1563
+ creditedMicro: number;
1564
+ remainingMicro: number;
1565
+ /** Rail settlement reference, provenance only. */
1566
+ fundingRef: string | null;
1567
+ createdAt: number;
1568
+ }
1569
+ interface LotDebit {
1570
+ lotId: string;
1571
+ micro: number;
1572
+ /** Decrease in this lot's floored sats obligation after taking the micro debit. */
1573
+ sats: number;
1574
+ }
1575
+ interface LotDepletion {
1576
+ debits: LotDebit[];
1577
+ satsOwed: number;
1578
+ /** Micro the lots could not cover. Non-zero means the credit's lots are short. */
1579
+ uncoveredMicro: number;
1580
+ /** Of the micro taken, how much came out of a lot carrying a real sats basis. */
1581
+ backedMicro: number;
1410
1582
  }
1411
-
1412
1583
  /**
1413
- * JSON wire shape POSTed to the platform's `/_internal/job-revenue` endpoint
1414
- * and stored in `pending_dvm_reports.payload` for durable retry.
1584
+ * Deplete lots in FIFO order, supplied by fifoOrder. Price each debit as
1585
+ * lotSats(before) - lotSats(after), not the independently floored slice: small
1586
+ * draws then telescope to the full deposit instead of losing up to one sat
1587
+ * per draw. Fractional remaining sats stay with the provider.
1415
1588
  */
1416
- interface RevenueReportPayload {
1417
- /** Platform DVM record ID (`dvms.id`). Identifies which DVM earned the revenue. */
1418
- dvmId: string;
1419
- /** Job identifier used by the platform to de-duplicate revenue rows and link to the job record. */
1420
- jobId: string;
1421
- /**
1422
- * Capability selected by dispatch for this job. Omitted for
1423
- * reports from older SDKs and legacy persisted jobs that predate capability
1424
- * dispatch; absence must not be rewritten as a capability name.
1425
- */
1426
- capability?: string;
1427
- /** Total amount paid by the caller in millisatoshis, across all credits that satisfied the job. */
1428
- paidMsats: number;
1429
- /** Cashu mint URL from which the payment tokens were issued. Set only for the `cashu` rail; omitted for all other rails. */
1430
- paymentMint?: string;
1431
- /**
1432
- * Payment rail identifier (`"cashu"`, `"tempo"`, `"x402"`, `"stripe"`).
1433
- * Mirrors `PaymentMethod` on the platform; carried as a plain string over the wire
1434
- * so the SDK has no platform-type dependency.
1435
- */
1436
- rail: string;
1437
- /**
1438
- * Settlement reference for the credit: EVM tx hash for `x402`, credential
1439
- * challenge ID for `tempo`, `X-Cashu-Request-Id` UUID for `cashu`. Required by the
1440
- * platform revenue ledger for `tempo`/`x402` rails (recordJobRevenue throws without it);
1441
- * nullable for `cashu`.
1442
- */
1443
- paymentTxHash?: string;
1444
- /**
1445
- * Rail-native payment amount in atomic units: USDC microunits for `tempo` and
1446
- * `x402`, USD cents for `stripe`. Omitted for `cashu` (platform derives
1447
- * `paidMsats / 1000`). Must be set together with `nativeAsset`.
1448
- */
1449
- nativeAmount?: number;
1450
- /**
1451
- * Asset tag paired with `nativeAmount`: `"sats"`, `"usdc"`, `"usdc.e"`, or
1452
- * `"usd-cents"`. Both fields must be present for the platform to write a non-cashu
1453
- * revenue row.
1454
- */
1455
- nativeAsset?: string;
1456
- /**
1457
- * Cashu flow discriminator. Set to `"p2pk_accumulator"` when the cashu
1458
- * accumulator path satisfied the job; omitted for legacy cashu. Written into
1459
- * `revenue_events.metadata.cashu_flow` by the platform so ops can split per-call cashu
1460
- * rows by source.
1461
- */
1462
- cashuFlow?: string;
1463
- /**
1464
- * What the job cost the **builder** to serve, as declared by the
1465
- * handler through `ctx.cost()` — the other half of the
1466
- * margin the dashboard states. `amountMicro` is 1e-6 of `currency`, which
1467
- * is the currency the builder incurred the cost in and need not be the
1468
- * credit's.
1469
- *
1470
- * Never netted against `paidMsats` or `drawAmountMicro`: what the caller
1471
- * paid and what serving them cost are two questions, so they are two fields.
1472
- *
1473
- * **Absent means unreported, not zero.** A handler that declares nothing
1474
- * sends no `cost` key at all, and the platform shows the margin as unknown
1475
- * rather than as 100%. A handler that declares `{ amount: 0 }` is making the
1476
- * opposite, positive claim and sends `amountMicro: 0`.
1477
- *
1478
- * Additive on the wire: a platform build that does not read this key keeps
1479
- * accepting the report unchanged.
1480
- */
1481
- cost?: {
1482
- amountMicro: number;
1483
- currency: string;
1484
- };
1485
- /**
1486
- * Credit this job's payment drew against. Present on
1487
- * every job a ledger-backed payment satisfied; its presence is what tells
1488
- * the platform the row is a **draw-keyed** revenue event rather than a
1489
- * pre-credits per-call one.
1490
- */
1491
- creditId?: string;
1492
- /** The draw this job settled — one draw, one revenue row. */
1493
- drawId?: string;
1494
- /** The draw's fiat amount, 1e-6 of `creditCurrency`. The exact liability offset. */
1495
- drawAmountMicro?: number;
1496
- /** Currency the credit is denominated in (lowercase ISO, e.g. `usd`). */
1497
- creditCurrency?: string;
1498
- /**
1499
- * Settlement instant (epoch ms) — draws are revenue *when they
1500
- * settle*, so this dates the row. Omitted for pre-credits rows, which the
1501
- * platform dates at record time as before.
1502
- */
1503
- settledAt?: number;
1504
- /** The funding's own rail reference, for reconciling a draw back to its deposit. */
1505
- fundingRef?: string;
1506
- /**
1507
- * Revenue class when this row is not ordinary service revenue.
1508
- * `short_pay_forfeit` marks money kept from an underpayment that bought no
1509
- * job so analytics can exclude it.
1510
- */
1511
- kind?: string;
1512
- }
1513
- /** Privacy-preserving terminal evidence; contains no caller or payment references. */
1514
- interface JobTerminalReportPayload {
1515
- dvmId: string;
1516
- jobId: string;
1517
- capability: string;
1518
- outcome: "completed" | "failed" | "cancelled";
1519
- endedBy: "caller" | "provider" | null;
1520
- paid: boolean;
1521
- acceptedAtMs: number;
1522
- terminalAtMs: number;
1523
- callerWaitMs: number;
1524
- receiptSeq: number | null;
1525
- }
1589
+ declare function depleteLots(lots: FundingLot[], amountMicro: number): LotDepletion;
1526
1590
  /**
1527
- * JSON wire shape POSTed to the platform's `/_internal/job-cost` endpoint
1528
- * when a terminal job incurred a declared builder cost but produced no
1529
- * revenue report.
1591
+ * What a settling draw's fiat debit was worth in kind, in millisatoshis
1592
+ * `null` where the lots cannot price it.
1530
1593
  *
1531
- * Unlike {@link RevenueReportPayload}, this event has no payment rail or
1532
- * revenue amount: a failed, cancelled, or free job is not a sale. The
1533
- * platform upserts it by `(dvmId, jobId)`, independently of the revenue
1534
- * ledger's rail-keyed constraint. It replaces the stored value only when
1535
- * `costRevision` is higher; equal revisions must be byte-identical. That rule
1536
- * makes a persisted prefix and a later in-memory total converge regardless of
1537
- * delivery order.
1594
+ * This is the single sats authority for a non-channel Bitcoin credit. The
1595
+ * settle already depletes the lots the draw consumed, so the figure costs no
1596
+ * extra read: it is `depletion.satsOwed` in the ledger's own msat unit, and
1597
+ * stamping it on `credit_draws.draw_msats` is what makes
1598
+ * `Σ settled draw sats + reclaim sats == Σ sats funded` close per credit.
1599
+ *
1600
+ * `null` follows {@link isInKindDepletion} exactly, so a settle and a reclaim
1601
+ * fall back together: lots short of the balance, or covering lots with no sats
1602
+ * basis, keep the pooled pro-rata figure rather than blending a lot rate with
1603
+ * a blended one. Mixed coverage counts as unpriceable for the same reason it
1604
+ * does on the reclaim.
1538
1605
  */
1539
- interface JobCostReportPayload {
1540
- /** Platform DVM record ID (`dvms.id`); bound to the authenticating token. */
1541
- dvmId: string;
1542
- /** Job identifier and the report's platform-side idempotency key. */
1543
- jobId: string;
1544
- /** Capability selected by dispatch; absent only on legacy persisted jobs. */
1545
- capability?: string;
1546
- /** Terminal outcome that explains why this event carries no revenue. */
1547
- outcome: "completed" | "failed" | "cancelled";
1548
- /**
1549
- * Number of declarations incorporated into `cost`; the
1550
- * platform keeps the payload with the highest revision for this job.
1551
- */
1552
- costRevision: number;
1553
- /**
1554
- * The builder's cumulative declared cost, in millionths of its currency.
1555
- * `null` at a higher revision invalidates an earlier prefix when the handler
1556
- * switched currencies and the final total is therefore unreportable.
1557
- */
1558
- cost: {
1559
- amountMicro: number;
1560
- currency: string;
1561
- } | null;
1562
- }
1606
+ declare function inKindDrawMsats(depletion: LotDepletion, amountMicro: number): number | null;
1563
1607
  /**
1564
- * JSON wire shape POSTed to the platform's `/_internal/credit-deposit`
1565
- * endpoint. A funding event is a **deposit** — a liability until
1566
- * drawn — reported separately and never summed into revenue (the deposit-accounting rule).
1608
+ * What these lots owe in kind if every remaining micro were reclaimed now —
1609
+ * the deposit-liability figure, and the floor the hub must never be swept
1610
+ * below.
1611
+ */
1612
+ declare function lotOwedSats(lots: FundingLot[]): number;
1613
+ /**
1614
+ * Refund delivery uses a fixed eight-sat reserve so owed_sats cannot shrink
1615
+ * after it is promised. Extra delivery cost is absorbed by the provider;
1616
+ * unused reserve stays with the provider, like fractional-sat dust.
1617
+ */
1618
+ declare const DRAIN_DELIVERY_RESERVE_SATS = 8;
1619
+ /**
1620
+ * The figure a reclaim publishes: its in-kind gross less
1621
+ * {@link DRAIN_DELIVERY_RESERVE_SATS}, floored at zero.
1567
1622
  *
1568
- * Idempotent on `(dvmId, creditId, fundingId)`, so the shared retry loop can
1569
- * redeliver freely.
1623
+ * Zero is a refusal rather than a payout — a balance worth no more than what
1624
+ * it costs to send is `drain_below_dust`, since parking a zero-value token
1625
+ * would hold the servicer's queue open forever.
1570
1626
  */
1571
- interface CreditDepositPayload {
1572
- /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
1573
- dvmId: string;
1574
- creditId: string;
1575
- /** Canonical owner of the funded credit. */
1576
- callerPubkey: string;
1577
- /** The rail's settlement reference — the deposit's idempotency key. */
1578
- fundingId: string;
1579
- /** Payment rail (`"cashu"`, `"tempo"`, `"x402"`). */
1580
- rail: string;
1581
- /** Rail value received, in millisatoshis. */
1582
- paidMsats: number;
1583
- /** Cashu only. Full received proof face before its receiver input fee. */
1584
- grossPaidMsats?: number;
1585
- /** Cashu only. Standalone NUT-02 fee reserved by the received proofs. */
1586
- cashuInputFeeMsats?: number;
1587
- /** Rail-native atomic units; omitted for cashu (derived from `paidMsats`). */
1588
- nativeAmount?: number;
1589
- /** Asset tag paired with `nativeAmount`. */
1590
- nativeAsset?: string;
1591
- /** Cashu mint URL the funding proofs were issued from. */
1592
- paymentMint?: string;
1593
- /** Fiat micro credited to the ledger — the liability this deposit creates. */
1594
- amountMicro: number;
1595
- /** Currency `amountMicro` is 1e-6 of. */
1596
- creditCurrency: string;
1597
- /** When the funding committed (epoch ms). */
1598
- fundedAt: number;
1599
- /** Current credit expiry after this funding committed (epoch ms). */
1600
- expiryMs: number;
1601
- /**
1602
- * Tempo session funding only: the TIP-1034 channel this deposit
1603
- * arrived on, registered so the platform's close observer can route a
1604
- * `CloseRequested` event back to the DVM that owns the channel.
1605
- *
1606
- * Rides the deposit report rather than a side channel on purpose — accepting
1607
- * the voucher and booking the registration then share the one durable commit
1608
- * boundary, so a registered channel can never be a channel whose funding
1609
- * rolled back, and the existing retry drain delivers it after a crash. The
1610
- * registration is therefore asynchronous relative to the close: an event can
1611
- * legitimately arrive first, which is what the platform's payee fallback is
1612
- * for.
1627
+ declare function netOwedSats(grossSats: number): number;
1628
+ /**
1629
+ * The order lots deplete in: oldest first, `lotId` breaking a tie.
1630
+ *
1631
+ * A tiebreak is not decoration — lot ids are surrogates rather than a
1632
+ * per-credit sequence (`fund` takes no credit lock, so two concurrent
1633
+ * fundings cannot cooperate on a counter), and two fundings can land on the
1634
+ * same millisecond. Without it the FIFO order is whatever the planner
1635
+ * returned, and a reclaim's figure would depend on it.
1636
+ */
1637
+ declare function fifoOrder(left: FundingLot, right: FundingLot): number;
1638
+ /**
1639
+ * Missing lot coverage or a zero legacy sats basis requires pooled pricing.
1640
+ * Mixed backed/unbacked lots also fall back: combining lot and live rates
1641
+ * would claim a value neither basis supports.
1642
+ */
1643
+ declare function isInKindDepletion(depletion: LotDepletion, amountMicro: number): boolean;
1644
+
1645
+ /** The rails a payout lands on. Lightning never pays out: credits fund straight into the receive wallet. */
1646
+ type PayoutRail = "cashu" | "x402" | "tempo";
1647
+ /**
1648
+ * Transfers aggregate payments: a Cashu melt, x402 batch to pay_to, or Tempo recipient
1649
+ * settlement/close.
1650
+ */
1651
+ type PayoutKind = "melt" | "batch" | "close" | "settle";
1652
+ type PayoutRepairKind = "x402_settlement_reconciled" | "lightning_invoice_reconciled" | "tempo_close_reconciled";
1653
+ /**
1654
+ * One landed movement into builder custody. Platform retries deduplicate by (dvmId,
1655
+ * payoutId); repaired redelivery upgrades that movement rather than adding another.
1656
+ */
1657
+ interface PayoutReportPayload {
1658
+ /**
1659
+ * Platform record ID; must match the bearer token’s DVM.
1613
1660
  */
1614
- tempoChannel?: {
1615
- chainId: number;
1616
- /** 32-byte channel id, `0x`-prefixed and lowercased. */
1617
- channelId: string;
1618
- /** TIP-1034 escrow the channel was opened against. */
1619
- escrow: string;
1620
- /** The DVM's on-chain recipient — the fallback routing key. */
1621
- payee?: string;
1622
- };
1661
+ dvmId: string;
1662
+ /** Required. The DVM's own id for the movement — `cashu:melt:<quote>`, `x402:batch:<uuid>`, `tempo:<tx>`. */
1663
+ payoutId: string;
1664
+ rail: PayoutRail;
1665
+ kind: PayoutKind;
1623
1666
  /**
1624
- * Tempo session funding only: which channel this deposit's voucher
1625
- * was accepted on, emitted **whenever the funding was session-based** —
1626
- * independently of whether {@link CreditDepositPayload.tempoChannel} could be
1627
- * read. That independence is the entire point: `rail: "tempo"` covers both
1628
- * `tempo/session` and the `tempo/charge` one-shot, and a charge legitimately
1629
- * carries no channel at all, so without this the platform cannot tell a
1630
- * routine charge deposit from a session whose chain binding was lost — the
1631
- * shape, where the close observer silently never watches the escrow.
1632
- *
1633
- * Carries only the channel id, not the chain binding: a DVM that could read
1634
- * the binding sends `tempoChannel` too, and one that could not still names
1635
- * the channel an operator has to go and look at.
1667
+ * Positive rail-native atomic units: sats or USDC micro.
1636
1668
  */
1637
- tempoSession?: {
1638
- /** 32-byte channel id, `0x`-prefixed and lowercased. */
1639
- channelId: string;
1640
- };
1669
+ nativeAmount: number;
1670
+ nativeAsset: "sats" | "usdc";
1671
+ /** Where it landed; `null` for a melt, whose Lightning destination the platform never records. */
1672
+ recipient: string | null;
1641
1673
  /**
1642
- * Set by the blocked-invoice repair: this deposit is a paid
1643
- * Lightning invoice the builder's agent reconciled onto a credit. The money
1644
- * landed in the receive wallet at payment time and this deposit is the one
1645
- * row it gets, so the settlements table types it `repair` off this flag
1646
- * rather than off a second row.
1674
+ * Descriptive mechanism references, never an arithmetic basis.
1647
1675
  */
1676
+ refs: Record<string, unknown>;
1677
+ /** Required. When the money landed (epoch ms). */
1678
+ landedAt: number;
1648
1679
  repaired?: boolean;
1680
+ /** Required iff `repaired`. */
1681
+ repairKind?: PayoutRepairKind;
1649
1682
  }
1650
- /**
1651
- * JSON wire shape POSTed to the platform's `/_internal/credit-draw-release`
1652
- * endpoint. A released hold leaves the credit balance intact, but
1653
- * marks that slice of the outstanding liability as failure churn rather than
1654
- * untouched prefunding.
1655
- *
1656
- * Idempotent on `(dvmId, creditId, drawId)`, so the reporter queue can
1657
- * redeliver freely.
1658
- */
1659
- interface CreditDrawReleasePayload {
1660
- /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
1661
- dvmId: string;
1662
- creditId: string;
1663
- /** Released draw; the platform idempotency key with DVM and credit. */
1664
- drawId: string;
1665
- /** Fiat micro restored to availability. */
1666
- amountMicro: number;
1667
- /** Currency `amountMicro` is 1e-6 of. */
1668
- creditCurrency: string;
1669
- /** Per-credit gap-free sequence assigned when the draw was placed. */
1670
- ledgerSeq: number;
1671
- /** When the hold reached `released` (epoch ms). */
1672
- releasedAt: number;
1683
+ interface PayoutWedge {
1684
+ id: string;
1685
+ /**
1686
+ * `settle`: an x402 batch whose transfer keeps failing (auto-retried).
1687
+ * `refund`: an x402 cooperative refund wedged between chain and ledger —
1688
+ * caller money, not in the pool. `close`: a Tempo close wedged the same way.
1689
+ * `melt`: cashu rows whose builder-side melt failed.
1690
+ */
1691
+ kind: "settle" | "refund" | "close" | "melt";
1692
+ /** Epoch ms the wedge was first observed. */
1693
+ since: number;
1694
+ /** Retry count where the retry is automatic, else null. */
1695
+ attempts: number | null;
1696
+ /** Epoch ms of the next automatic retry, else null. */
1697
+ nextRetry: number | null;
1698
+ lastError: string | null;
1699
+ /** Rail-native amount held up, when known. */
1700
+ native?: number;
1701
+ }
1702
+ interface PayoutPool {
1703
+ id: string;
1704
+ native: number;
1705
+ label?: string;
1673
1706
  }
1674
- type CreditDrainRepairKind = "x402_settlement_reconciled" | "tempo_close_reconciled";
1675
1707
  /**
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).
1708
+ * A replaceable snapshot of one rail’s pending builder funds. Best-effort delivery uses no
1709
+ * durable queue; a later tick supersedes a lost report.
1679
1710
  */
1680
- interface CreditDrainPayload {
1681
- /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
1711
+ interface PayoutPendingPayload {
1682
1712
  dvmId: string;
1683
- creditId: string;
1684
- /** Canonical owner of the reclaimed credit. */
1685
- callerPubkey: string;
1686
- /** The drain's own idempotency key. */
1687
- drainId: string;
1688
- /** Payout rail (`"cashu"`, `"x402"`, `"tempo"`). */
1689
- method: string;
1690
- /** Fiat micro reclaimed — the liability this drain extinguishes. */
1691
- amountMicro: number;
1692
- /** Currency `amountMicro` is 1e-6 of. */
1693
- creditCurrency: string;
1694
- /** Terminal state: `"picked_up"` for cashu, `"sent"` for other rails. */
1695
- status: string;
1696
- /** When the money left (epoch ms). */
1697
- drainedAt: number;
1698
- /** Per-credit gap-free ledger sequence, for reconciling against the DVM's ledger. */
1699
- ledgerSeq?: number;
1700
- /** Set when an operator repair verb completed this reclaim. */
1701
- repaired?: boolean;
1702
- /** Required iff `repaired`; identifies the repair path that completed the reclaim. */
1703
- repairKind?: CreditDrainRepairKind;
1713
+ rail: PayoutRail;
1714
+ /** Everything on this rail still moving toward the builder, in atomic units. */
1715
+ poolNative: number;
1716
+ nativeAsset: "sats" | "usdc";
1717
+ items: number;
1718
+ pools: PayoutPool[];
1719
+ wedged: PayoutWedge[];
1720
+ /** This DVM's clock when the snapshot was taken (epoch ms). */
1721
+ snapshotAt: number;
1704
1722
  }
1705
- /**
1706
- * Open Postgres transaction handle accepted by the reporter-owned outbox
1707
- * enqueue. The payment rail owns BEGIN/COMMIT; the reporter owns its table.
1708
- */
1709
- interface RevenueReporterQuerier {
1710
- query: Pool["query"];
1723
+ interface PayoutTransport {
1724
+ /**
1725
+ * Queue a payout through the caller's open transaction, so the row and the
1726
+ * fact it reports commit or roll back together; without `tx` it is queued on
1727
+ * its own. Durable once committed — the retry loop delivers it, or
1728
+ * {@link PayoutTransport.drainPending} when a caller wants it now.
1729
+ */
1730
+ enqueuePayout(payload: PayoutReportPayload, tx?: RevenueReporterQuerier): Promise<void>;
1731
+ enqueueHostedPayout?(payload: PayoutReportPayload, obligationIds: string[], tx?: RevenueReporterQuerier): Promise<void>;
1732
+ /** Queue a payout on its own and try to deliver it at once. */
1733
+ reportPayout(payload: PayoutReportPayload): Promise<void>;
1734
+ /** Best-effort: one POST, replaced by the next snapshot. */
1735
+ reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
1736
+ /** Deliver what is queued now rather than on the next retry tick. */
1737
+ drainPending?(): Promise<void>;
1711
1738
  }
1712
1739
  /**
1713
- * Transactional deposit-outbox seam threaded through each rail commit.
1714
- *
1715
- * Implemented by {@link RevenueReporter.enqueueDeposit}; callers must await it
1716
- * before committing the rail receive and ledger funding.
1717
- */
1718
- type CreditDepositEnqueue = (tx: RevenueReporterQuerier, payload: CreditDepositPayload) => Promise<void>;
1719
- /** Transactional draw-release outbox seam used by the credit ledger. */
1720
- type CreditDrawReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload) => Promise<void>;
1721
- /**
1722
- * Transactional drain-outbox seam threaded through each terminal drain
1723
- * transition.
1724
- *
1725
- * Implemented by {@link RevenueReporter.enqueueCreditDrain}; callers must await
1726
- * it before committing the status CAS that moved the money.
1727
- */
1728
- type CreditDrainEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrainPayload) => Promise<void>;
1729
- /**
1730
- * JSON wire shape POSTed to the platform's `/_internal/credit-expiry-release`
1731
- * endpoint. A prepaid credit that reaches its TTL with an undrawn
1732
- * remainder RELEASES that remainder to the builder: on cashu, Lightning and
1733
- * x402 the funding value reached builder custody at funding time or on the
1734
- * settlement cycle, so expiry extinguishes the caller's remaining claim.
1735
- * Reported so committed value nets to
1736
- * `deposited − drawn − drained − released`; without it an expired credit is
1737
- * owed forever on paper, exactly like an unreported drain.
1738
- *
1739
- * Never sent for a Tempo credit. Tempo balances are channel-backed and undrawn
1740
- * channel value returns to the *caller* at exit, so no release exists on that
1741
- * rail — the sweep skips them and the platform refuses one outright.
1742
- *
1743
- * Two reports share this shape, distinguished only by `reversedAt`:
1744
- *
1745
- * - **The release**, written by the expiry sweep with `reversedAt` absent.
1746
- * - **The reversal**, written when a later funding revives the credit
1747
- * (`CreditLedger.fund` overwrites `expiry_ms`, so the recorded remainder is
1748
- * no longer final). It repeats the whole payload rather than sending a
1749
- * reference, because the retry loop can deliver the two out of order and the
1750
- * platform's upsert must be able to land either one first.
1751
- *
1752
- * Idempotent on `(dvmId, creditId, releaseId)`, so the shared retry loop can
1753
- * redeliver freely.
1740
+ * enqueue joins the money transaction and must throw on failure so its fact rolls back.
1741
+ * committed runs after commit, when delivery and snapshots can safely read the new state.
1754
1742
  */
1755
- interface CreditExpiryReleasePayload {
1756
- /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
1757
- dvmId: string;
1758
- creditId: string;
1759
- /** Per-expiry-episode key; the platform idempotency key with DVM and credit. */
1760
- releaseId: string;
1761
- callerPubkey: string;
1762
- /** Funding rail. `null` on a credit funded before the basis became mandatory. */
1763
- rail: string | null;
1764
- /** Fiat micro released — the liability this expiry extinguishes. */
1765
- amountMicro: number;
1766
- /** Currency `amountMicro` is 1e-6 of. */
1767
- creditCurrency: string;
1768
- /** When the credit's TTL ran out (epoch ms) — when the liability ended. */
1769
- expiryMs: number;
1770
- /** When the sweep observed the expiry (epoch ms). */
1771
- releasedAt: number;
1772
- /** Set only on the reversal report: when the revival funding landed (epoch ms). */
1773
- reversedAt?: number | null;
1743
+ interface TransactionalPayoutHook<E> {
1744
+ enqueue(event: E, tx: RevenueReporterQuerier): Promise<void>;
1745
+ committed(): void;
1746
+ }
1747
+ interface CashuMeltCompleted {
1748
+ rows: {
1749
+ id: string;
1750
+ mintUrl: string;
1751
+ proofAmount: number;
1752
+ }[];
1753
+ meltQuoteId: string;
1754
+ paymentPreimage: string;
1755
+ }
1756
+ interface TempoSessionSettled {
1757
+ txHash: string;
1758
+ channelId: string;
1759
+ trigger: "settle" | "close" | "scheduled";
1760
+ /** Cumulative amount settled to the payee on this channel, atomic units. */
1761
+ amount: bigint;
1762
+ /** Newly settled to the payee by this transaction, atomic units. */
1763
+ delta: bigint;
1764
+ }
1765
+ interface TempoCloseReconciled {
1766
+ channelId: string;
1767
+ txHash: string;
1768
+ /** Captured by the payee — this DVM — per the receipt, atomic units as a decimal string. */
1769
+ settledToPayee: string;
1770
+ }
1771
+ interface X402TrackedChannel {
1772
+ channelId: string;
1773
+ chargedCumulativeAmount: string;
1774
+ totalClaimed: string;
1775
+ }
1776
+ interface X402WedgedRefund {
1777
+ settlementId: string;
1778
+ /** Epoch ms the settlement was first prepared. */
1779
+ createdAt: number;
1780
+ native?: number;
1781
+ }
1782
+ interface X402PayoutContext {
1783
+ /** `${network}|${payTo}|${token}` — the scope the settle-pending marker is keyed on. */
1784
+ scope: string;
1785
+ payTo: string;
1786
+ network: string;
1787
+ storage: {
1788
+ list(): Promise<X402TrackedChannel[]>;
1789
+ };
1790
+ /** The scheduler's settle cadence — what a wedge's `nextRetry` is derived from. */
1791
+ settleIntervalMs: number;
1792
+ listWedgedRefunds?: () => Promise<X402WedgedRefund[]>;
1793
+ }
1794
+ interface X402TrackedManager {
1795
+ claim(...args: never[]): Promise<{
1796
+ vouchers: number;
1797
+ transaction: string;
1798
+ }[]>;
1799
+ settle(): Promise<{
1800
+ transaction: string;
1801
+ }>;
1802
+ refund?(channelIds?: string[]): Promise<{
1803
+ channel: string;
1804
+ transaction: string;
1805
+ }[]>;
1774
1806
  }
1775
1807
  /**
1776
- * Transactional expiry-release outbox seam, threaded through the sweep's insert
1777
- * and through the funding that reverses a release.
1778
- *
1779
- * Implemented by {@link RevenueReporter.enqueueCreditExpiryRelease}; callers
1780
- * must await it before committing the write that made the release (or its
1781
- * reversal) true, for the same reason the drain seam does — a report lost
1782
- * between the two overstates or understates committed value forever.
1783
- */
1784
- type CreditExpiryReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload) => Promise<void>;
1785
- /**
1786
- * JSON wire shape POSTed to the platform's `/_internal/paid-job-death` endpoint
1787
- * when the stale-job reaper force-fails a *paid* job — its pending
1788
- * credit draw is released rather than settled, so the caller's payment is back
1789
- * on their credit balance and reclaimable by drain. The platform resolves fiat
1790
- * + owner and sends an ambient notice via the shared alert sink. Unlike
1791
- * {@link RevenueReportPayload} this carries no durable-retry semantics: the
1792
- * reaper's `stale_job_failed` structured log is the durable backstop, so a lost
1793
- * report degrades to log-only rather than blocking the sweep.
1808
+ * The batch-settlement server's view of the reporter: attach once with the
1809
+ * scope, then route every claim and settle through the tracked manager so
1810
+ * the open batch is kept and the settle emits the payout.
1794
1811
  */
1795
- interface PaidJobDeathPayload {
1796
- /** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
1797
- dvmId: string;
1798
- jobId: string;
1799
- capability: string;
1800
- /** Amount of the released credit draw in millisatoshis. */
1801
- paidMsats: number;
1802
- /** Payment rail of the released draw (`"cashu"`, `"tempo"`, `"x402"`). */
1803
- rail: string;
1804
- /** Reaper reason: dead worker or never-terminal idle job. */
1805
- reason: string;
1806
- /** Rail-native released amount, paired with `nativeAsset`. */
1807
- nativeAmount?: number;
1808
- /** Asset tag for `nativeAmount` (`"sats"` / `"usdc"` / …). */
1809
- nativeAsset?: string;
1810
- /** Cashu mint URL the credit draw's tokens were issued from. */
1811
- paymentMint?: string;
1812
+ interface X402PayoutObserver {
1813
+ attach(ctx: X402PayoutContext): void;
1814
+ trackManager<M extends X402TrackedManager>(manager: M): M;
1815
+ /** A settle that landed outside the tracked manager — the manual claim-and-settle's own retry loop. */
1816
+ recordSettle(transaction: string): Promise<void>;
1817
+ /** A settle attempt that failed outside the tracked manager. One call per attempt a builder would count as one. */
1818
+ recordSettleFailure(error: unknown): Promise<void>;
1819
+ /** A credit-drain refund's bundled claim, recorded after its exact pre-effect reservation. */
1820
+ recordRefundClaim?(transaction: string, delta: bigint): Promise<void>;
1821
+ }
1822
+ interface TempoPayoutReader {
1823
+ recipient?: string;
1824
+ /** CAIP-2 network for the refs, when known. */
1825
+ network?: string;
1826
+ listActive?: (limit: number, cursor?: {
1827
+ updatedAt: number;
1828
+ key: string;
1829
+ }) => Promise<{
1830
+ key: string;
1831
+ state: Record<string, unknown>;
1832
+ updatedAt: number;
1833
+ }[]>;
1834
+ listChannelDrains?: (args: {
1835
+ limit: number;
1836
+ after?: ChannelDrainCursor;
1837
+ createdBeforeMs: number;
1838
+ rail: "tempo";
1839
+ }) => Promise<CreditDrainRecord[]>;
1812
1840
  }
1813
- type RevenueSkippedNoRailReason = "credit_rail_null" | "credit_rail_unrecognized" | "job_rail_missing" | "ledger_not_configured";
1814
1841
  /**
1815
- * JSON wire shape POSTed to the platform's `/_internal/revenue-skipped-no-rail`
1816
- * endpoint when a settled credit draw has no usable payment rail
1817
- * for its revenue event. This is an operator notice rather than a durable
1818
- * accounting report: the SDK's structured log remains the durable backstop,
1819
- * and a failed alert must never block terminal handling or reconciliation.
1842
+ * Claims accumulated since the last settle. The batch is durable across machines: claims and
1843
+ * eventual settlement can run on different hosts under the fleet lock.
1820
1844
  */
1821
- interface RevenueSkippedNoRailPayload {
1822
- /** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
1823
- dvmId: string;
1824
- jobId: string;
1825
- creditId: string;
1826
- drawId: string;
1827
- /** Caller payment captured for the job, in millisatoshis. */
1828
- paidMsats: number;
1829
- /** Settled draw amount in 1e-6 of `creditCurrency`. */
1830
- drawAmountMicro?: number;
1831
- /** Currency the draw amount is denominated in. */
1832
- creditCurrency?: string;
1833
- /** Credit funding rail as stored on the draw, or null for legacy rows. */
1834
- creditRail: string | null;
1835
- reason: RevenueSkippedNoRailReason;
1845
+ interface X402OpenBatch {
1846
+ scope: string;
1847
+ batchId: string;
1848
+ claimedNative: bigint;
1849
+ voucherCount: number;
1850
+ claims: number;
1851
+ openedAt: number;
1852
+ attempts: number;
1853
+ firstFailedAt: number | null;
1854
+ lastError: string | null;
1855
+ nextRetryAt: number | null;
1856
+ obligationIds?: string[];
1836
1857
  }
1837
- /** Gas-balance observation POSTed by a platform-hosted x402 self-relay. */
1838
- interface X402SelfRelayGasBalancePayload {
1839
- /** Platform DVM record ID, bound to the authenticating platform token. */
1840
- dvmId: string;
1841
- /** Dedicated relay EOA whose Base ETH balance was read. */
1842
- address: `0x${string}`;
1843
- /** CAIP-2 Base network identifier. */
1844
- network: string;
1845
- /** Current native balance as a decimal wei string. */
1846
- balanceWei: string;
1847
- /** Configured paging floor as a decimal wei string. */
1848
- lowBalanceWei: string;
1849
- /** Millisecond timestamp at which the DVM read the balance. */
1850
- checkedAt: number;
1858
+ interface X402BatchStore {
1859
+ get(scope: string): Promise<X402OpenBatch | undefined>;
1860
+ put(batch: X402OpenBatch): Promise<void>;
1861
+ delete(scope: string): Promise<void>;
1862
+ /**
1863
+ * Atomically delete the matching batch and enqueue its payout. Failed enqueue preserves the
1864
+ * batch for retry; false means the scope no longer holds batchId and nothing was queued.
1865
+ */
1866
+ settle(scope: string, batchId: string, enqueue: (tx?: RevenueReporterQuerier) => Promise<void>): Promise<boolean>;
1851
1867
  }
1852
- /** RPC-health transition POSTed by a platform-hosted x402 self-relay. */
1853
- type X402SelfRelayRpcHealthPayload = {
1854
- /** Platform DVM record ID, bound to the authenticating platform token. */
1855
- dvmId: string;
1856
- /** Dedicated relay EOA identifying the affected self-relay. */
1857
- address: `0x${string}`;
1858
- /** CAIP-2 Base network identifier. */
1859
- network: string;
1860
- /** Healthy transition clears an open RPC page. */
1861
- status: "healthy";
1862
- /** Millisecond timestamp at which the DVM observed the transition. */
1863
- checkedAt: number;
1864
- } | {
1865
- /** Platform DVM record ID, bound to the authenticating platform token. */
1866
- dvmId: string;
1867
- /** Dedicated relay EOA identifying the affected self-relay. */
1868
- address: `0x${string}`;
1869
- /** CAIP-2 Base network identifier. */
1870
- network: string;
1871
- /** Failed transition opens or refreshes an RPC page. */
1872
- status: "failed";
1873
- /** Redacted failure class; never the provider error or endpoint URL. */
1874
- reason: X402SelfRelayRpcFailureReason;
1875
- /** Millisecond timestamp at which the DVM observed the transition. */
1876
- checkedAt: number;
1877
- };
1878
- /** Configured Tempo operator fee-token balance POSTed by a hosted DVM. */
1879
- interface TempoSettlementBalancePayload {
1868
+ interface PayoutReporterOpts {
1880
1869
  dvmId: string;
1881
- address: `0x${string}`;
1882
- network: string;
1883
- token: `0x${string}`;
1884
- balanceMicro: string;
1885
- lowBalanceMicro: string;
1886
- checkedAt: number;
1887
- ready: boolean;
1870
+ transport: PayoutTransport;
1871
+ /**
1872
+ * Postgres holds durable x402 batches and supplies Cashu snapshots. Without it, the default
1873
+ * batch store is memory and Cashu snapshots are omitted.
1874
+ */
1875
+ db?: Pool;
1876
+ /**
1877
+ * Only accumulator hosts create wallet_accumulator; enabling this on another host makes
1878
+ * each snapshot query fail.
1879
+ */
1880
+ cashu?: boolean;
1881
+ /** Override the batch store (tests); defaults to Postgres on `db`, memory without. */
1882
+ batchStore?: X402BatchStore;
1883
+ hostedObligations?: HostedSettlementReportObligations;
1884
+ /** Snapshot cadence, ms. Default 5 minutes. */
1885
+ snapshotIntervalMs?: number;
1886
+ now?: () => number;
1888
1887
  }
1889
- /** Redacted Tempo operator fee-funding rejection POSTed by a hosted DVM. */
1890
- interface TempoSettlementFailurePayload {
1891
- dvmId: string;
1892
- address: `0x${string}`;
1893
- network: string;
1894
- token: `0x${string}`;
1895
- operation: "settle" | "close";
1896
- errorClass: "InsufficientFundsError";
1897
- checkedAt: number;
1898
- balanceMicro?: string;
1899
- channelId?: string;
1900
- creditId?: string;
1901
- drainId?: string;
1888
+ /**
1889
+ * Cashu melts and repaired Tempo closes enqueue inside their caller’s transaction and
1890
+ * propagate failures. x402 reporting and pending snapshots instead log failures; failed x402
1891
+ * enqueue preserves the open batch for retry.
1892
+ */
1893
+ declare class PayoutReporter implements PayoutTransport {
1894
+ private readonly dvmId;
1895
+ private readonly transport;
1896
+ private readonly db?;
1897
+ private readonly cashu;
1898
+ private readonly batches;
1899
+ private readonly ownsBatchStore;
1900
+ private readonly intervalMs;
1901
+ private readonly now;
1902
+ private readonly hostedObligations?;
1903
+ private lastX402SnapshotAt;
1904
+ private x402?;
1905
+ private tempo?;
1906
+ private timer;
1907
+ private warmup;
1908
+ constructor(opts: PayoutReporterOpts);
1909
+ init(): Promise<void>;
1910
+ /**
1911
+ * Start the snapshot loop. Unref'd, so it never keeps a scale-to-zero
1912
+ * machine awake; a first snapshot runs shortly after boot so a fresh process
1913
+ * reports without waiting a whole interval.
1914
+ */
1915
+ start(): void;
1916
+ stop(): void;
1917
+ private reconcileHosted;
1918
+ enqueuePayout(payload: PayoutReportPayload, tx?: RevenueReporterQuerier): Promise<void>;
1919
+ reportPayout(payload: PayoutReportPayload): Promise<void>;
1920
+ reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
1921
+ /** Deliver what is queued now; a transport without the seam waits for its retry loop. */
1922
+ flush(): Promise<void>;
1923
+ meltHook(): TransactionalPayoutHook<CashuMeltCompleted>;
1924
+ /**
1925
+ * Report melted face value per mint, not the Lightning amount received after fees. Hash the
1926
+ * payment preimage; do not publish it. With tx, propagate enqueue failures and defer
1927
+ * delivery until commit. Without tx, enqueue independently and start delivery.
1928
+ */
1929
+ cashuMelted(event: CashuMeltCompleted, tx?: RevenueReporterQuerier): Promise<void>;
1930
+ attachTempo(reader: TempoPayoutReader): void;
1931
+ /**
1932
+ * A Tempo channel settled or closed on chain. `delta` is what this
1933
+ * transaction newly paid the recipient; a transaction that paid nothing new
1934
+ * (a close of a fully settled channel) is no payout.
1935
+ */
1936
+ tempoSettled(event: TempoSessionSettled): Promise<void>;
1937
+ tempoRepairHook(): TransactionalPayoutHook<TempoCloseReconciled>;
1938
+ /**
1939
+ * Report the receipt’s payee total under the live close’s transaction ID, flagged repaired.
1940
+ * The platform upgrades a live row if one exists. Join tx and propagate enqueue failures;
1941
+ * this does not report the caller refund.
1942
+ */
1943
+ tempoCloseReconciled(event: TempoCloseReconciled, tx?: RevenueReporterQuerier): Promise<void>;
1944
+ x402Observer(): X402PayoutObserver;
1945
+ /**
1946
+ * Read claim deltas from storage because upstream returns voucher counts. Under the fleet
1947
+ * lock, before/after reads are consistent. A deleted channel contributes its pre-claim
1948
+ * unclaimed value, including refund claim-then-delete.
1949
+ */
1950
+ private trackX402Manager;
1951
+ private recordX402Claim;
1952
+ private recordX402Settle;
1953
+ private recordX402SettleFailure;
1954
+ /** Every rail this reporter can read, each on its own failure boundary. */
1955
+ snapshot(): Promise<void>;
1956
+ /**
1957
+ * Cashu: every unmelted proof in the accumulator, per mint. Rows whose
1958
+ * builder-side melt failed are still in the pool (they are still at the
1959
+ * mint) and listed as wedged until `restart-failed` clears them.
1960
+ */
1961
+ snapshotCashu(): Promise<void>;
1962
+ /**
1963
+ * x402: the open batch (claimed, not yet transferred) plus every channel's
1964
+ * unclaimed voucher value; wedged when the settle keeps failing, and the
1965
+ * refund settlements stuck between chain and ledger beside it.
1966
+ */
1967
+ snapshotX402(): Promise<void>;
1968
+ /**
1969
+ * Tempo: each active channel's spent-but-unsettled balance — earned, not
1970
+ * yet paid to the recipient — and the cooperative closes wedged between
1971
+ * chain and ledger, aged past the same floor the repair queue
1972
+ * uses so an in-flight close is not reported as stuck.
1973
+ */
1974
+ snapshotTempo(): Promise<void>;
1902
1975
  }
1976
+
1903
1977
  /**
1904
- * Durable revenue reporter for container-runtime DVMs.
1905
- *
1906
- * On job completion the SDK calls `report()`, which persists the payload to
1907
- * a local Postgres table and attempts an immediate POST to the platform.
1908
- * A background retry loop picks up un-acked rows with exponential backoff.
1909
- * Gauge and health notifications are best-effort: failed delivery is logged
1910
- * and dropped rather than queued for retry.
1978
+ * JSON wire shape POSTed to the platform's `/_internal/job-revenue` endpoint
1979
+ * and stored in `pending_dvm_reports.payload` for durable retry.
1911
1980
  */
1912
- declare class RevenueReporter {
1913
- private readonly db;
1914
- private readonly platformUrl;
1915
- private readonly platformToken;
1916
- private retryTimer;
1917
- private static readonly RETRY_INTERVAL_MS;
1918
- private static readonly MAX_BACKOFF_MS;
1919
- private static readonly ABANDON_AFTER_MS;
1920
- constructor(db: Pool, platformUrl: string, platformToken: string);
1921
- /** Create the pending-reports table and migrate the legacy SDK outbox (idempotent). */
1922
- init(): Promise<void>;
1923
- private createTables;
1924
- private migrateLegacySdkOutbox;
1925
- private assertSdkTableShape;
1926
- private assertSdkColumns;
1927
- private tableColumns;
1928
- /** Persist a revenue report and attempt immediate delivery. */
1929
- report(payload: RevenueReportPayload): Promise<void>;
1930
- /** The permanent deduplication claim and outbox insert commit together.
1931
- * The claim survives successful delivery, including for custom job stores.
1981
+ interface RevenueReportPayload {
1982
+ /** Platform DVM record ID (`dvms.id`). Identifies which DVM earned the revenue. */
1983
+ dvmId: string;
1984
+ /** Job identifier used by the platform to de-duplicate revenue rows and link to the job record. */
1985
+ jobId: string;
1986
+ /**
1987
+ * Capability selected by dispatch for this job. Omitted for
1988
+ * reports from older SDKs and legacy persisted jobs that predate capability
1989
+ * dispatch; absence must not be rewritten as a capability name.
1932
1990
  */
1933
- reportTerminal(payload: JobTerminalReportPayload): Promise<void>;
1934
- /** Persist a zero-revenue job cost and attempt immediate delivery. */
1935
- reportCost(payload: JobCostReportPayload): Promise<void>;
1991
+ capability?: string;
1992
+ /** Total amount paid by the caller in millisatoshis, across all credits that satisfied the job. */
1993
+ paidMsats: number;
1994
+ /** Cashu mint URL from which the payment tokens were issued. Set only for the `cashu` rail; omitted for all other rails. */
1995
+ paymentMint?: string;
1936
1996
  /**
1937
- * Join a credit deposit to the caller-owned rail transaction.
1938
- *
1939
- * This method performs no network I/O: it only writes the reporter-owned
1940
- * durable queue row through `tx`. The retry drain delivers the row after the
1941
- * rail transaction commits, including after a process restart.
1997
+ * Payment rail identifier (`"cashu"`, `"tempo"`, `"x402"`, `"stripe"`).
1998
+ * Mirrors `PaymentMethod` on the platform; carried as a plain string over the wire
1999
+ * so the SDK has no platform-type dependency.
1942
2000
  */
1943
- enqueueDeposit(tx: RevenueReporterQuerier, payload: CreditDepositPayload): Promise<void>;
1944
- /** Join a released draw to the transaction that made the hold terminal. */
1945
- enqueueCreditDrawRelease(tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload): Promise<void>;
2001
+ rail: string;
1946
2002
  /**
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.
2003
+ * Settlement reference for the credit: EVM tx hash for `x402`, credential
2004
+ * challenge ID for `tempo`, `X-Cashu-Request-Id` UUID for `cashu`. Required by the
2005
+ * platform revenue ledger for `tempo`/`x402` rails (recordJobRevenue throws without it);
2006
+ * nullable for `cashu`.
1949
2007
  */
1950
- enqueueCreditDrain(tx: RevenueReporterQuerier, payload: CreditDrainPayload): Promise<void>;
2008
+ paymentTxHash?: string;
1951
2009
  /**
1952
- * Join a payout to the transaction that commits the movement it reports
1953
- * the batch close, the accumulator mark, the drain booking.
1954
- * No network I/O, only the durable queue row through `tx`, the discipline
1955
- * {@link enqueueCreditDrain} set and for the same reason: the money has
1956
- * already moved on a rail this ledger does not own, so the platform row is
1957
- * the only record the builder's dashboard has of it, and a row lost between
1958
- * the fact and the queue would understate "paid out" forever. Without `tx`
1959
- * the row is queued on the pool, for callers with no fact of their own to
1960
- * commit. Idempotent on `(dvmId, payoutId)` platform-side, so the retry
1961
- * loop redelivers freely.
2010
+ * Rail-native payment amount in atomic units: USDC microunits for `tempo` and
2011
+ * `x402`, USD cents for `stripe`. Omitted for `cashu` (platform derives
2012
+ * `paidMsats / 1000`). Must be set together with `nativeAsset`.
1962
2013
  */
1963
- enqueuePayout(payload: PayoutReportPayload, tx?: RevenueReporterQuerier): Promise<void>;
2014
+ nativeAmount?: number;
1964
2015
  /**
1965
- * Report one landed payout on its own and try to deliver it at once — the
1966
- * live Tempo settlement, which has no dvmkit transaction to join. Durable
1967
- * like {@link report}.
2016
+ * Asset tag paired with `nativeAmount`: `"sats"`, `"usdc"`, `"usdc.e"`, or
2017
+ * `"usd-cents"`. Both fields must be present for the platform to write a non-cashu
2018
+ * revenue row.
1968
2019
  */
1969
- reportPayout(payload: PayoutReportPayload): Promise<void>;
2020
+ nativeAsset?: string;
1970
2021
  /**
1971
- * Report one rail's pending-pool snapshot. Best-effort and
1972
- * fire-and-forget, deliberately unlike {@link reportPayout}: a snapshot is
1973
- * replaced by the next tick, so queueing a stale one behind an outage would
1974
- * only deliver figures the platform already has newer ones for.
2022
+ * Cashu flow discriminator. Set to `"p2pk_accumulator"` when the cashu
2023
+ * accumulator path satisfied the job; omitted for legacy cashu. Written into
2024
+ * `revenue_events.metadata.cashu_flow` by the platform so ops can split per-call cashu
2025
+ * rows by source.
1975
2026
  */
1976
- reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
2027
+ cashuFlow?: string;
1977
2028
  /**
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.
2029
+ * What the job cost the **builder** to serve, as declared by the
2030
+ * handler through `ctx.cost()` — the other half of the
2031
+ * margin the dashboard states. `amountMicro` is 1e-6 of `currency`, which
2032
+ * is the currency the builder incurred the cost in and need not be the
2033
+ * credit's.
2034
+ *
2035
+ * Never netted against `paidMsats` or `drawAmountMicro`: what the caller
2036
+ * paid and what serving them cost are two questions, so they are two fields.
2037
+ *
2038
+ * **Absent means unreported, not zero.** A handler that declares nothing
2039
+ * sends no `cost` key at all, and the platform shows the margin as unknown
2040
+ * rather than as 100%. A handler that declares `{ amount: 0 }` is making the
2041
+ * opposite, positive claim and sends `amountMicro: 0`.
2042
+ *
2043
+ * Additive on the wire: a platform build that does not read this key keeps
2044
+ * accepting the report unchanged.
1980
2045
  */
1981
- enqueueCreditExpiryRelease(tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload): Promise<void>;
2046
+ cost?: {
2047
+ amountMicro: number;
2048
+ currency: string;
2049
+ };
1982
2050
  /**
1983
- * Report a reaper-force-failed paid job to the platform so the
1984
- * operator receives an ambient notice that a DVM died or wedged mid-job.
1985
- * Best-effort and fire-and-forget: unlike {@link report} there is no local
1986
- * durable queue — a non-2xx or transport failure is structured-logged
1987
- * (`paid_job_death_report_failed`) and dropped, since the reaper's own
1988
- * `stale_job_failed` log is the durable record and a failed *alert* must never
1989
- * wedge the sweep.
2051
+ * Credit this job's payment drew against. Present on
2052
+ * every job a ledger-backed payment satisfied; its presence is what tells
2053
+ * the platform the row is a **draw-keyed** revenue event rather than a
2054
+ * pre-credits per-call one.
1990
2055
  */
1991
- reportPaidJobDeath(payload: PaidJobDeathPayload): Promise<void>;
2056
+ creditId?: string;
2057
+ /** The draw this job settled — one draw, one revenue row. */
2058
+ drawId?: string;
2059
+ /** The draw's fiat amount, 1e-6 of `creditCurrency`. The exact liability offset. */
2060
+ drawAmountMicro?: number;
2061
+ /** Currency the credit is denominated in (lowercase ISO, e.g. `usd`). */
2062
+ creditCurrency?: string;
1992
2063
  /**
1993
- * Report a settled draw whose revenue event was skipped.
1994
- * Best-effort and fire-and-forget: the SDK's structured
1995
- * `revenue_skipped_no_rail` log is the durable backstop, so a failed notice
1996
- * must never wedge terminal handling or the orphan-draw reconciler.
2064
+ * Settlement instant (epoch ms) — draws are revenue *when they
2065
+ * settle*, so this dates the row. Omitted for pre-credits rows, which the
2066
+ * platform dates at record time as before.
1997
2067
  */
1998
- reportRevenueSkippedNoRail(payload: RevenueSkippedNoRailPayload): Promise<void>;
1999
- reportX402SelfRelayGasBalance(payload: X402SelfRelayGasBalancePayload): Promise<void>;
2000
- reportX402SelfRelayRpcHealth(payload: X402SelfRelayRpcHealthPayload): Promise<void>;
2001
- reportTempoSettlementBalance(payload: TempoSettlementBalancePayload): Promise<void>;
2002
- reportTempoSettlementFailure(payload: TempoSettlementFailurePayload): Promise<void>;
2003
- private postTempoSettlementReadiness;
2004
- startRetryLoop(): void;
2005
- stop(): void;
2068
+ settledAt?: number;
2069
+ /** The funding's own rail reference, for reconciling a draw back to its deposit. */
2070
+ fundingRef?: string;
2006
2071
  /**
2007
- * Operator-facing queue snapshot: distinguishes the live retry queue from
2008
- * terminal (stale) rows and breaks the latter down by classification reason
2009
- * (`token_rotated`, `client_error:<status>`, `abandoned`). Lets monitoring tell
2010
- * transient backlog apart from permanently-failed rows.
2072
+ * Revenue class when this row is not ordinary service revenue.
2073
+ * `short_pay_forfeit` marks money kept from an underpayment that bought no
2074
+ * job so analytics can exclude it.
2011
2075
  */
2012
- getQueueStats(): Promise<{
2013
- liveRetry: number;
2014
- stale: number;
2015
- byReason: Record<string, number>;
2016
- }>;
2076
+ kind?: string;
2077
+ }
2078
+ /** Privacy-preserving terminal evidence; contains no caller or payment references. */
2079
+ interface JobTerminalReportPayload {
2080
+ dvmId: string;
2081
+ jobId: string;
2082
+ capability: string;
2083
+ outcome: "completed" | "failed" | "cancelled";
2084
+ endedBy: "caller" | "provider" | null;
2085
+ paid: boolean;
2086
+ acceptedAtMs: number;
2087
+ terminalAtMs: number;
2088
+ callerWaitMs: number;
2089
+ receiptSeq: number | null;
2090
+ }
2091
+ /**
2092
+ * JSON wire shape POSTed to the platform's `/_internal/job-cost` endpoint
2093
+ * when a terminal job incurred a declared builder cost but produced no
2094
+ * revenue report.
2095
+ *
2096
+ * Unlike {@link RevenueReportPayload}, this event has no payment rail or
2097
+ * revenue amount: a failed, cancelled, or free job is not a sale. The
2098
+ * platform upserts it by `(dvmId, jobId)`, independently of the revenue
2099
+ * ledger's rail-keyed constraint. It replaces the stored value only when
2100
+ * `costRevision` is higher; equal revisions must be byte-identical. That rule
2101
+ * makes a persisted prefix and a later in-memory total converge regardless of
2102
+ * delivery order.
2103
+ */
2104
+ interface JobCostReportPayload {
2105
+ /** Platform DVM record ID (`dvms.id`); bound to the authenticating token. */
2106
+ dvmId: string;
2107
+ /** Job identifier and the report's platform-side idempotency key. */
2108
+ jobId: string;
2109
+ /** Capability selected by dispatch; absent only on legacy persisted jobs. */
2110
+ capability?: string;
2111
+ /** Terminal outcome that explains why this event carries no revenue. */
2112
+ outcome: "completed" | "failed" | "cancelled";
2113
+ /**
2114
+ * Number of declarations incorporated into `cost`; the
2115
+ * platform keeps the payload with the highest revision for this job.
2116
+ */
2117
+ costRevision: number;
2017
2118
  /**
2018
- * Deliver one bounded batch from the durable queue.
2019
- *
2020
- * Processes due, non-stale rows; failures back off up to five minutes and
2021
- * become terminal after 24 hours. Successful deliveries remove their rows.
2119
+ * The builder's cumulative declared cost, in millionths of its currency.
2120
+ * `null` at a higher revision invalidates an earlier prefix when the handler
2121
+ * switched currencies and the final total is therefore unreportable.
2022
2122
  */
2023
- drainPending(): Promise<void>;
2024
- private enqueue;
2025
- private insertPending;
2026
- private attemptDelivery;
2027
- private markStale;
2123
+ cost: {
2124
+ amountMicro: number;
2125
+ currency: string;
2126
+ } | null;
2028
2127
  }
2029
-
2030
- /** Query surface shared by the pool and an open Postgres transaction. */
2031
- type X402ChannelQuerier = Pick<Pool, "query">;
2032
2128
  /**
2033
- * Durable phase of an irreversible x402 facilitator settlement.
2129
+ * JSON wire shape POSTed to the platform's `/_internal/credit-deposit`
2130
+ * endpoint. A funding event is a **deposit** — a liability until
2131
+ * drawn — reported separately and never summed into revenue (the deposit-accounting rule).
2034
2132
  *
2035
- * `written_off` is the operator's terminal: a settlement whose
2036
- * ledger leg cannot be completed, reviewed and closed so it leaves the repair
2037
- * queue without pretending it booked. It is deliberately reversible — a later
2038
- * reconcile still accepts it, because it moved nothing.
2133
+ * Idempotent on `(dvmId, creditId, fundingId)`, so the shared retry loop can
2134
+ * redeliver freely.
2039
2135
  */
2040
- type X402SettlementStatus = "prepared" | "submitting" | "settled" | "complete" | "written_off";
2041
- /** Recovery record binding one chain settlement to one credit-ledger effect. */
2042
- interface X402SettlementIntent {
2043
- settlementId: string;
2044
- channelId: string;
2045
- operation: "fund" | "refund";
2046
- effectId: string;
2047
- paymentId: string;
2048
- payload: PaymentPayload$1;
2049
- requirements: PaymentRequirements$1;
2050
- channelBefore: Channel;
2136
+ interface CreditDepositPayload {
2137
+ /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
2138
+ dvmId: string;
2139
+ creditId: string;
2140
+ /** Canonical owner of the funded credit. */
2141
+ callerPubkey: string;
2142
+ /** The rail's settlement reference — the deposit's idempotency key. */
2143
+ fundingId: string;
2144
+ /** Payment rail (`"cashu"`, `"tempo"`, `"x402"`). */
2145
+ rail: string;
2146
+ /** Rail value received, in millisatoshis. */
2147
+ paidMsats: number;
2148
+ /** Cashu only. Full received proof face before its receiver input fee. */
2149
+ grossPaidMsats?: number;
2150
+ /** Cashu only. Standalone NUT-02 fee reserved by the received proofs. */
2151
+ cashuInputFeeMsats?: number;
2152
+ /** Rail-native atomic units; omitted for cashu (derived from `paidMsats`). */
2153
+ nativeAmount?: number;
2154
+ /** Asset tag paired with `nativeAmount`. */
2155
+ nativeAsset?: string;
2156
+ /** Cashu mint URL the funding proofs were issued from. */
2157
+ paymentMint?: string;
2158
+ /** Fiat micro credited to the ledger — the liability this deposit creates. */
2159
+ amountMicro: number;
2160
+ /** Currency `amountMicro` is 1e-6 of. */
2161
+ creditCurrency: string;
2162
+ /** When the funding committed (epoch ms). */
2163
+ fundedAt: number;
2164
+ /** Current credit expiry after this funding committed (epoch ms). */
2165
+ expiryMs: number;
2051
2166
  /**
2052
- * The channel as it stood when the settlement was handed to the facilitator —
2053
- * after the refund's mandatory on-chain refresh, which {@link channelBefore}
2054
- * precedes.
2167
+ * Tempo session funding only: the TIP-1034 channel this deposit
2168
+ * arrived on, registered so the platform's close observer can route a
2169
+ * `CloseRequested` event back to the DVM that owns the channel.
2055
2170
  *
2056
- * The facilitator sizes a cooperative refund from *live* state
2057
- * (`balance − chargedCumulativeAmount`) and signs it against the *live*
2058
- * `refundNonce`, so this is the snapshot chain evidence has to be derived
2059
- * from. `channelBefore` is a pre-verify mirror kept for the voucher restore,
2060
- * and whenever it was stale a refund that settled read as one that never
2061
- * happened. Absent on rows an older build wrote, and on any row that never
2062
- * reached `submitting`.
2171
+ * Rides the deposit report rather than a side channel on purpose — accepting
2172
+ * the voucher and booking the registration then share the one durable commit
2173
+ * boundary, so a registered channel can never be a channel whose funding
2174
+ * rolled back, and the existing retry drain delivers it after a crash. The
2175
+ * registration is therefore asynchronous relative to the close: an event can
2176
+ * legitimately arrive first, which is what the platform's payee fallback is
2177
+ * for.
2063
2178
  */
2064
- channelAtSubmission?: Channel;
2065
- submissionBlock?: string;
2066
- pendingId?: string;
2067
- status: X402SettlementStatus;
2068
- response?: SettleResponse$1;
2069
- /** Epoch ms the row was first prepared. */
2070
- createdAt: number;
2071
- /** Epoch ms of the last status write — what the repair queue ages against. */
2072
- updatedAt: number;
2073
- /** Operator's reason, recorded by {@link PostgresX402ChannelStorage.writeOffSettlement}. */
2074
- writeOffNote?: string;
2075
- /** Epoch ms an operator reconciled or wrote this settlement off. */
2076
- resolvedAt?: number;
2179
+ tempoChannel?: {
2180
+ chainId: number;
2181
+ /** 32-byte channel id, `0x`-prefixed and lowercased. */
2182
+ channelId: string;
2183
+ /** TIP-1034 escrow the channel was opened against. */
2184
+ escrow: string;
2185
+ /** The DVM's on-chain recipient — the fallback routing key. */
2186
+ payee?: string;
2187
+ };
2188
+ /**
2189
+ * Tempo session funding only: which channel this deposit's voucher
2190
+ * was accepted on, emitted **whenever the funding was session-based** —
2191
+ * independently of whether {@link CreditDepositPayload.tempoChannel} could be
2192
+ * read. That independence is the entire point: `rail: "tempo"` covers both
2193
+ * `tempo/session` and the `tempo/charge` one-shot, and a charge legitimately
2194
+ * carries no channel at all, so without this the platform cannot tell a
2195
+ * routine charge deposit from a session whose chain binding was lost — the
2196
+ * shape, where the close observer silently never watches the escrow.
2197
+ *
2198
+ * Carries only the channel id, not the chain binding: a DVM that could read
2199
+ * the binding sends `tempoChannel` too, and one that could not still names
2200
+ * the channel an operator has to go and look at.
2201
+ */
2202
+ tempoSession?: {
2203
+ /** 32-byte channel id, `0x`-prefixed and lowercased. */
2204
+ channelId: string;
2205
+ };
2206
+ /**
2207
+ * Set by the blocked-invoice repair: this deposit is a paid
2208
+ * Lightning invoice the builder's agent reconciled onto a credit. The money
2209
+ * landed in the receive wallet at payment time and this deposit is the one
2210
+ * row it gets, so the settlements table types it `repair` off this flag
2211
+ * rather than off a second row.
2212
+ */
2213
+ repaired?: boolean;
2077
2214
  }
2078
- /** The caller-supplied half of a settlement row — everything the store does not stamp itself. */
2079
- type X402SettlementIdentity = Omit<X402SettlementIntent, "status" | "response" | "createdAt" | "updatedAt" | "writeOffNote" | "resolvedAt" | "channelAtSubmission">;
2080
2215
  /**
2081
- * Keyset position in the repair queue's `(created_at, settlement_id)` ordering.
2216
+ * JSON wire shape POSTed to the platform's `/_internal/credit-draw-release`
2217
+ * endpoint. A released hold leaves the credit balance intact, but
2218
+ * marks that slice of the outstanding liability as failure churn rather than
2219
+ * untouched prefunding.
2082
2220
  *
2083
- * `createdAt` is Postgres's own rendering of the `TIMESTAMPTZ`, carried as text
2084
- * and handed straight back as one — never a `Date`. `created_at` has microsecond
2085
- * resolution and `Date` has millisecond, so a cursor that round-tripped through
2086
- * epoch ms would land *before* the row it was taken from and re-serve it on
2087
- * every page, which at `limit: 1` never advances at all.
2221
+ * Idempotent on `(dvmId, creditId, drawId)`, so the reporter queue can
2222
+ * redeliver freely.
2088
2223
  */
2089
- interface X402SettlementCursor {
2090
- createdAt: string;
2091
- settlementId: string;
2092
- }
2093
- /** One page of the repair queue, plus the position to resume from. */
2094
- interface X402SettlementPage {
2095
- settlements: X402SettlementIntent[];
2096
- /** Absent when this page was not full — there is nothing behind it. */
2097
- nextAfter?: X402SettlementCursor;
2224
+ interface CreditDrawReleasePayload {
2225
+ /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
2226
+ dvmId: string;
2227
+ creditId: string;
2228
+ /** Released draw; the platform idempotency key with DVM and credit. */
2229
+ drawId: string;
2230
+ /** Fiat micro restored to availability. */
2231
+ amountMicro: number;
2232
+ /** Currency `amountMicro` is 1e-6 of. */
2233
+ creditCurrency: string;
2234
+ /** Per-credit gap-free sequence assigned when the draw was placed. */
2235
+ ledgerSeq: number;
2236
+ /** When the hold reached `released` (epoch ms). */
2237
+ releasedAt: number;
2098
2238
  }
2239
+ type CreditDrainRepairKind = "x402_settlement_reconciled" | "tempo_close_reconciled";
2099
2240
  /**
2100
- * Fleet-wide serialisation of the self-relay's chain submissions.
2101
- *
2102
- * Narrow on purpose, and implemented by {@link PostgresX402ChannelStorage}: the
2103
- * relay asks for one thing — exclusive use of its gas EOA for the length of one
2104
- * submission — and must not acquire the ability to touch a channel or a
2105
- * settlement row. Handing it this instead of the host `Pool` is what keeps the
2106
- * acquire off the pool the channel transaction is already holding a client from.
2241
+ * Terminal picked_up/sent drains report reclaimed fiat micro-units, reducing
2242
+ * outstanding liability rather than adding revenue. Requested or parked drains
2243
+ * have not paid the caller. Idempotency key: (dvmId, creditId, drainId).
2107
2244
  */
2108
- interface X402RelaySubmissionLock {
2109
- withRelaySubmissionLock<T>(key: string, operation: () => Promise<T>): Promise<T>;
2110
- }
2111
- interface X402ChannelStorageOpts {
2112
- /**
2113
- * Override the relay submission lock's wait ceiling, in milliseconds.
2114
- *
2115
- * A test seam: the production ceiling is tens of seconds, which no test can
2116
- * wait out, and the refusal path is exactly what needs pinning.
2117
- */
2118
- relaySubmissionLockTimeoutMs?: number;
2119
- /**
2120
- * Retire ledger credit when a fresh chain observation proves its channel
2121
- * has no unclaimed backing. Runs under the channel transaction so the
2122
- * channel-first, credit-second lock order stays fleet-wide.
2123
- */
2124
- terminalizeUnbackedCredit?: (evidence: {
2125
- channelId: string;
2126
- channelBalanceNative: bigint;
2127
- totalClaimedNative: bigint;
2128
- observedAt: number;
2129
- }, tx: X402ChannelQuerier) => Promise<unknown>;
2245
+ interface CreditDrainPayload {
2246
+ /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
2247
+ dvmId: string;
2248
+ creditId: string;
2249
+ /** Canonical owner of the reclaimed credit. */
2250
+ callerPubkey: string;
2251
+ /** The drain's own idempotency key. */
2252
+ drainId: string;
2253
+ /** Payout rail (`"cashu"`, `"x402"`, `"tempo"`). */
2254
+ method: string;
2255
+ /** Fiat micro reclaimed — the liability this drain extinguishes. */
2256
+ amountMicro: number;
2257
+ /** Currency `amountMicro` is 1e-6 of. */
2258
+ creditCurrency: string;
2259
+ /** Terminal state: `"picked_up"` for cashu, `"sent"` for other rails. */
2260
+ status: string;
2261
+ /** When the money left (epoch ms). */
2262
+ drainedAt: number;
2263
+ /** Per-credit gap-free ledger sequence, for reconciling against the DVM's ledger. */
2264
+ ledgerSeq?: number;
2265
+ /** Set when an operator repair verb completed this reclaim. */
2266
+ repaired?: boolean;
2267
+ /** Required iff `repaired`; identifies the repair path that completed the reclaim. */
2268
+ repairKind?: CreditDrainRepairKind;
2130
2269
  }
2131
- /** Best-effort identification of the backend holding the relay submission lock. */
2132
- interface X402RelayLockHolder {
2133
- pid: number;
2134
- applicationName?: string;
2135
- clientAddr?: string;
2136
- state?: string;
2137
- /** Seconds since the holder's current statement started. */
2138
- heldSeconds?: number;
2270
+ /**
2271
+ * Open Postgres transaction handle accepted by the reporter-owned outbox
2272
+ * enqueue. The payment rail owns BEGIN/COMMIT; the reporter owns its table.
2273
+ */
2274
+ interface RevenueReporterQuerier {
2275
+ query: Pool["query"];
2139
2276
  }
2140
2277
  /**
2141
- * The fleet-wide relay submission lock was still held when the wait ceiling expired.
2278
+ * Transactional deposit-outbox seam threaded through each rail commit.
2279
+ *
2280
+ * Implemented by {@link RevenueReporter.enqueueDeposit}; callers must await it
2281
+ * before committing the rail receive and ledger funding.
2282
+ */
2283
+ type CreditDepositEnqueue = (tx: RevenueReporterQuerier, payload: CreditDepositPayload) => Promise<void>;
2284
+ /** Transactional draw-release outbox seam used by the credit ledger. */
2285
+ type CreditDrawReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload) => Promise<void>;
2286
+ /**
2287
+ * Transactional drain-outbox seam threaded through each terminal drain
2288
+ * transition.
2289
+ *
2290
+ * Implemented by {@link RevenueReporter.enqueueCreditDrain}; callers must await
2291
+ * it before committing the status CAS that moved the money.
2292
+ */
2293
+ type CreditDrainEnqueue = (tx: RevenueReporterQuerier, payload: CreditDrainPayload) => Promise<void>;
2294
+ /**
2295
+ * JSON wire shape POSTed to the platform's `/_internal/credit-expiry-release`
2296
+ * endpoint. A prepaid credit that reaches its TTL with an undrawn
2297
+ * remainder RELEASES that remainder to the builder: on cashu, Lightning and
2298
+ * x402 the funding value reached builder custody at funding time or on the
2299
+ * settlement cycle, so expiry extinguishes the caller's remaining claim.
2300
+ * Reported so committed value nets to
2301
+ * `deposited − drawn − drained − released`; without it an expired credit is
2302
+ * owed forever on paper, exactly like an unreported drain.
2142
2303
  *
2143
- * Typed, and `retryable`, because it is an availability outcome rather than a
2144
- * fault in the payment: a fleet-mate whose RPC is retrying holds the lock, and
2145
- * the same request works once it lets go. It refuses rather than proceeding —
2146
- * two machines submitting from one EOA collide on the nonce — and it refuses
2147
- * rather than blocking, which is the defect: the old acquire had no
2148
- * `lock_timeout`, so a wedged holder pinned a host-pool client and a channel
2149
- * advisory lock for as long as it took.
2304
+ * Never sent for a Tempo credit. Tempo balances are channel-backed and undrawn
2305
+ * channel value returns to the *caller* at exit, so no release exists on that
2306
+ * rail — the sweep skips them and the platform refuses one outright.
2307
+ *
2308
+ * Two reports share this shape, distinguished only by `reversedAt`:
2309
+ *
2310
+ * - **The release**, written by the expiry sweep with `reversedAt` absent.
2311
+ * - **The reversal**, written when a later funding revives the credit
2312
+ * (`CreditLedger.fund` overwrites `expiry_ms`, so the recorded remainder is
2313
+ * no longer final). It repeats the whole payload rather than sending a
2314
+ * reference, because the retry loop can deliver the two out of order and the
2315
+ * platform's upsert must be able to land either one first.
2316
+ *
2317
+ * Idempotent on `(dvmId, creditId, releaseId)`, so the shared retry loop can
2318
+ * redeliver freely.
2150
2319
  */
2151
- declare class X402RelaySubmissionLockError extends Error {
2152
- readonly lockKey: string;
2153
- readonly timeoutMs: number;
2154
- readonly holder?: X402RelayLockHolder | undefined;
2155
- readonly code = "x402_relay_submission_lock_timeout";
2156
- readonly retryable = true;
2157
- constructor(lockKey: string, timeoutMs: number, holder?: X402RelayLockHolder | undefined);
2158
- }
2159
- /** Outcome of {@link PostgresX402ChannelStorage.writeOffSettlement} — `written` is the transition bit. */
2160
- interface X402SettlementWriteOff {
2161
- intent: X402SettlementIntent;
2162
- written: boolean;
2163
- }
2164
- /** The refund standing between a channel and the credit it backs. */
2165
- interface X402UnresolvedRefund {
2166
- settlementId: string;
2167
- /** `${creditId}:${drainId}` — the drain this settlement belongs to. */
2168
- effectId: string;
2169
- status: X402SettlementStatus;
2320
+ interface CreditExpiryReleasePayload {
2321
+ /** Platform DVM record ID (`dvms.id`); must match the bearer token's DVM. */
2322
+ dvmId: string;
2323
+ creditId: string;
2324
+ /** Per-expiry-episode key; the platform idempotency key with DVM and credit. */
2325
+ releaseId: string;
2326
+ callerPubkey: string;
2327
+ /** Funding rail. `null` on a credit funded before the basis became mandatory. */
2328
+ rail: string | null;
2329
+ /** Fiat micro released — the liability this expiry extinguishes. */
2330
+ amountMicro: number;
2331
+ /** Currency `amountMicro` is 1e-6 of. */
2332
+ creditCurrency: string;
2333
+ /** When the credit's TTL ran out (epoch ms) — when the liability ended. */
2334
+ expiryMs: number;
2335
+ /** When the sweep observed the expiry (epoch ms). */
2336
+ releasedAt: number;
2337
+ /** Set only on the reversal report: when the revival funding landed (epoch ms). */
2338
+ reversedAt?: number | null;
2170
2339
  }
2171
2340
  /**
2172
- * The credit ledger's read into durable x402 settlement state.
2341
+ * Transactional expiry-release outbox seam, threaded through the sweep's insert
2342
+ * and through the funding that reverses a release.
2173
2343
  *
2174
- * Narrow on purpose: the ledger asks one question — is this channel's refund
2175
- * still unresolved — and must not acquire the ability to move a settlement.
2176
- * Implemented by {@link PostgresX402ChannelStorage.pendingRefundSettlement}.
2344
+ * Implemented by {@link RevenueReporter.enqueueCreditExpiryRelease}; callers
2345
+ * must await it before committing the write that made the release (or its
2346
+ * reversal) true, for the same reason the drain seam does — a report lost
2347
+ * between the two overstates or understates committed value forever.
2177
2348
  */
2178
- interface X402RefundSettlementGate {
2179
- pendingRefundSettlement(channelId: string, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
2349
+ type CreditExpiryReleaseEnqueue = (tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload) => Promise<void>;
2350
+ /**
2351
+ * JSON wire shape POSTed to the platform's `/_internal/paid-job-death` endpoint
2352
+ * when the stale-job reaper force-fails a *paid* job — its pending
2353
+ * credit draw is released rather than settled, so the caller's payment is back
2354
+ * on their credit balance and reclaimable by drain. The platform resolves fiat
2355
+ * + owner and sends an ambient notice via the shared alert sink. Unlike
2356
+ * {@link RevenueReportPayload} this carries no durable-retry semantics: the
2357
+ * reaper's `stale_job_failed` structured log is the durable backstop, so a lost
2358
+ * report degrades to log-only rather than blocking the sweep.
2359
+ */
2360
+ interface PaidJobDeathPayload {
2361
+ /** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
2362
+ dvmId: string;
2363
+ jobId: string;
2364
+ capability: string;
2365
+ /** Amount of the released credit draw in millisatoshis. */
2366
+ paidMsats: number;
2367
+ /** Payment rail of the released draw (`"cashu"`, `"tempo"`, `"x402"`). */
2368
+ rail: string;
2369
+ /** Reaper reason: dead worker or never-terminal idle job. */
2370
+ reason: string;
2371
+ /** Rail-native released amount, paired with `nativeAsset`. */
2372
+ nativeAmount?: number;
2373
+ /** Asset tag for `nativeAmount` (`"sats"` / `"usdc"` / …). */
2374
+ nativeAsset?: string;
2375
+ /** Cashu mint URL the credit draw's tokens were issued from. */
2376
+ paymentMint?: string;
2180
2377
  }
2378
+ type RevenueSkippedNoRailReason = "credit_rail_null" | "credit_rail_unrecognized" | "job_rail_missing" | "ledger_not_configured";
2181
2379
  /**
2182
- * Postgres implementation of x402's server-side {@link ChannelStorage}.
2183
- *
2184
- * `updateChannel` takes an advisory transaction lock before it reads the row,
2185
- * so the read/callback/write sequence is atomic even for a first deposit whose
2186
- * row does not exist yet. {@link withChannelTransaction} exposes the same
2187
- * transaction to the credit ledger: the fixed lock order is channel first,
2188
- * credit second, and no credit-ledger path calls back into this store.
2189
- *
2190
- * Two pools, and the split is load-bearing. The settlement markers
2191
- * must write *outside* the ledger transaction, which means a second connection
2192
- * taken while the transaction's client is still checked out — on the shared
2193
- * host pool that is a self-deadlock the moment concurrent deposits reach its
2194
- * `max`, with no error and no timeout because every waiter is also a holder.
2195
- * They therefore run on a small dedicated pool that acquires nothing itself,
2196
- * so nothing can ever queue behind them. The rule this rests on: a client held
2197
- * from one pool may only ever be used to acquire from a pool that is terminal.
2198
- * {@link withFleetSettlementLock} and {@link withRelaySubmissionLock} are the
2199
- * other two nesting sites and are bounded a different way — see their docs.
2380
+ * JSON wire shape POSTed to the platform's `/_internal/revenue-skipped-no-rail`
2381
+ * endpoint when a settled credit draw has no usable payment rail
2382
+ * for its revenue event. This is an operator notice rather than a durable
2383
+ * accounting report: the SDK's structured log remains the durable backstop,
2384
+ * and a failed alert must never block terminal handling or reconciliation.
2200
2385
  */
2201
- declare class PostgresX402ChannelStorage implements ChannelStorage {
2202
- private readonly pool;
2203
- private readonly transaction;
2204
- private readonly refundBase;
2205
- private settlementPool?;
2206
- private fleetLock;
2207
- private fleetLockQueueDepth;
2208
- private readonly relayLockTimeoutMs;
2209
- private readonly terminalizeUnbackedCredit?;
2210
- constructor(pool: Pool, opts?: X402ChannelStorageOpts);
2211
- init(): Promise<void>;
2212
- /**
2213
- * Read the fleet's durable "claimed funds are awaiting transfer" marker.
2214
- *
2215
- * Upstream keeps this in process memory, which a multi-machine fleet cannot
2216
- * share: the machine that won a claim tick is rarely the one that wins the
2217
- * next settle tick. An unset scope reads false, so a receiver that has never
2218
- * claimed never submits a settlement.
2219
- *
2220
- * Both accessors run on the settlement pool: every caller reads or writes the
2221
- * marker while {@link withFleetSettlementLock} holds a host-pool client, which
2222
- * is the nesting the split exists to keep off a non-terminal pool.
2223
- */
2224
- isSettlePending(scope: string): Promise<boolean>;
2225
- /** Record whether a claim's funds still need a `settle(receiver, token)` transfer. */
2226
- setSettlePending(scope: string, pending: boolean): Promise<void>;
2227
- /**
2228
- * Look up the durable settlement state for one credit-ledger effect.
2229
- *
2230
- * On the dedicated settlement pool like every other marker, and for a sharper
2231
- * reason than they had: this and {@link prepareSettlement} now run *inside*
2232
- * the channel transaction, which holds a host-pool client for its
2233
- * whole duration. Taking a second client from that same pool is the
2234
- * self-deadlock fixed — at pool max every client is a holder and a
2235
- * waiter — and moving these calls under the transaction is exactly what would
2236
- * reintroduce it.
2386
+ interface RevenueSkippedNoRailPayload {
2387
+ /** Platform DVM record ID (`dvms.id`). Bound to the authenticating token. */
2388
+ dvmId: string;
2389
+ jobId: string;
2390
+ creditId: string;
2391
+ drawId: string;
2392
+ /** Caller payment captured for the job, in millisatoshis. */
2393
+ paidMsats: number;
2394
+ /** Settled draw amount in 1e-6 of `creditCurrency`. */
2395
+ drawAmountMicro?: number;
2396
+ /** Currency the draw amount is denominated in. */
2397
+ creditCurrency?: string;
2398
+ /** Credit funding rail as stored on the draw, or null for legacy rows. */
2399
+ creditRail: string | null;
2400
+ reason: RevenueSkippedNoRailReason;
2401
+ }
2402
+ /** Gas-balance observation POSTed by a platform-hosted x402 self-relay. */
2403
+ interface X402SelfRelayGasBalancePayload {
2404
+ /** Platform DVM record ID, bound to the authenticating platform token. */
2405
+ dvmId: string;
2406
+ /** Dedicated relay EOA whose Base ETH balance was read. */
2407
+ address: `0x${string}`;
2408
+ /** CAIP-2 Base network identifier. */
2409
+ network: string;
2410
+ /** Current native balance as a decimal wei string. */
2411
+ balanceWei: string;
2412
+ /** Configured paging floor as a decimal wei string. */
2413
+ lowBalanceWei: string;
2414
+ /** Millisecond timestamp at which the DVM read the balance. */
2415
+ checkedAt: number;
2416
+ }
2417
+ /** RPC-health transition POSTed by a platform-hosted x402 self-relay. */
2418
+ type X402SelfRelayRpcHealthPayload = {
2419
+ /** Platform DVM record ID, bound to the authenticating platform token. */
2420
+ dvmId: string;
2421
+ /** Dedicated relay EOA identifying the affected self-relay. */
2422
+ address: `0x${string}`;
2423
+ /** CAIP-2 Base network identifier. */
2424
+ network: string;
2425
+ /** Healthy transition clears an open RPC page. */
2426
+ status: "healthy";
2427
+ /** Millisecond timestamp at which the DVM observed the transition. */
2428
+ checkedAt: number;
2429
+ } | {
2430
+ /** Platform DVM record ID, bound to the authenticating platform token. */
2431
+ dvmId: string;
2432
+ /** Dedicated relay EOA identifying the affected self-relay. */
2433
+ address: `0x${string}`;
2434
+ /** CAIP-2 Base network identifier. */
2435
+ network: string;
2436
+ /** Failed transition opens or refreshes an RPC page. */
2437
+ status: "failed";
2438
+ /** Redacted failure class; never the provider error or endpoint URL. */
2439
+ reason: X402SelfRelayRpcFailureReason;
2440
+ /** Millisecond timestamp at which the DVM observed the transition. */
2441
+ checkedAt: number;
2442
+ };
2443
+ /** Configured Tempo operator fee-token balance POSTed by a hosted DVM. */
2444
+ interface TempoSettlementBalancePayload {
2445
+ dvmId: string;
2446
+ address: `0x${string}`;
2447
+ network: string;
2448
+ token: `0x${string}`;
2449
+ balanceMicro: string;
2450
+ lowBalanceMicro: string;
2451
+ checkedAt: number;
2452
+ ready: boolean;
2453
+ }
2454
+ /** Redacted Tempo operator fee-funding rejection POSTed by a hosted DVM. */
2455
+ interface TempoSettlementFailurePayload {
2456
+ dvmId: string;
2457
+ address: `0x${string}`;
2458
+ network: string;
2459
+ token: `0x${string}`;
2460
+ operation: "settle" | "close";
2461
+ errorClass: "InsufficientFundsError";
2462
+ checkedAt: number;
2463
+ balanceMicro?: string;
2464
+ channelId?: string;
2465
+ creditId?: string;
2466
+ drainId?: string;
2467
+ }
2468
+ /**
2469
+ * Durable revenue reporter for container-runtime DVMs.
2470
+ *
2471
+ * On job completion the SDK calls `report()`, which persists the payload to
2472
+ * a local Postgres table and attempts an immediate POST to the platform.
2473
+ * A background retry loop picks up un-acked rows with exponential backoff.
2474
+ * Gauge and health notifications are best-effort: failed delivery is logged
2475
+ * and dropped rather than queued for retry.
2476
+ */
2477
+ declare class RevenueReporter {
2478
+ private readonly db;
2479
+ private readonly platformUrl;
2480
+ private readonly platformToken;
2481
+ private readonly hostedObligations?;
2482
+ private retryTimer;
2483
+ private static readonly RETRY_INTERVAL_MS;
2484
+ private static readonly MAX_BACKOFF_MS;
2485
+ private static readonly ABANDON_AFTER_MS;
2486
+ constructor(db: Pool, platformUrl: string, platformToken: string, hostedObligations?: HostedSettlementReportObligations | undefined);
2487
+ /** Create the pending-reports table and migrate the legacy SDK outbox (idempotent). */
2488
+ init(): Promise<void>;
2489
+ private createTables;
2490
+ private migrateLegacySdkOutbox;
2491
+ private assertSdkTableShape;
2492
+ private assertSdkColumns;
2493
+ private tableColumns;
2494
+ /** Persist a revenue report and attempt immediate delivery. */
2495
+ report(payload: RevenueReportPayload): Promise<void>;
2496
+ /** The permanent deduplication claim and outbox insert commit together.
2497
+ * The claim survives successful delivery, including for custom job stores.
2237
2498
  */
2238
- getSettlement(operation: "fund" | "refund", effectId: string): Promise<X402SettlementIntent | undefined>;
2499
+ reportTerminal(payload: JobTerminalReportPayload): Promise<void>;
2500
+ /** Persist a zero-revenue job cost and attempt immediate delivery. */
2501
+ reportCost(payload: JobCostReportPayload): Promise<void>;
2239
2502
  /**
2240
- * The refund on this channel that is neither finished nor abandoned, if there
2241
- * is one — the credit ledger's spend gate.
2242
- *
2243
- * `statuses` is the caller's, because the two gates disagree by design: a
2244
- * draw is blocked by {@link X402_SPEND_BLOCKING_SETTLEMENT_STATUSES}, a fresh
2245
- * drain only by {@link X402_WEDGED_SETTLEMENT_STATUSES}.
2246
- *
2247
- * On the dedicated settlement pool, and that is what makes the gate callable
2248
- * at all: a draw routinely runs inside an open channel transaction holding a
2249
- * host-pool client, and taking a second one from that pool is the
2250
- * self-deadlock. The oldest blocking row wins, so a channel that somehow
2251
- * accumulated two names the one an operator should reach for first.
2503
+ * Join a credit deposit to the caller-owned rail transaction.
2252
2504
  *
2253
- * Projects three columns rather than decoding a whole {@link
2254
- * X402SettlementIntent}: the decoder refuses a legacy row that carries
2255
- * no reconciliation snapshot, and a gate that throws on one would take every
2256
- * draw on that channel down with it — when the row is exactly the kind that
2257
- * ought to block them.
2258
- */
2259
- pendingRefundSettlement(channelId: string, statuses: X402SettlementStatus[]): Promise<X402UnresolvedRefund | undefined>;
2260
- /**
2261
- * Read one settlement by its own identifier — the handle an operator holds
2262
- * Same dedicated pool as {@link getSettlement}, for the same
2263
- * reason: the repair reads this while a channel transaction may be open.
2505
+ * This method performs no network I/O: it only writes the reporter-owned
2506
+ * durable queue row through `tx`. The retry drain delivers the row after the
2507
+ * rail transaction commits, including after a process restart.
2264
2508
  */
2265
- getSettlementById(settlementId: string): Promise<X402SettlementIntent | undefined>;
2266
- /** Lazily move a legacy settlement row onto its authorization/effect-scoped replay key. */
2267
- normalizeLegacyPaymentId(settlementId: string, operation: "fund" | "refund", legacyPaymentId: string, paymentId: string): Promise<X402SettlementIntent>;
2509
+ enqueueDeposit(tx: RevenueReporterQuerier, payload: CreditDepositPayload): Promise<void>;
2510
+ /** Join a released draw to the transaction that made the hold terminal. */
2511
+ enqueueCreditDrawRelease(tx: RevenueReporterQuerier, payload: CreditDrawReleasePayload): Promise<void>;
2268
2512
  /**
2269
- * The operator's repair queue: settlements stuck between "chain paid" and
2270
- * "ledger booked", oldest write first.
2271
- *
2272
- * Keyset-paginated on `(created_at, settlement_id)` for the reason the
2273
- * blocked-invoice queue is: a wedged row never self-clears, so a fixed first
2274
- * page of rows the operator has declined to act on would starve everything
2275
- * behind it on every run, forever.
2276
- *
2277
- * The keyset is on `created_at`, which never moves, while `updatedBeforeMs`
2278
- * filters on `updated_at`, which does — so a row an operator touches mid-walk
2279
- * can leave the queue but can never jump the cursor and be skipped. The cursor
2280
- * itself is Postgres's text rendering of that timestamp, handed back verbatim;
2281
- * see {@link X402SettlementCursor} for why a `Date` cannot carry it.
2282
- *
2283
- * `updatedBeforeMs` is the staleness floor. A healthy settlement passes
2284
- * through `submitting` and `settled` inside one request, so anything younger
2285
- * than the floor is in flight rather than wedged and must not be offered as
2286
- * repairable work.
2513
+ * Await durable queue insertion through tx so the terminal drain and its
2514
+ * report commit or roll back together; no network I/O here.
2287
2515
  */
2288
- listSettlements(args: {
2289
- statuses: X402SettlementStatus[];
2290
- updatedBeforeMs: number;
2291
- limit: number;
2292
- after?: X402SettlementCursor;
2293
- }): Promise<X402SettlementPage>;
2516
+ enqueueCreditDrain(tx: RevenueReporterQuerier, payload: CreditDrainPayload): Promise<void>;
2294
2517
  /**
2295
- * Close a wedged settlement without booking anything.
2296
- *
2297
- * A single-statement CAS on the wedged statuses, on the settlement pool —
2298
- * there is no ledger effect to be atomic with, which is the whole point:
2299
- * the write-off records that an operator reviewed this settlement and is not
2300
- * completing it, and no dvmkit table moves. Idempotent, and the first note
2301
- * wins; `written` is the transition bit the caller logs on, so the one place
2302
- * the amount is ever recorded gets exactly one entry.
2303
- *
2304
- * Reversible by construction: a later reconcile calls
2305
- * {@link recordSettlementResponse}, whose `status <> 'complete'` CAS lifts a
2306
- * written-off row back to `settled`.
2518
+ * Join a payout to the transaction that commits the movement it reports
2519
+ * the batch close, the accumulator mark, the drain booking.
2520
+ * No network I/O, only the durable queue row through `tx`, the discipline
2521
+ * {@link enqueueCreditDrain} set and for the same reason: the money has
2522
+ * already moved on a rail this ledger does not own, so the platform row is
2523
+ * the only record the builder's dashboard has of it, and a row lost between
2524
+ * the fact and the queue would understate "paid out" forever. Without `tx`
2525
+ * the row is queued on the pool, for callers with no fact of their own to
2526
+ * commit. Idempotent on `(dvmId, payoutId)` platform-side, so the retry
2527
+ * loop redelivers freely.
2307
2528
  */
2308
- writeOffSettlement(settlementId: string, note: string | undefined, nowMs: number): Promise<X402SettlementWriteOff | undefined>;
2309
- /** Persist the reconciliation identity, refreshing a prepared retry's transient reservation. */
2310
- prepareSettlement(intent: X402SettlementIdentity): Promise<X402SettlementIntent>;
2529
+ enqueuePayout(payload: PayoutReportPayload, tx?: RevenueReporterQuerier): Promise<void>;
2530
+ enqueueHostedPayout(payload: PayoutReportPayload, obligationIds: string[], tx?: RevenueReporterQuerier): Promise<void>;
2311
2531
  /**
2312
- * Mark that the next operation is the ambiguous external-call boundary.
2313
- *
2314
- * `channelAtSubmission` is the live channel the facilitator is about to size
2315
- * and sign the settlement against — bookmarked here, with the
2316
- * block height, because these two are the only description of that instant
2317
- * that survives the call.
2532
+ * Report one landed payout on its own and try to deliver it at once — the
2533
+ * live Tempo settlement, which has no dvmkit transaction to join. Durable
2534
+ * like {@link report}.
2318
2535
  */
2319
- markSettlementSubmitting(settlementId: string, submissionBlock: string, channelAtSubmission?: Channel): Promise<void>;
2320
- /** Return an explicitly failed facilitator submission to the safe retry phase. */
2321
- markSettlementRetryable(settlementId: string): Promise<void>;
2536
+ reportPayout(payload: PayoutReportPayload): Promise<void>;
2322
2537
  /**
2323
- * Record a successful facilitator response independently of the enclosing
2324
- * ledger transaction.
2325
- *
2326
- * The write-off columns are cleared, not carried: this CAS is what lifts a
2327
- * `written_off` row back to `settled` (which is what makes the write-off
2328
- * reversible), and a row that is no longer written off must not still be
2329
- * carrying the note and resolution timestamp of a decision that has been
2330
- * reversed. On the live path the columns are already `NULL` and this is a
2331
- * no-op.
2538
+ * Report one rail's pending-pool snapshot. Best-effort and
2539
+ * fire-and-forget, deliberately unlike {@link reportPayout}: a snapshot is
2540
+ * replaced by the next tick, so queueing a stale one behind an outage would
2541
+ * only deliver figures the platform already has newer ones for.
2332
2542
  */
2333
- recordSettlementResponse(settlementId: string, response: SettleResponse$1): Promise<void>;
2543
+ reportPayoutPending(payload: PayoutPendingPayload): Promise<void>;
2334
2544
  /**
2335
- * Mark the external settlement and ledger effect complete in the active transaction.
2336
- *
2337
- * `resolvedAtMs` is set only by the operator repair, so an audit
2338
- * read can tell a settlement that completed on its own from one a human had
2339
- * to finish.
2545
+ * Await durable queue insertion through tx so expiry release or reversal and
2546
+ * its report commit together. A missing reversal would overstate builder value.
2340
2547
  */
2341
- completeSettlement(settlementId: string, resolvedAtMs?: number): Promise<void>;
2342
- get(channelId: string): Promise<Channel | undefined>;
2343
- /** List all channel snapshots in stable channel-id order. */
2344
- list(): Promise<Channel[]>;
2345
- /** Atomic backend-level read/modify/write for one channel. */
2346
- updateChannel(channelId: string, update: (current: Channel | undefined) => Channel | undefined): Promise<ChannelUpdateResult>;
2548
+ enqueueCreditExpiryRelease(tx: RevenueReporterQuerier, payload: CreditExpiryReleasePayload): Promise<void>;
2347
2549
  /**
2348
- * Run channel state and its ledger effect under the channel lock.
2349
- *
2350
- * Upstream's voucher-only settle updates `chargedCumulativeAmount` through
2351
- * `updateChannel`. Keeping that update and `commitMarkedFunding` in this one
2352
- * transaction means a ledger refusal rolls the voucher state back. External
2353
- * deposit/refund responses are recorded separately before this transaction
2354
- * commits, leaving a durable reconciliation row if COMMIT fails.
2550
+ * Report a reaper-force-failed paid job to the platform so the
2551
+ * operator receives an ambient notice that a DVM died or wedged mid-job.
2552
+ * Best-effort and fire-and-forget: unlike {@link report} there is no local
2553
+ * durable queue — a non-2xx or transport failure is structured-logged
2554
+ * (`paid_job_death_report_failed`) and dropped, since the reaper's own
2555
+ * `stale_job_failed` log is the durable record and a failed *alert* must never
2556
+ * wedge the sweep.
2355
2557
  */
2558
+ reportPaidJobDeath(payload: PaidJobDeathPayload): Promise<void>;
2356
2559
  /**
2357
- * Sanction one cooperative-refund base for the updates inside `operation`.
2358
- *
2359
- * The store cannot derive the base — it comes from the credit ledger — so
2360
- * this is how the settlement layer says "this exact decrease is the one the
2361
- * payer's verified refund voucher authorises". Outside this scope a decrease
2362
- * of `chargedCumulativeAmount` is refused flat, which is what keeps the
2363
- * arbitrary-decrease guard meaningful.
2560
+ * Report a settled draw whose revenue event was skipped.
2561
+ * Best-effort and fire-and-forget: the SDK's structured
2562
+ * `revenue_skipped_no_rail` log is the durable backstop, so a failed notice
2563
+ * must never wedge terminal handling or the orphan-draw reconciler.
2364
2564
  */
2365
- withSanctionedRefundBase<T>(base: string, operation: () => Promise<T>): Promise<T>;
2366
- withChannelTransaction<T>(channelId: string, operation: (tx: PoolClient) => Promise<T>): Promise<T>;
2565
+ reportRevenueSkippedNoRail(payload: RevenueSkippedNoRailPayload): Promise<void>;
2566
+ reportX402SelfRelayGasBalance(payload: X402SelfRelayGasBalancePayload): Promise<void>;
2567
+ reportX402SelfRelayRpcHealth(payload: X402SelfRelayRpcHealthPayload): Promise<void>;
2568
+ reportTempoSettlementBalance(payload: TempoSettlementBalancePayload): Promise<void>;
2569
+ reportTempoSettlementFailure(payload: TempoSettlementFailurePayload): Promise<void>;
2570
+ private postTempoSettlementReadiness;
2571
+ startRetryLoop(): void;
2572
+ stop(): void;
2367
2573
  /**
2368
- * Run one fleet-wide settlement operation, optionally waiting for the current owner.
2369
- *
2370
- * The lock is session-scoped, so its client stays checked out for the whole
2371
- * operation — and `operation()` (upstream's claim/settle) re-enters the pool
2372
- * through {@link updateChannel}. Two bounds keep that from wedging the pool
2373
- * local callers are serialized in-process, so this holds at most
2374
- * one client per process no matter how many requests arrive, and a waiting
2375
- * acquire carries a `lock_timeout` so a wedged fleet-mate can't pin that
2376
- * client — and the caller's request — indefinitely. A timeout reports the
2377
- * lock as not acquired, which is what every caller already handles.
2378
- *
2379
- * The queue depth is reserved synchronously, before the promise chain can
2380
- * yield. That makes the non-waiting form a true local try-lock even while the
2381
- * first caller is still acquiring its connection: an overlapping request
2382
- * returns `false` instead of queuing behind the facilitator operation.
2574
+ * Operator-facing queue snapshot: distinguishes the live retry queue from
2575
+ * terminal (stale) rows and breaks the latter down by classification reason
2576
+ * (`token_rotated`, `client_error:<status>`, `abandoned`). Lets monitoring tell
2577
+ * transient backlog apart from permanently-failed rows.
2383
2578
  */
2384
- withFleetSettlementLock(operation: () => Promise<unknown>, wait?: boolean): Promise<boolean>;
2579
+ getQueueStats(): Promise<{
2580
+ liveRetry: number;
2581
+ stale: number;
2582
+ byReason: Record<string, number>;
2583
+ }>;
2385
2584
  /**
2386
- * Hold the fleet-wide self-relay submission lock for one chain submission.
2387
- *
2388
- * The session lock keeps one per-DVM gas EOA to one in-flight submission
2389
- * across the whole fleet — two machines submitting at once collide on the
2390
- * nonce. It is reached from `resource.settlePayment` inside
2391
- * {@link withChannelTransaction} and from upstream's claim/settle inside
2392
- * {@link withFleetSettlementLock}, so on both paths a host-pool client is
2393
- * already checked out: it therefore acquires from the terminal settlement
2394
- * pool, which is the rule in this class's doc, and never from the host pool
2395
- * the caller is holding.
2396
- *
2397
- * The wait is bounded by `lock_timeout` and a timeout **refuses** rather than
2398
- * proceeding unlocked, which is where this differs from
2399
- * {@link withFleetSettlementLock}: a fleet lock nobody got means the claim is
2400
- * someone else's turn, while a submission lock nobody got means the caller
2401
- * must not submit at all. The refusal is typed and retryable, and names the
2402
- * holder when Postgres will tell us who it is.
2585
+ * Deliver one bounded batch from the durable queue.
2403
2586
  *
2404
- * The relay's own in-process tail serialises callers before they reach here,
2405
- * so one process pins at most one settlement-pool client no matter how many
2406
- * deposits arrive.
2587
+ * Processes due, non-stale rows; failures back off up to five minutes and
2588
+ * become terminal after 24 hours. Successful deliveries remove their rows.
2407
2589
  */
2408
- withRelaySubmissionLock<T>(key: string, operation: () => Promise<T>): Promise<T>;
2409
- /** Release the dedicated settlement pool; the host-owned pool is left alone. */
2410
- close(): Promise<void>;
2411
- private settlementWriter;
2412
- private settlementPoolRef;
2413
- private runUnderFleetLock;
2414
- private updateInTransaction;
2590
+ drainPending(): Promise<void>;
2591
+ private enqueue;
2592
+ private insertPending;
2593
+ private attemptDelivery;
2594
+ private markStale;
2415
2595
  }
2416
2596
 
2417
2597
  interface TempoSessionSettlementStore {
@@ -2486,6 +2666,8 @@ declare class CreditLedger {
2486
2666
  readonly durable = true;
2487
2667
  private x402Settlements?;
2488
2668
  private expiryReleaseOutbox?;
2669
+ private hostingCleanupFence?;
2670
+ private readonly invoiceSettlementClients;
2489
2671
  constructor(pool: CreditLedgerPool, tempoSessionStore?: TempoSessionSettlementStore | undefined);
2490
2672
  /**
2491
2673
  * Bound after construction because the settlement store shares this ledger and is
@@ -2498,6 +2680,7 @@ declare class CreditLedger {
2498
2680
  * wiring, ledger transitions still run.
2499
2681
  */
2500
2682
  useCreditExpiryReleaseOutbox(outbox: CreditExpiryReleaseOutbox): void;
2683
+ useHostingCleanupFence(fence: HostingCleanupFence): void;
2501
2684
  init(): Promise<void>;
2502
2685
  private createTables;
2503
2686
  /**
@@ -2584,6 +2767,8 @@ declare class CreditLedger {
2584
2767
  drawId: string;
2585
2768
  amountMicro: number;
2586
2769
  jobId?: string;
2770
+ hostingAdmissionId?: string;
2771
+ hostingPaymentSeq?: number;
2587
2772
  /** External transaction handle — caller owns BEGIN/COMMIT (spec condition 3). */
2588
2773
  tx?: CreditLedgerQuerier;
2589
2774
  nowMs?: number;
@@ -2679,6 +2864,12 @@ declare class CreditLedger {
2679
2864
  amountMicro: number;
2680
2865
  /** Rail the money arrived on, for the deposit audit trail. */
2681
2866
  rail?: string;
2867
+ hostingAdmissionId?: string;
2868
+ hostingCallerPubkey?: string;
2869
+ hostingJobId?: string;
2870
+ hostingPaymentSeq?: number;
2871
+ hostingInvoicePaymentHash?: string;
2872
+ hostingInvoiceRepairPaymentHash?: string;
2682
2873
  /** External transaction handle — caller owns BEGIN/COMMIT (spec condition 3). */
2683
2874
  tx?: CreditLedgerQuerier;
2684
2875
  nowMs?: number;
@@ -3177,6 +3368,12 @@ interface CreditLedgerLike {
3177
3368
  amountMicro: number;
3178
3369
  expiryMs: number;
3179
3370
  basis: CreditFundingBasis;
3371
+ hostingAdmissionId?: string;
3372
+ hostingJobId?: string;
3373
+ hostingFundId?: string;
3374
+ hostingPaymentSeq?: number;
3375
+ hostingInvoicePaymentHash?: string;
3376
+ hostingInvoiceRepairPaymentHash?: string;
3180
3377
  tx?: CreditLedgerQuerier;
3181
3378
  nowMs?: number;
3182
3379
  }): Promise<CreditSnapshot>;
@@ -3185,6 +3382,8 @@ interface CreditLedgerLike {
3185
3382
  drawId: string;
3186
3383
  amountMicro: number;
3187
3384
  jobId?: string;
3385
+ hostingAdmissionId?: string;
3386
+ hostingPaymentSeq?: number;
3188
3387
  tx?: CreditLedgerQuerier;
3189
3388
  nowMs?: number;
3190
3389
  }): Promise<DrawResult>;
@@ -3218,6 +3417,12 @@ interface CreditLedgerLike {
3218
3417
  fundId: string;
3219
3418
  amountMicro: number;
3220
3419
  rail?: string;
3420
+ hostingAdmissionId?: string;
3421
+ hostingCallerPubkey?: string;
3422
+ hostingJobId?: string;
3423
+ hostingPaymentSeq?: number;
3424
+ hostingInvoicePaymentHash?: string;
3425
+ hostingInvoiceRepairPaymentHash?: string;
3221
3426
  tx?: CreditLedgerQuerier;
3222
3427
  nowMs?: number;
3223
3428
  }): Promise<FundingRecord>;
@@ -3871,6 +4076,13 @@ interface CreditFundArgs {
3871
4076
  * Rail value contributed by this funding; required on writes.
3872
4077
  */
3873
4078
  basis: CreditFundingBasis;
4079
+ /** Server-verified original hosted admission, never accepted from the caller body. */
4080
+ hostingAdmissionId?: string;
4081
+ hostingJobId?: string;
4082
+ hostingFundId?: string;
4083
+ hostingPaymentSeq?: number;
4084
+ hostingInvoicePaymentHash?: string;
4085
+ hostingInvoiceRepairPaymentHash?: string;
3874
4086
  /**
3875
4087
  * External transaction handle; caller owns BEGIN, COMMIT and ROLLBACK.
3876
4088
  */
@@ -4119,6 +4331,8 @@ interface TempoSessionLifecycle {
4119
4331
  */
4120
4332
  declare class PostgresTempoSessionStore implements Store.AtomicStore {
4121
4333
  private readonly pool;
4334
+ private hostingCleanupFence?;
4335
+ useHostingCleanupFence(fence: HostingCleanupFence): void;
4122
4336
  private readonly acceptance;
4123
4337
  constructor(pool: Pool);
4124
4338
  init(): Promise<void>;
@@ -4132,6 +4346,7 @@ declare class PostgresTempoSessionStore implements Store.AtomicStore {
4132
4346
  withVoucherAcceptance<value, result>(voucher: {
4133
4347
  channelId: string;
4134
4348
  cumulativeAmount: bigint;
4349
+ hostingFunding?: HostedFundingReservation;
4135
4350
  }, commit: (tx: PoolClient, voucher: TempoVoucherDelta) => Promise<result>, operation: () => Promise<value>): Promise<{
4136
4351
  value: value;
4137
4352
  result: result | undefined;
@@ -4240,6 +4455,7 @@ interface MppOpts {
4240
4455
  */
4241
4456
  tempoSession?: {
4242
4457
  store: Store.AtomicStore;
4458
+ hostedObligations?: HostedSettlementReportObligations;
4243
4459
  account: Account;
4244
4460
  getClient?: (parameters: {
4245
4461
  chainId?: number;
@@ -4300,6 +4516,10 @@ interface MppxServer {
4300
4516
  * always the mutating path, so this is the only one the SDK binds.
4301
4517
  */
4302
4518
  broadcastCredential(credential: Credential.Credential | string, options?: any): Promise<Receipt.Receipt>;
4519
+ /** Non-mutating mppx HMAC, route, expiry and method preflight. */
4520
+ validateCredential?(credential: Credential.Credential | string, options?: any): Promise<Method.Validation>;
4521
+ completeHostedTempoCharge?: (challengeId: string) => Promise<void>;
4522
+ permitHostedTempoClose?: (credential: Credential.Credential | string, reservationId: string) => void;
4303
4523
  /**
4304
4524
  * Typed dispatcher over `mppx.challenge.<method>.<intent>(opts)`. Localizes
4305
4525
  * the cast across the open challenge dispatcher so callers don't repeat it.
@@ -5410,7 +5630,7 @@ interface DVMConfig<State = Record<string, unknown>, InputSchema extends ZodLike
5410
5630
  /** Payment methods this DVM accepts. Default: derived from configured rails — adds "cashu" when `mints` is set, "x402" when `x402` is set, "tempo" when `tempo` is set. */
5411
5631
  paymentMethods?: PaymentMethod[];
5412
5632
  /** x402 stablecoin payment configuration (provider-side). */
5413
- x402?: X402Config;
5633
+ x402?: X402Config$1;
5414
5634
  /** MPP multi-rail payment handle (provider-side, built via `createMppFromOpts`). */
5415
5635
  mpp?: MppxServer;
5416
5636
  /**
@@ -5499,7 +5719,7 @@ interface DVMDescriptor<State = Record<string, unknown>, InputSchema extends Zod
5499
5719
  /** Payment methods this DVM accepts. */
5500
5720
  readonly paymentMethods?: PaymentMethod[];
5501
5721
  /** x402 stablecoin payment configuration. */
5502
- readonly x402?: X402Config;
5722
+ readonly x402?: X402Config$1;
5503
5723
  /** MPP multi-rail payment handle. */
5504
5724
  readonly mpp?: MppxServer;
5505
5725
  /**
@@ -5541,4 +5761,4 @@ declare class StepCache {
5541
5761
  static deserialize(records: StepRecord[]): StepCache;
5542
5762
  }
5543
5763
 
5544
- export { type X402Version as $, type ApprovalContent as A, SignedRequestError as B, type Currency as C, type DVMConfig as D, type SignedRequestFailure as E, type SignedRequestReplayStore as F, type SignedRequestSignOpts as G, type SignedRequestStatementHeader as H, type IncomingMessage as I, type JobCost as J, type KVStore as K, type Logger as L, type SignedRequestVerifier as M, createSignedRequestVerifier as N, isZodSchema as O, type PaymentContent as P, type QuoteConfig as Q, type ResolvedCreditConfig as R, type SDKJobContext as S, signedRequestStatementHeader as T, UnsupportedCurrencyError as U, validateCurrency as V, type JsonValue as W, type FundingReceipt as X, type JobReceipt as Y, type ZodLike as Z, type PaymentRequired as _, type DVMDescriptor as a, isDrainReceipt as a$, type MppxCredential as a0, type Message as a1, type MessageType as a2, type MppxChallenge as a3, X402_BATCH_SETTLEMENT_SCHEME as a4, X402_EXACT_SCHEME as a5, type ReceiptCredit as a6, type X402Wallet as a7, type ResourceInfo as a8, type BuildPaymentRequirementsOpts as a9, type UnsignedFundingReceipt as aA, type UnsignedJobReceipt as aB, type VerifyResponse as aC, type WorkingContent as aD, type X402Config as aE, type X402ResponseBody as aF, type X402SelfRelayRpcFailureReason as aG, X402_DEFAULT_NETWORK as aH, X402_V1_VERSION as aI, X402_VERSION as aJ, buildPaymentRequiredV2 as aK, buildPaymentRequirements as aL, caip2ToX402Network as aM, canonicalRequestPath as aN, canonicaliseForSigning as aO, canonicalize as aP, chainIdFromCaip2 as aQ, computeResultHash as aR, decodePayment as aS, decodePaymentRequiredHeader as aT, encodePayment as aU, encodePaymentRequiredHeader as aV, encodeSettleResponseHeader as aW, exactEvmAuthorization as aX, isArtifactMessage as aY, isCancelMessage as aZ, isCompleteMessage as a_, type CapabilityDescriptor as aa, type CashuMode as ab, type CompleteContent as ac, type DrainReceipt as ad, type DrainReceiptEvent as ae, type ExactEvmPayload as af, type ExactEvmPayloadAuthorization as ag, type FundingMethod as ah, type MessageFrom as ai, type PaymentPayload as aj, type PaymentPayloadV1 as ak, type PaymentPayloadV2 as al, type PaymentRequestContent as am, type PaymentRequiredV2 as an, type PaymentRequirements as ao, type PaymentRequirementsV1 as ap, type PaymentRequirementsV2 as aq, type PromptContent as ar, RAIL_REFUNDABLE as as, type ReceiptOutcome as at, type ReceiptPayment as au, type SettleResponse as av, StepCache as aw, type StepRecord as ax, type TextContent as ay, type UnsignedDrainReceipt as az, type ArtifactContent as b, type StalePendingDrawCursor as b$, isFundingReceipt as b0, isPaymentRequestMessage as b1, isPromptMessage as b2, isSignedJobReceipt as b3, isTextMessage as b4, isWorkingMessage as b5, paymentRequiredV2FromV1 as b6, signDrainReceipt as b7, signFundingReceipt as b8, signReceipt as b9, _testing as bA, createDefaultReplayStore as bB, depleteLots as bC, fifoOrder as bD, inKindDrawMsats as bE, isInKindDepletion as bF, isNonChannelBitcoinRail as bG, lotOwedSats as bH, netOwedSats as bI, wrapMppx as bJ, type CreditDrainEnqueue as bK, type DVMAuthScheme as bL, type CreditLedgerLike as bM, type X402RefundSettlementGate as bN, type CreditSnapshot as bO, type DrawResult as bP, type X402SettlementStatus as bQ, type X402UnresolvedRefund as bR, type GrownDrawResult as bS, type DrawResolution as bT, type FundingRecord as bU, type CreditInvoiceRecord as bV, type InvoiceSettlement as bW, type BlockedInvoiceCursor as bX, type InvoiceReconciliation as bY, type InvoiceWriteOff as bZ, type DrawRecord as b_, usdcContractByCaip2 as ba, usdcContractFor as bb, usdcDomainNameFor as bc, usdcDomainVersionFor as bd, verifyDrainReceipt as be, verifyFundingReceipt as bf, verifyReceipt as bg, x402NetworkToCaip2 as bh, type X402ExactVersionSupport as bi, type X402Receipt as bj, type TransactionalPayoutHook as bk, type CashuMeltCompleted as bl, type CreditDepositPayload as bm, type CreditDrainPayload as bn, type FundingLot as bo, type LotDebit as bp, type LotDepletion as bq, type MppxServer as br, NON_CHANNEL_BITCOIN_RAILS as bs, type NonChannelBitcoinRail as bt, PayoutReporter as bu, PostgresTempoSessionStore as bv, RevenueReporter as bw, SIGNED_ENVELOPE_FIELDS as bx, SIGNED_ENVELOPE_TYPES as by, type X402TrackedChannel as bz, type CancelContent as c, type CreditDrawReleaseEnqueue as c$, type TempoCreditLossEvidence as c0, type CreditLedgerQuerier as c1, type TempoCreditLoss as c2, type X402CreditLossEvidence as c3, type X402CreditLoss as c4, type DrainMethod as c5, type DrainRequestResult as c6, type BitcoinDepositLiability as c7, type CreditDrainRecord as c8, type ChannelDrainCursor as c9, type ReplayStoreBackend as cA, type RevenueSkippedNoRailPayload as cB, type RevenueSkippedNoRailReason as cC, type Secp256k1AuthOpts as cD, type X402ChannelStorageOpts as cE, type X402RelayLockHolder as cF, type X402RelaySubmissionLock as cG, X402RelaySubmissionLockError as cH, allocateDrawValue as cI, clientCompatibilityAttributes as cJ, clientCompatibilityMiddleware as cK, clientUpgradeRequired as cL, parseClientCapabilities as cM, parseClientCompatibility as cN, parseDvmClient as cO, parseProtocolVersion as cP, requireClientCompatibility as cQ, secp256k1Auth as cR, signedRequestInput as cS, type X402SettlementIntent as cT, type X402SettlementCursor as cU, type X402SettlementWriteOff as cV, type X402FacilitatorAuth as cW, type X402BatchSettlementConfig as cX, type X402PayoutObserver as cY, type CreditDepositEnqueue as cZ, type X402SettlementReconciliationReason as c_, type DrainWriteOff as ca, type DrainReleaseResult as cb, type DrainFulfilment as cc, type DrainTransitionResult as cd, CLIENT_COMPATIBILITY_HEADERS as ce, type ClientCompatibility as cf, type ClientCompatibilityEnv as cg, type ClientCompatibilityGate as ch, type ClientCompatibilityRequirement as ci, type ClientSemVer as cj, type CreditFundingBasis as ck, type CreditInvoiceStatus as cl, CreditLedger as cm, CreditLedgerError as cn, type CreditLedgerErrorCode as co, type CreditLedgerErrorDetails as cp, type CreditLedgerPool as cq, type CreditStatus as cr, DRAIN_DELIVERY_RESERVE_SATS as cs, DVM_PROTOCOL_VERSION as ct, type DrainConflictReason as cu, type DrawRailValue as cv, type DrawStatus as cw, type JobCostReportPayload as cx, type JobTerminalReportPayload as cy, PostgresX402ChannelStorage as cz, type CanonicalEnvelope as d, type CreateSignedRequestVerifierOpts as e, type CreditConfig as f, type CreditView as g, DEFAULT_CREDIT_MAX as h, DEFAULT_CREDIT_MIN as i, DEFAULT_CREDIT_TTL_SECONDS as j, DEFAULT_JOB_RETENTION_DAYS as k, type DVMRouteContext as l, type InputType as m, InvalidCurrencyError as n, type PaymentMethod as o, type PriceValue as p, type ProgressContent as q, type PromptOpts as r, type QuoteContext as s, type QuoteResult as t, type ResponseContent as u, type SDKPaymentRequestOpts as v, SIGNED_REQUEST_AUTH_ID as w, SIGNED_REQUEST_STATEMENT_VERSION as x, type SignedRequestAudience as y, type SignedRequestDomain as z };
5764
+ export { type X402Version as $, type ApprovalContent as A, SignedRequestError as B, type Currency as C, type DVMConfig as D, type SignedRequestFailure as E, type SignedRequestReplayStore as F, type SignedRequestSignOpts as G, type SignedRequestStatementHeader as H, type IncomingMessage as I, type JobCost as J, type KVStore as K, type Logger as L, type SignedRequestVerifier as M, createSignedRequestVerifier as N, isZodSchema as O, type PaymentContent as P, type QuoteConfig as Q, type ResolvedCreditConfig as R, type SDKJobContext as S, signedRequestStatementHeader as T, UnsupportedCurrencyError as U, validateCurrency as V, type JsonValue as W, type FundingReceipt as X, type JobReceipt as Y, type ZodLike as Z, type PaymentRequired as _, type DVMDescriptor as a, isDrainReceipt as a$, type MppxCredential as a0, type Message as a1, type MessageType as a2, type MppxChallenge as a3, X402_BATCH_SETTLEMENT_SCHEME as a4, X402_EXACT_SCHEME as a5, type DrainReceipt as a6, type ReceiptCredit as a7, type X402Wallet as a8, type ResourceInfo as a9, type UnsignedFundingReceipt as aA, type UnsignedJobReceipt as aB, type VerifyResponse as aC, type WorkingContent as aD, type X402Config$1 as aE, type X402ResponseBody as aF, type X402SelfRelayRpcFailureReason as aG, X402_DEFAULT_NETWORK as aH, X402_V1_VERSION as aI, X402_VERSION as aJ, buildPaymentRequiredV2 as aK, buildPaymentRequirements as aL, caip2ToX402Network as aM, canonicalRequestPath as aN, canonicaliseForSigning as aO, canonicalize as aP, chainIdFromCaip2 as aQ, computeResultHash as aR, decodePayment as aS, decodePaymentRequiredHeader as aT, encodePayment as aU, encodePaymentRequiredHeader as aV, encodeSettleResponseHeader as aW, exactEvmAuthorization as aX, isArtifactMessage as aY, isCancelMessage as aZ, isCompleteMessage as a_, type BuildPaymentRequirementsOpts as aa, type CapabilityDescriptor as ab, type CashuMode as ac, type CompleteContent as ad, type DrainReceiptEvent as ae, type ExactEvmPayload as af, type ExactEvmPayloadAuthorization as ag, type FundingMethod as ah, type MessageFrom as ai, type PaymentPayload as aj, type PaymentPayloadV1 as ak, type PaymentPayloadV2 as al, type PaymentRequestContent as am, type PaymentRequiredV2 as an, type PaymentRequirements as ao, type PaymentRequirementsV1 as ap, type PaymentRequirementsV2 as aq, type PromptContent as ar, RAIL_REFUNDABLE as as, type ReceiptOutcome as at, type ReceiptPayment as au, type SettleResponse as av, StepCache as aw, type StepRecord as ax, type TextContent as ay, type UnsignedDrainReceipt as az, type ArtifactContent as b, type DrawRecord as b$, isFundingReceipt as b0, isPaymentRequestMessage as b1, isPromptMessage as b2, isSignedJobReceipt as b3, isTextMessage as b4, isWorkingMessage as b5, paymentRequiredV2FromV1 as b6, signDrainReceipt as b7, signFundingReceipt as b8, signReceipt as b9, _testing as bA, createDefaultReplayStore as bB, depleteLots as bC, fifoOrder as bD, inKindDrawMsats as bE, isInKindDepletion as bF, isNonChannelBitcoinRail as bG, lotOwedSats as bH, netOwedSats as bI, wrapMppx as bJ, type CreditDrainEnqueue as bK, HostedSettlementReportObligations as bL, type DVMAuthScheme as bM, type CreditLedgerLike as bN, type X402RefundSettlementGate as bO, type CreditSnapshot as bP, type DrawResult as bQ, type X402SettlementStatus as bR, type X402UnresolvedRefund as bS, type GrownDrawResult as bT, type DrawResolution as bU, type FundingRecord as bV, type CreditInvoiceRecord as bW, type InvoiceSettlement as bX, type BlockedInvoiceCursor as bY, type InvoiceReconciliation as bZ, type InvoiceWriteOff as b_, usdcContractByCaip2 as ba, usdcContractFor as bb, usdcDomainNameFor as bc, usdcDomainVersionFor as bd, verifyDrainReceipt as be, verifyFundingReceipt as bf, verifyReceipt as bg, x402NetworkToCaip2 as bh, type X402ExactVersionSupport as bi, type X402Receipt as bj, type TransactionalPayoutHook as bk, type CashuMeltCompleted as bl, type CreditDepositPayload as bm, type CreditDrainPayload as bn, type FundingLot as bo, type LotDebit as bp, type LotDepletion as bq, type MppxServer as br, NON_CHANNEL_BITCOIN_RAILS as bs, type NonChannelBitcoinRail as bt, PayoutReporter as bu, PostgresTempoSessionStore as bv, RevenueReporter as bw, SIGNED_ENVELOPE_FIELDS as bx, SIGNED_ENVELOPE_TYPES as by, type X402TrackedChannel as bz, type CancelContent as c, type X402BatchSettlementConfig as c$, type StalePendingDrawCursor as c0, type TempoCreditLossEvidence as c1, type CreditLedgerQuerier as c2, type TempoCreditLoss as c3, type X402CreditLossEvidence as c4, type X402CreditLoss as c5, type DrainMethod as c6, type DrainRequestResult as c7, type BitcoinDepositLiability as c8, type CreditDrainRecord as c9, type JobTerminalReportPayload as cA, PostgresX402ChannelStorage as cB, type ReplayStoreBackend as cC, type RevenueSkippedNoRailPayload as cD, type RevenueSkippedNoRailReason as cE, type Secp256k1AuthOpts as cF, type X402ChannelStorageOpts as cG, type X402RelayLockHolder as cH, type X402RelaySubmissionLock as cI, X402RelaySubmissionLockError as cJ, allocateDrawValue as cK, clientCompatibilityAttributes as cL, clientCompatibilityMiddleware as cM, clientUpgradeRequired as cN, parseClientCapabilities as cO, parseClientCompatibility as cP, parseDvmClient as cQ, parseProtocolVersion as cR, requireClientCompatibility as cS, secp256k1Auth as cT, signedRequestInput as cU, type HostedIntent as cV, type X402SettlementIntent as cW, type HostedFundingReservation as cX, type X402SettlementCursor as cY, type X402SettlementWriteOff as cZ, type X402FacilitatorAuth as c_, type ChannelDrainCursor as ca, type DrainWriteOff as cb, type DrainReleaseResult as cc, type DrainFulfilment as cd, type DrainTransitionResult as ce, HostingCleanupFence as cf, CLIENT_COMPATIBILITY_HEADERS as cg, type ClientCompatibility as ch, type ClientCompatibilityEnv as ci, type ClientCompatibilityGate as cj, type ClientCompatibilityRequirement as ck, type ClientSemVer as cl, type CreditFundingBasis as cm, type CreditInvoiceStatus as cn, CreditLedger as co, CreditLedgerError as cp, type CreditLedgerErrorCode as cq, type CreditLedgerErrorDetails as cr, type CreditLedgerPool as cs, type CreditStatus as ct, DRAIN_DELIVERY_RESERVE_SATS as cu, DVM_PROTOCOL_VERSION as cv, type DrainConflictReason as cw, type DrawRailValue as cx, type DrawStatus as cy, type JobCostReportPayload as cz, type CanonicalEnvelope as d, type X402PayoutObserver as d0, type CreditDepositEnqueue as d1, type X402SettlementReconciliationReason as d2, type CreditDrawReleaseEnqueue as d3, type CreateSignedRequestVerifierOpts as e, type CreditConfig as f, type CreditView as g, DEFAULT_CREDIT_MAX as h, DEFAULT_CREDIT_MIN as i, DEFAULT_CREDIT_TTL_SECONDS as j, DEFAULT_JOB_RETENTION_DAYS as k, type DVMRouteContext as l, type InputType as m, InvalidCurrencyError as n, type PaymentMethod as o, type PriceValue as p, type ProgressContent as q, type PromptOpts as r, type QuoteContext as s, type QuoteResult as t, type ResponseContent as u, type SDKPaymentRequestOpts as v, SIGNED_REQUEST_AUTH_ID as w, SIGNED_REQUEST_STATEMENT_VERSION as x, type SignedRequestAudience as y, type SignedRequestDomain as z };