@dvmkit/sdk 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4VV7WACY.js → chunk-2XQLL6N6.js} +1 -1
- package/dist/{chunk-LCCG5HQM.js → chunk-3Q6FTZDB.js} +2 -2
- package/dist/{chunk-TRY7BJA4.js → chunk-3R7NQA3Z.js} +1 -1
- package/dist/{chunk-KGL3YMZA.js → chunk-AJ6UHXUA.js} +1 -1
- package/dist/{chunk-BAQRUYRL.js → chunk-EWZFANHJ.js} +1 -1
- package/dist/{chunk-K2MSIKMM.js → chunk-FFXXGCVG.js} +1 -1
- package/dist/{chunk-WLPLHXNP.js → chunk-JNECYURT.js} +1 -1
- package/dist/{chunk-TFDR2YP2.js → chunk-KFCEXCZM.js} +1 -1
- package/dist/{chunk-IYQQXEHV.js → chunk-LDIPVPKI.js} +1 -1
- package/dist/{chunk-QSZW2I5Y.js → chunk-M3T5JYDJ.js} +15 -8
- package/dist/{chunk-Q3VYRU5R.js → chunk-MB6R4XPZ.js} +1 -1
- package/dist/{chunk-IBX2ORBV.js → chunk-MMGSYMIC.js} +1 -1
- package/dist/{chunk-LFVIZH2G.js → chunk-MU3IE434.js} +278 -152
- package/dist/{chunk-OIP44KBB.js → chunk-QGNXYVBO.js} +47 -12
- package/dist/{chunk-73C4JN2E.js → chunk-QNKK7SPY.js} +273 -54
- package/dist/{chunk-YYRY32NR.js → chunk-STJBBVNL.js} +2 -2
- package/dist/{chunk-WB3E7KE7.js → chunk-VMHYGKWT.js} +1 -1
- package/dist/{chunk-GAZBBMAA.js → chunk-VTHYQCDB.js} +1 -1
- package/dist/{chunk-LW7KZQHG.js → chunk-VUGEEMLT.js} +1 -1
- package/dist/{chunk-HC4I7LAV.js → chunk-W3VGCNG6.js} +1 -1
- package/dist/{chunk-GODOH2AF.js → chunk-Y6DO5WSO.js} +1 -1
- package/dist/{chunk-ZBQKXAYK.js → chunk-YOSZIWC3.js} +1 -1
- package/dist/{content-retention-CVBYehRS.d.ts → content-retention-CrILCNxk.d.ts} +1 -1
- package/dist/{credit-ledger-CZQ56DYI.js → credit-ledger-4PZNAMDT.js} +3 -3
- package/dist/{credit-menu-QntEhZJS.d.ts → credit-menu-VwX_6KM7.d.ts} +4 -4
- package/dist/{fx-CMyNFenZ.d.ts → fx-BM5h4o5S.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/internal/caller.d.ts +7 -7
- package/dist/internal/caller.js +7 -7
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +20 -20
- package/dist/{job-store-qOD-FrQF.d.ts → job-store-92v8e1LQ.d.ts} +1 -1
- package/dist/{lightning-backend-DtdG6UT5.d.ts → lightning-backend-9oky_wJi.d.ts} +1 -1
- package/dist/{memory-credit-ledger-EMN7G3MO.js → memory-credit-ledger-RP7DT5IY.js} +4 -4
- package/dist/{mpp-setup-VW53KFPO.js → mpp-setup-FURD45LI.js} +3 -3
- package/dist/{payout-reporter-TZX2S7KG.js → payout-reporter-HVJ6WUF3.js} +2 -2
- package/dist/{postgres-consumed-credential-store-V464OAJB.js → postgres-consumed-credential-store-WO6OCFXZ.js} +1 -1
- package/dist/{postgres-job-store-27VOCSPO.js → postgres-job-store-FSRUPYH2.js} +2 -2
- package/dist/{postgres-replay-store-7GJFAQXY.js → postgres-replay-store-XXJSA457.js} +2 -2
- package/dist/{pricing-NNMBNP7N.js → pricing-X4XU5HXY.js} +3 -3
- package/dist/{revenue-reporter-4EAVPWXS.js → revenue-reporter-EV3K5L6B.js} +2 -2
- package/dist/server/index.d.ts +74 -11
- package/dist/server/index.js +65 -27
- package/dist/{step-cache-B7svmPz3.d.ts → step-cache--7yA5Ihr.d.ts} +48 -6
- package/dist/{tempo-charge-store-2RPOJBM2.js → tempo-charge-store-5JOIYUIH.js} +2 -2
- package/dist/{tempo-session-store-LI3Q3A3U.js → tempo-session-store-S6QMLQUA.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +6 -6
- package/dist/{usd-D3izFz71.d.ts → usd-C7lvLvLk.d.ts} +1 -1
- package/dist/{x402-SL36LSOD.js → x402-EN5LSOVM.js} +3 -3
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-XRLKGCAW.js";
|
|
6
6
|
import {
|
|
7
7
|
x402EarnedCeiling
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FFXXGCVG.js";
|
|
9
9
|
import {
|
|
10
10
|
createX402FacilitatorAuthHeaders,
|
|
11
11
|
createX402FacilitatorClient,
|
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
verifyWithFacilitator,
|
|
15
15
|
withPaymentDiagnostics,
|
|
16
16
|
withPaymentStage
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-VTHYQCDB.js";
|
|
18
18
|
import {
|
|
19
19
|
MemoryCreditLedger
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YOSZIWC3.js";
|
|
21
21
|
import {
|
|
22
22
|
PostgresJobStore
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-Y6DO5WSO.js";
|
|
24
24
|
import {
|
|
25
25
|
parseStoreValue
|
|
26
26
|
} from "./chunk-YG7G4DPZ.js";
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
humanizeZodIssues,
|
|
32
32
|
signedRequestInput,
|
|
33
33
|
zodIssuesOf
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-2XQLL6N6.js";
|
|
35
35
|
import {
|
|
36
36
|
MemoryJobStore,
|
|
37
37
|
UNCAPPABLE_ASK_TOTAL,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
isJobRetentionStore,
|
|
41
41
|
isReceiptIssuingStore,
|
|
42
42
|
isStreamableJobStore
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-KFCEXCZM.js";
|
|
44
44
|
import {
|
|
45
45
|
ContentHoldError,
|
|
46
46
|
JobCancelledError,
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
signReceipt,
|
|
81
81
|
toLockPubkey,
|
|
82
82
|
verifyChallenge
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-VUGEEMLT.js";
|
|
84
84
|
import {
|
|
85
85
|
X402_BATCH_SETTLEMENT_SCHEME,
|
|
86
86
|
X402_DEFAULT_FACILITATOR,
|
|
@@ -108,7 +108,7 @@ import {
|
|
|
108
108
|
} from "./chunk-UKD3R2KU.js";
|
|
109
109
|
import {
|
|
110
110
|
createConsoleLogger
|
|
111
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-3R7NQA3Z.js";
|
|
112
112
|
import {
|
|
113
113
|
StepCache
|
|
114
114
|
} from "./chunk-BQJIFRAZ.js";
|
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
roundUsd,
|
|
126
126
|
satsToFiat,
|
|
127
127
|
validateCurrency
|
|
128
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-W3VGCNG6.js";
|
|
129
129
|
import {
|
|
130
130
|
CreditLedger,
|
|
131
131
|
CreditLedgerError,
|
|
@@ -141,13 +141,13 @@ import {
|
|
|
141
141
|
isNonChannelBitcoinRail,
|
|
142
142
|
netOwedSats,
|
|
143
143
|
x402SettlementPending
|
|
144
|
-
} from "./chunk-
|
|
144
|
+
} from "./chunk-STJBBVNL.js";
|
|
145
145
|
import {
|
|
146
146
|
withSdkInitLock
|
|
147
147
|
} from "./chunk-COYKJRMM.js";
|
|
148
148
|
import {
|
|
149
149
|
withPostgresRecoveryContext
|
|
150
|
-
} from "./chunk-
|
|
150
|
+
} from "./chunk-MMGSYMIC.js";
|
|
151
151
|
import {
|
|
152
152
|
DvmError,
|
|
153
153
|
x402NetworkByCaip2
|
|
@@ -162,17 +162,17 @@ import {
|
|
|
162
162
|
installHostedTempoCloseGuard,
|
|
163
163
|
isTempoInsufficientFundsError,
|
|
164
164
|
resolveTempoNetwork
|
|
165
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-MU3IE434.js";
|
|
166
166
|
import {
|
|
167
167
|
loggers,
|
|
168
168
|
pinDimension,
|
|
169
169
|
withSpan,
|
|
170
170
|
withTraceContext
|
|
171
|
-
} from "./chunk-
|
|
171
|
+
} from "./chunk-M3T5JYDJ.js";
|
|
172
172
|
import {
|
|
173
173
|
createDiagnosticCapabilityAuthority,
|
|
174
174
|
diagnosticConsole
|
|
175
|
-
} from "./chunk-
|
|
175
|
+
} from "./chunk-QGNXYVBO.js";
|
|
176
176
|
|
|
177
177
|
// src/sdk/server/lock-pubkey-store.ts
|
|
178
178
|
async function initLockPubkeyTable(db) {
|
|
@@ -3884,7 +3884,7 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
3884
3884
|
x402Requirements
|
|
3885
3885
|
});
|
|
3886
3886
|
}
|
|
3887
|
-
const { verifyX402Payment } = await import("./x402-
|
|
3887
|
+
const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
|
|
3888
3888
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
3889
3889
|
x402Payment,
|
|
3890
3890
|
x402Config,
|
|
@@ -4193,7 +4193,18 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
4193
4193
|
replayRealm,
|
|
4194
4194
|
cred.challenge.id,
|
|
4195
4195
|
CONSUMED_CREDENTIAL_TTL_MS / 1e3
|
|
4196
|
-
))
|
|
4196
|
+
)).catch((error) => {
|
|
4197
|
+
const transactionHash = tempoReceiptTransactionHash(tempoReceipt);
|
|
4198
|
+
if (!transactionHash) throw error;
|
|
4199
|
+
diagnosticConsole.error({
|
|
4200
|
+
level: "error",
|
|
4201
|
+
event: "tempo_replay_mark_failed",
|
|
4202
|
+
phase: "upfront",
|
|
4203
|
+
rail: "tempo",
|
|
4204
|
+
transaction_hash: transactionHash,
|
|
4205
|
+
...paymentFailureFields(error)
|
|
4206
|
+
});
|
|
4207
|
+
});
|
|
4197
4208
|
}
|
|
4198
4209
|
if (ledgerCtx) {
|
|
4199
4210
|
try {
|
|
@@ -4897,7 +4908,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
|
|
|
4897
4908
|
snapshot
|
|
4898
4909
|
});
|
|
4899
4910
|
}
|
|
4900
|
-
const { verifyX402Payment } = await import("./x402-
|
|
4911
|
+
const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
|
|
4901
4912
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
4902
4913
|
body.content.x402_payment,
|
|
4903
4914
|
opts.x402Config,
|
|
@@ -5328,7 +5339,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
|
|
|
5328
5339
|
try {
|
|
5329
5340
|
const requiredMsats = job.pendingPaymentMsats ?? 0;
|
|
5330
5341
|
const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
|
|
5331
|
-
const { verifyX402Payment } = await import("./x402-
|
|
5342
|
+
const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
|
|
5332
5343
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
5333
5344
|
body.content.x402_payment,
|
|
5334
5345
|
opts.x402Config,
|
|
@@ -7572,13 +7583,8 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7572
7583
|
try {
|
|
7573
7584
|
const state = await rail.mpp.getTempoSessionDrainState?.(expectedChannelId);
|
|
7574
7585
|
if (state?.finalized || state?.deposit === 0n) {
|
|
7575
|
-
const recovered2 = await rail.mpp
|
|
7576
|
-
|
|
7577
|
-
record.drainId
|
|
7578
|
-
);
|
|
7579
|
-
if (recovered2) {
|
|
7580
|
-
return await markTempoDrainSent(deps, rail.mpp, record, expectedChannelId, recovered2);
|
|
7581
|
-
}
|
|
7586
|
+
const recovered2 = await recoverTempoDrain(deps, rail.mpp, record, expectedChannelId);
|
|
7587
|
+
if (recovered2) return recovered2;
|
|
7582
7588
|
}
|
|
7583
7589
|
if (state && (state.closeRequestedAt !== 0n || isDefinitiveTempoCloseFailure(error))) {
|
|
7584
7590
|
return await releaseTempoDrain(c, deps, record, expectedChannelId);
|
|
@@ -7666,10 +7672,38 @@ function tempoExitRequiredResponse(record) {
|
|
|
7666
7672
|
function tempoCloseCredentialNeedsRefresh(error) {
|
|
7667
7673
|
return error instanceof MppxErrors.InvalidChallengeError || error instanceof MppxErrors.PaymentExpiredError;
|
|
7668
7674
|
}
|
|
7675
|
+
function tempoRefundUnprovenResponse(record) {
|
|
7676
|
+
return jsonPaymentError("settlement_pending", 409, {
|
|
7677
|
+
message: "The full Tempo refund could not be proven yet.",
|
|
7678
|
+
display: "Your funds remain held and have not been lost. The DVM operator has been alerted.",
|
|
7679
|
+
hint: `Keep drain ${record.drainId}; no replacement drain is needed.`,
|
|
7680
|
+
extra: { drain_registered: true, drain_id: record.drainId }
|
|
7681
|
+
});
|
|
7682
|
+
}
|
|
7669
7683
|
async function recoverTempoDrain(deps, mpp, record, channelId) {
|
|
7670
7684
|
try {
|
|
7671
|
-
const
|
|
7672
|
-
|
|
7685
|
+
const result = await mpp.recoverTempoSessionClose?.(channelId, record.drainId, record.creditId);
|
|
7686
|
+
const durable = await deps.ledger.getDrain({ creditId: record.creditId, drainId: record.drainId });
|
|
7687
|
+
if (durable?.status === "sent") return durable;
|
|
7688
|
+
if (result && typeof result === "object") {
|
|
7689
|
+
return tempoRefundUnprovenResponse(durable ?? record);
|
|
7690
|
+
}
|
|
7691
|
+
if (!result) return null;
|
|
7692
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(result)) {
|
|
7693
|
+
loggers.tempo.warn("tempo_session_drain_refund_unproven", {
|
|
7694
|
+
channel_id: channelId,
|
|
7695
|
+
credit_id: record.creditId,
|
|
7696
|
+
drain_id: record.drainId,
|
|
7697
|
+
amount_at_stake_native: record.drainedNative?.toString() ?? null,
|
|
7698
|
+
amount_at_stake_micro: record.amountMicro,
|
|
7699
|
+
currency: record.currency,
|
|
7700
|
+
reason: "close_evidence_missing",
|
|
7701
|
+
error: "Recovery returned no proven refund"
|
|
7702
|
+
});
|
|
7703
|
+
return tempoRefundUnprovenResponse(durable ?? record);
|
|
7704
|
+
}
|
|
7705
|
+
if (durable?.status === "exit_required") return null;
|
|
7706
|
+
return await markTempoDrainSent(deps, mpp, durable ?? record, channelId, result);
|
|
7673
7707
|
} catch (error) {
|
|
7674
7708
|
const refused = tempoFloorRefusal(error, record);
|
|
7675
7709
|
if (refused) return refused;
|
|
@@ -7679,7 +7713,9 @@ async function recoverTempoDrain(deps, mpp, record, channelId) {
|
|
|
7679
7713
|
error: error instanceof Error ? error.message : String(error)
|
|
7680
7714
|
});
|
|
7681
7715
|
return jsonPaymentError("payment_invalid", 402, {
|
|
7682
|
-
message: "The Tempo session close is still being reconciled; retry this drain."
|
|
7716
|
+
message: "The Tempo session close is still being reconciled; retry this drain.",
|
|
7717
|
+
hint: `Retry this same drain ${record.drainId} after the service can read and persist the close evidence.`,
|
|
7718
|
+
retryable: true
|
|
7683
7719
|
});
|
|
7684
7720
|
}
|
|
7685
7721
|
}
|
|
@@ -9646,6 +9682,16 @@ import { createPublicClient as createPublicClient4, decodeEventLog, decodeFuncti
|
|
|
9646
9682
|
var HOSTED_SETTLEMENT_UNRESOLVED_SQL = `state NOT IN ('delivered','no_report')
|
|
9647
9683
|
OR (rail='x402' AND operation IN ('claim','refund')
|
|
9648
9684
|
AND COALESCE(original->>'authorizationCallData','') !~* '^0x([0-9a-f]{2})+$')`;
|
|
9685
|
+
var TEMPO_CLOSE_RETRYABLE_SQL = `target.rail='tempo' AND target.operation='close'
|
|
9686
|
+
AND target.state IN ('unmatched','no_report') AND target.tx_hash IS NULL
|
|
9687
|
+
AND NOT EXISTS (
|
|
9688
|
+
SELECT 1 FROM sdk_hosted_settlement_report_obligations other
|
|
9689
|
+
WHERE other.dvm_id=target.dvm_id AND other.scope=target.scope
|
|
9690
|
+
AND other.rail='tempo' AND other.operation='close' AND other.channel_id=target.channel_id
|
|
9691
|
+
AND other.id<>target.id AND (other.state<>'no_report' OR other.tx_hash IS NOT NULL)
|
|
9692
|
+
)`;
|
|
9693
|
+
var TEMPO_CLOSE_WRITE_OFF_ALLOWED_SQL = `target.rail='tempo' AND target.operation='close'
|
|
9694
|
+
AND target.state='unmatched' AND target.tx_hash IS NULL`;
|
|
9649
9695
|
var X402_SETTLED_EVENT = parseAbiItem2(
|
|
9650
9696
|
"event Settled(address indexed receiver,address indexed token,address indexed sender,uint128 amount)"
|
|
9651
9697
|
);
|
|
@@ -9860,7 +9906,7 @@ var HostedSettlementReportObligations = class {
|
|
|
9860
9906
|
network TEXT NOT NULL, contract TEXT NOT NULL, channel_id TEXT NOT NULL,
|
|
9861
9907
|
original JSONB NOT NULL,
|
|
9862
9908
|
state TEXT NOT NULL CHECK (state IN ('reserved','observed','report_queued','delivered','no_report','exhausted','unmatched')),
|
|
9863
|
-
close_search_before BIGINT, close_write_off_note TEXT,
|
|
9909
|
+
close_search_before BIGINT, close_write_off_note TEXT, close_written_off_at_ms BIGINT,
|
|
9864
9910
|
tx_hash TEXT, block_number BIGINT, block_hash TEXT, delta_native NUMERIC(78,0),
|
|
9865
9911
|
payout_id TEXT, report_id TEXT, report_payload JSONB, delivered_at_ms BIGINT,
|
|
9866
9912
|
created_at_ms BIGINT NOT NULL DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint,
|
|
@@ -9877,7 +9923,7 @@ var HostedSettlementReportObligations = class {
|
|
|
9877
9923
|
`SELECT attname FROM pg_attribute
|
|
9878
9924
|
WHERE attrelid='sdk_hosted_settlement_report_obligations'::regclass AND NOT attisdropped`
|
|
9879
9925
|
);
|
|
9880
|
-
for (const [name, type] of [["close_search_before", "BIGINT"], ["close_write_off_note", "TEXT"]]) {
|
|
9926
|
+
for (const [name, type] of [["close_search_before", "BIGINT"], ["close_write_off_note", "TEXT"], ["close_written_off_at_ms", "BIGINT"]]) {
|
|
9881
9927
|
if (!columns.rows.some((column) => column.attname === name)) {
|
|
9882
9928
|
await this.pool.query(`ALTER TABLE sdk_hosted_settlement_report_obligations ADD COLUMN ${name} ${type}`);
|
|
9883
9929
|
}
|
|
@@ -10076,41 +10122,104 @@ var HostedSettlementReportObligations = class {
|
|
|
10076
10122
|
);
|
|
10077
10123
|
return result.rowCount === 1;
|
|
10078
10124
|
}
|
|
10125
|
+
async listTempoCloseReports(args) {
|
|
10126
|
+
const result = await this.pool.query(
|
|
10127
|
+
`WITH candidates AS (
|
|
10128
|
+
SELECT target.*, (${TEMPO_CLOSE_RETRYABLE_SQL}) AS retryable,
|
|
10129
|
+
(${TEMPO_CLOSE_WRITE_OFF_ALLOWED_SQL}) AS write_off_allowed
|
|
10130
|
+
FROM sdk_hosted_settlement_report_obligations target
|
|
10131
|
+
WHERE target.dvm_id=$1 AND target.scope=$2 AND target.rail='tempo' AND target.operation='close'
|
|
10132
|
+
AND target.state IN ('unmatched','no_report')
|
|
10133
|
+
)
|
|
10134
|
+
SELECT * FROM candidates
|
|
10135
|
+
WHERE ($6::boolean OR (state='unmatched' AND (retryable OR write_off_allowed)))
|
|
10136
|
+
AND ($4::bigint IS NULL OR (created_at_ms,id)>($4,$5))
|
|
10137
|
+
ORDER BY created_at_ms,id LIMIT $3`,
|
|
10138
|
+
[
|
|
10139
|
+
this.dvmId,
|
|
10140
|
+
this.scope,
|
|
10141
|
+
args.limit,
|
|
10142
|
+
args.after?.createdAtMs ?? null,
|
|
10143
|
+
args.after?.obligationId ?? null,
|
|
10144
|
+
args.all ?? false
|
|
10145
|
+
]
|
|
10146
|
+
);
|
|
10147
|
+
return result.rows.map((row) => ({
|
|
10148
|
+
id: row.id,
|
|
10149
|
+
channelId: row.channel_id,
|
|
10150
|
+
amountNative: tempoCloseAmount(row.original),
|
|
10151
|
+
status: row.state,
|
|
10152
|
+
createdAtMs: Number(row.created_at_ms),
|
|
10153
|
+
updatedAtMs: Number(row.updated_at_ms),
|
|
10154
|
+
deliveredAtMs: row.delivered_at_ms === null ? null : Number(row.delivered_at_ms),
|
|
10155
|
+
retryable: row.retryable,
|
|
10156
|
+
writeOffAllowed: row.write_off_allowed,
|
|
10157
|
+
writtenOff: row.close_write_off_note !== null,
|
|
10158
|
+
writeOffNote: row.close_write_off_note,
|
|
10159
|
+
writtenOffAtMs: row.close_written_off_at_ms === null ? null : Number(row.close_written_off_at_ms)
|
|
10160
|
+
}));
|
|
10161
|
+
}
|
|
10162
|
+
async hasTempoCloseReport(id) {
|
|
10163
|
+
const result = await this.pool.query(
|
|
10164
|
+
`SELECT id FROM sdk_hosted_settlement_report_obligations
|
|
10165
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND rail='tempo' AND operation='close'`,
|
|
10166
|
+
[id, this.dvmId, this.scope]
|
|
10167
|
+
);
|
|
10168
|
+
return result.rowCount === 1;
|
|
10169
|
+
}
|
|
10079
10170
|
async writeOffTempoClose(id, note) {
|
|
10080
10171
|
const result = await this.pool.query(
|
|
10081
|
-
`UPDATE sdk_hosted_settlement_report_obligations
|
|
10172
|
+
`UPDATE sdk_hosted_settlement_report_obligations target
|
|
10173
|
+
SET state='no_report', close_write_off_note=$4,
|
|
10174
|
+
close_written_off_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint,
|
|
10082
10175
|
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
|
|
10083
|
-
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND
|
|
10084
|
-
|
|
10176
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND ${TEMPO_CLOSE_WRITE_OFF_ALLOWED_SQL}
|
|
10177
|
+
RETURNING channel_id,original`,
|
|
10085
10178
|
[id, this.dvmId, this.scope, note]
|
|
10086
10179
|
);
|
|
10087
10180
|
const row = result.rows[0];
|
|
10088
|
-
|
|
10181
|
+
if (!row) return void 0;
|
|
10182
|
+
const written = { channelId: row.channel_id, amount: tempoCloseAmount(row.original) };
|
|
10183
|
+
loggers.tempo.warn("tempo_session_close_report_written_off", {
|
|
10184
|
+
channel_id: written.channelId,
|
|
10185
|
+
obligation_id: id,
|
|
10186
|
+
amount_forgone_native: written.amount
|
|
10187
|
+
});
|
|
10188
|
+
return written;
|
|
10089
10189
|
}
|
|
10090
10190
|
async reopenTempoClose(id) {
|
|
10091
|
-
|
|
10191
|
+
const reopened = await transaction(this.pool, async (tx) => {
|
|
10092
10192
|
const owned = await tx.query(
|
|
10093
10193
|
"SELECT id FROM sdk_hosted_settlement_report_obligations WHERE id=$1 AND dvm_id=$2 AND scope=$3",
|
|
10094
10194
|
[id, this.dvmId, this.scope]
|
|
10095
10195
|
);
|
|
10096
|
-
if (!owned.rowCount) return
|
|
10196
|
+
if (!owned.rowCount) return void 0;
|
|
10097
10197
|
await this.lockIdentity(tx);
|
|
10198
|
+
const prior = await tx.query(
|
|
10199
|
+
`SELECT channel_id,state,close_write_off_note,original FROM sdk_hosted_settlement_report_obligations
|
|
10200
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 FOR UPDATE`,
|
|
10201
|
+
[id, this.dvmId, this.scope]
|
|
10202
|
+
);
|
|
10203
|
+
const row = prior.rows[0];
|
|
10204
|
+
if (!row) return void 0;
|
|
10098
10205
|
const result = await tx.query(
|
|
10099
10206
|
`UPDATE sdk_hosted_settlement_report_obligations target
|
|
10100
10207
|
SET state='reserved', close_search_before=NULL,
|
|
10101
10208
|
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
|
|
10102
|
-
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND
|
|
10103
|
-
AND state IN ('unmatched','no_report') AND tx_hash IS NULL
|
|
10104
|
-
AND NOT EXISTS (
|
|
10105
|
-
SELECT 1 FROM sdk_hosted_settlement_report_obligations other
|
|
10106
|
-
WHERE other.dvm_id=target.dvm_id AND other.scope=target.scope
|
|
10107
|
-
AND other.rail='tempo' AND other.operation='close' AND other.channel_id=target.channel_id
|
|
10108
|
-
AND other.id<>target.id AND (other.state<>'no_report' OR other.tx_hash IS NOT NULL)
|
|
10109
|
-
)`,
|
|
10209
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND ${TEMPO_CLOSE_RETRYABLE_SQL}`,
|
|
10110
10210
|
[id, this.dvmId, this.scope]
|
|
10111
10211
|
);
|
|
10112
|
-
return result.rowCount === 1;
|
|
10212
|
+
return result.rowCount === 1 ? row : void 0;
|
|
10213
|
+
});
|
|
10214
|
+
if (!reopened) return false;
|
|
10215
|
+
loggers.tempo.info("tempo_session_close_report_reopened", {
|
|
10216
|
+
obligation_id: id,
|
|
10217
|
+
channel_id: reopened.channel_id,
|
|
10218
|
+
prior_status: reopened.state,
|
|
10219
|
+
written_off: reopened.close_write_off_note !== null,
|
|
10220
|
+
amount_native: tempoCloseAmount(reopened.original)
|
|
10113
10221
|
});
|
|
10222
|
+
return true;
|
|
10114
10223
|
}
|
|
10115
10224
|
async reserveTempoIn(tx, channelId, operation, drainId) {
|
|
10116
10225
|
const config = this.tempo;
|
|
@@ -10185,6 +10294,17 @@ var HostedSettlementReportObligations = class {
|
|
|
10185
10294
|
searchBefore: searchCursor(row.close_search_before)
|
|
10186
10295
|
};
|
|
10187
10296
|
}
|
|
10297
|
+
/** A recorded close transaction remains refund evidence after its report is delivered. */
|
|
10298
|
+
async tempoCloseTransaction(channelId, drainId) {
|
|
10299
|
+
const result = await this.pool.query(
|
|
10300
|
+
`SELECT tx_hash FROM sdk_hosted_settlement_report_obligations
|
|
10301
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10302
|
+
AND original->>'drainId'=$4 AND tx_hash IS NOT NULL
|
|
10303
|
+
ORDER BY created_at_ms DESC LIMIT 1`,
|
|
10304
|
+
[this.dvmId, this.scope, channelId.toLowerCase(), drainId]
|
|
10305
|
+
);
|
|
10306
|
+
return result.rows[0]?.tx_hash;
|
|
10307
|
+
}
|
|
10188
10308
|
/**
|
|
10189
10309
|
* Where the next search for this close obligation's transaction resumes. It
|
|
10190
10310
|
* lives on the obligation, not the drain's close intent, because releasing
|
|
@@ -10836,12 +10956,10 @@ async function writeOffHostedTempoCloseReport(args) {
|
|
|
10836
10956
|
if (!note || note.length > 2e3) throw new Error("A write-off requires a note of 1 to 2000 characters");
|
|
10837
10957
|
const row = await new HostedSettlementReportObligations(args.db, args.dvmId, args.scope).writeOffTempoClose(args.obligationId, note);
|
|
10838
10958
|
if (!row) return false;
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
});
|
|
10844
|
-
return true;
|
|
10959
|
+
return args.includeDetails ? { channelId: row.channelId, amountForgoneNative: row.amount } : true;
|
|
10960
|
+
}
|
|
10961
|
+
function tempoCloseAmount(original) {
|
|
10962
|
+
return (BigInt(String(original.highestVoucherAmount)) - BigInt(String(original.settledOnChain))).toString();
|
|
10845
10963
|
}
|
|
10846
10964
|
function searchCursor(value) {
|
|
10847
10965
|
return value === null ? null : BigInt(value);
|
|
@@ -12174,7 +12292,7 @@ function jobCredentialGate(opts) {
|
|
|
12174
12292
|
// to a human verbatim, so the conditional advice lives in `hint`, which
|
|
12175
12293
|
// is what the agent acts on.
|
|
12176
12294
|
display: "Couldn't retrieve your result \u2014 the request was missing its job credential.",
|
|
12177
|
-
hint: `Send the job_token from the POST /v1/job response as the X-Job-Token header${alternative}. The token is minted once, at submit, and the DVM cannot reissue it \u2014 a job submitted from another machine, or by a caller that never stored it, can't be read here. If your caller never sends X-Job-Token at all, upgrade: npm i -g @dvmkit/dvm-cli@
|
|
12295
|
+
hint: `Send the job_token from the POST /v1/job response as the X-Job-Token header${alternative}. The token is minted once, at submit, and the DVM cannot reissue it \u2014 a job submitted from another machine, or by a caller that never stored it, can't be read here. If your caller never sends X-Job-Token at all, upgrade: npm i -g @dvmkit/dvm-cli@latest, then retry the same command with --endpoint <url> --job-token <hex> (or set DVM_JOB_ENDPOINT and DVM_JOB_TOKEN).`
|
|
12178
12296
|
};
|
|
12179
12297
|
}
|
|
12180
12298
|
};
|
|
@@ -17033,6 +17151,9 @@ function installAdminCreditRoutes(app, opts) {
|
|
|
17033
17151
|
"/admin/credit/reconcile-x402-exact-settlement",
|
|
17034
17152
|
(c) => handleReconcileX402Exact(c, opts)
|
|
17035
17153
|
);
|
|
17154
|
+
app.get("/admin/credit/tempo-close-reports", (c) => handleTempoCloseReports(c, opts));
|
|
17155
|
+
app.post("/admin/credit/retry-tempo-close-report", (c) => handleTempoCloseReportAction(c, opts, "retry"));
|
|
17156
|
+
app.post("/admin/credit/write-off-tempo-close-report", (c) => handleTempoCloseReportAction(c, opts, "write-off"));
|
|
17036
17157
|
app.get("/admin/credit/tempo-drains", (c) => handleTempoDrains(c, opts));
|
|
17037
17158
|
app.get("/admin/credit/tempo-losses", (c) => handleTempoLosses(c, opts));
|
|
17038
17159
|
app.post("/admin/credit/reconcile-tempo-drain", (c) => handleReconcileTempo(c, opts));
|
|
@@ -18008,6 +18129,101 @@ async function handleWriteOffX402(c, opts) {
|
|
|
18008
18129
|
replayed: !outcome.written
|
|
18009
18130
|
});
|
|
18010
18131
|
}
|
|
18132
|
+
function tempoCloseReportsUnavailable(c) {
|
|
18133
|
+
return c.json({
|
|
18134
|
+
error: "tempo_close_report_unavailable",
|
|
18135
|
+
message: "Hosted settlement reports are not configured on this DVM."
|
|
18136
|
+
}, 503);
|
|
18137
|
+
}
|
|
18138
|
+
async function handleTempoCloseReports(c, opts) {
|
|
18139
|
+
const auth = await requireAuth(c, opts);
|
|
18140
|
+
if ("response" in auth) return auth.response;
|
|
18141
|
+
if (!opts.hostedSettlementReports) return tempoCloseReportsUnavailable(c);
|
|
18142
|
+
const limit = parseTempoLimit(c.req.query("limit"));
|
|
18143
|
+
if (limit === void 0) {
|
|
18144
|
+
return c.json({ error: "invalid_query", message: "limit must be an integer in 1..50." }, 400);
|
|
18145
|
+
}
|
|
18146
|
+
let after;
|
|
18147
|
+
const rawAfter = c.req.query("after");
|
|
18148
|
+
if (rawAfter !== void 0) {
|
|
18149
|
+
try {
|
|
18150
|
+
const value = JSON.parse(Buffer.from(rawAfter, "base64url").toString("utf8"));
|
|
18151
|
+
if (!Array.isArray(value) || value.length !== 2 || !Number.isSafeInteger(value[0]) || value[0] < 0 || typeof value[1] !== "string" || !value[1]) throw new Error("invalid cursor");
|
|
18152
|
+
after = { createdAtMs: value[0], obligationId: value[1] };
|
|
18153
|
+
} catch {
|
|
18154
|
+
return c.json({ error: "invalid_query", message: "after must be a next_after value from a previous response." }, 400);
|
|
18155
|
+
}
|
|
18156
|
+
}
|
|
18157
|
+
const all = c.req.query("all");
|
|
18158
|
+
if (all !== void 0 && all !== "0" && all !== "1") {
|
|
18159
|
+
return c.json({ error: "invalid_query", message: "all must be 0 or 1." }, 400);
|
|
18160
|
+
}
|
|
18161
|
+
const rows2 = await opts.hostedSettlementReports.store.listTempoCloseReports({ limit, after, all: all === "1" });
|
|
18162
|
+
const last = rows2.length === limit ? rows2.at(-1) : void 0;
|
|
18163
|
+
const response = {
|
|
18164
|
+
reports: rows2.map((row) => ({
|
|
18165
|
+
obligation_id: row.id,
|
|
18166
|
+
channel_id: row.channelId,
|
|
18167
|
+
amount_native: row.amountNative,
|
|
18168
|
+
status: row.status,
|
|
18169
|
+
created_at_ms: row.createdAtMs,
|
|
18170
|
+
updated_at_ms: row.updatedAtMs,
|
|
18171
|
+
delivered_at_ms: row.deliveredAtMs,
|
|
18172
|
+
retryable: row.retryable,
|
|
18173
|
+
write_off_allowed: row.writeOffAllowed,
|
|
18174
|
+
written_off: row.writtenOff,
|
|
18175
|
+
write_off_note: row.writeOffNote,
|
|
18176
|
+
written_off_at_ms: row.writtenOffAtMs
|
|
18177
|
+
})),
|
|
18178
|
+
...last ? { next_after: Buffer.from(JSON.stringify([last.createdAtMs, last.id])).toString("base64url") } : {}
|
|
18179
|
+
};
|
|
18180
|
+
return c.json(response);
|
|
18181
|
+
}
|
|
18182
|
+
async function handleTempoCloseReportAction(c, opts, action) {
|
|
18183
|
+
const auth = await requireAuth(c, opts);
|
|
18184
|
+
if ("response" in auth) return auth.response;
|
|
18185
|
+
const reports = opts.hostedSettlementReports;
|
|
18186
|
+
if (!reports) return tempoCloseReportsUnavailable(c);
|
|
18187
|
+
const body = await c.req.json().catch(() => null);
|
|
18188
|
+
if (!body || typeof body.obligation_id !== "string" || !body.obligation_id.trim()) {
|
|
18189
|
+
return c.json({ error: "invalid_request", message: "obligation_id must be a non-empty string." }, 400);
|
|
18190
|
+
}
|
|
18191
|
+
const obligationId = body.obligation_id.trim();
|
|
18192
|
+
if (action === "retry") {
|
|
18193
|
+
if (await reports.store.reopenTempoClose(obligationId)) {
|
|
18194
|
+
const response = { status: "reserved", obligation_id: obligationId };
|
|
18195
|
+
return c.json(response);
|
|
18196
|
+
}
|
|
18197
|
+
} else {
|
|
18198
|
+
if (typeof body.note !== "string" || !body.note.trim() || body.note.trim().length > 2e3) {
|
|
18199
|
+
return c.json({ error: "invalid_request", message: "note must contain 1 to 2000 characters after trimming." }, 400);
|
|
18200
|
+
}
|
|
18201
|
+
const note = body.note.trim();
|
|
18202
|
+
const result = await reports.store.writeOffTempoClose(obligationId, note);
|
|
18203
|
+
if (result) {
|
|
18204
|
+
const response = {
|
|
18205
|
+
status: "written_off",
|
|
18206
|
+
obligation_id: obligationId,
|
|
18207
|
+
channel_id: result.channelId,
|
|
18208
|
+
amount_forgone_native: result.amount,
|
|
18209
|
+
note
|
|
18210
|
+
};
|
|
18211
|
+
return c.json(response);
|
|
18212
|
+
}
|
|
18213
|
+
}
|
|
18214
|
+
if (!await reports.store.hasTempoCloseReport(obligationId)) {
|
|
18215
|
+
return c.json({
|
|
18216
|
+
error: "tempo_close_report_not_found",
|
|
18217
|
+
message: "Close-report obligation not found.",
|
|
18218
|
+
obligation_id: obligationId
|
|
18219
|
+
}, 404);
|
|
18220
|
+
}
|
|
18221
|
+
return c.json({
|
|
18222
|
+
error: "tempo_close_report_conflict",
|
|
18223
|
+
message: action === "retry" ? "Retry requires an unsubmitted unmatched or no_report close obligation without a competing report." : "Write-off requires an unsubmitted unmatched close obligation.",
|
|
18224
|
+
obligation_id: obligationId
|
|
18225
|
+
}, 409);
|
|
18226
|
+
}
|
|
18011
18227
|
async function handleTempoDrains(c, opts) {
|
|
18012
18228
|
const auth = await requireAuth(c, opts);
|
|
18013
18229
|
if ("response" in auth) return auth.response;
|
|
@@ -19913,7 +20129,7 @@ async function createDVMServer(descriptor, opts) {
|
|
|
19913
20129
|
}
|
|
19914
20130
|
}
|
|
19915
20131
|
if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
|
|
19916
|
-
const { RevenueReporter } = await import("./revenue-reporter-
|
|
20132
|
+
const { RevenueReporter } = await import("./revenue-reporter-EV3K5L6B.js");
|
|
19917
20133
|
const reporter = new RevenueReporter(
|
|
19918
20134
|
opts.db,
|
|
19919
20135
|
platformUrl,
|
|
@@ -19923,7 +20139,7 @@ async function createDVMServer(descriptor, opts) {
|
|
|
19923
20139
|
revenueReporter = reporter;
|
|
19924
20140
|
await reporter.init();
|
|
19925
20141
|
reporter.startRetryLoop();
|
|
19926
|
-
const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-
|
|
20142
|
+
const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-HVJ6WUF3.js");
|
|
19927
20143
|
payoutReporter = new PayoutReporter({
|
|
19928
20144
|
dvmId,
|
|
19929
20145
|
db: opts.db,
|
|
@@ -20590,6 +20806,9 @@ var DVMServer = class {
|
|
|
20590
20806
|
// DVM wired with an in-memory session store resolves to a rail with no
|
|
20591
20807
|
// close-intent read and the verbs answer 503.
|
|
20592
20808
|
tempoDrainRail: () => Promise.resolve(this.opts.mpp),
|
|
20809
|
+
...this.opts.hostedSettlementReports ? {
|
|
20810
|
+
hostedSettlementReports: { store: this.opts.hostedSettlementReports }
|
|
20811
|
+
} : {},
|
|
20593
20812
|
// Health-filtered, and read per request rather than captured: the
|
|
20594
20813
|
// servicer picking a mint to buy a refund at wants the list that
|
|
20595
20814
|
// excludes a mint currently failing its probe.
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-COYKJRMM.js";
|
|
4
4
|
import {
|
|
5
5
|
applyRetryToPool
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-MMGSYMIC.js";
|
|
7
7
|
import {
|
|
8
8
|
x402NetworkByCaip2
|
|
9
9
|
} from "./chunk-BPYMEMQX.js";
|
|
10
10
|
import {
|
|
11
11
|
diagnosticConsole
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QGNXYVBO.js";
|
|
13
13
|
|
|
14
14
|
// src/sdk/server/credit-ledger.ts
|
|
15
15
|
import { randomUUID } from "crypto";
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
isDrainMethod,
|
|
11
11
|
isFundingRail,
|
|
12
12
|
x402SettlementPending
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-STJBBVNL.js";
|
|
14
14
|
import "./chunk-COYKJRMM.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-MMGSYMIC.js";
|
|
16
16
|
import "./chunk-BPYMEMQX.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-QGNXYVBO.js";
|
|
18
18
|
export {
|
|
19
19
|
CreditLedger,
|
|
20
20
|
CreditLedgerError,
|