@dvmkit/sdk 0.8.0 → 0.8.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.
- package/dist/{chunk-5CQKSLAU.js → chunk-2U6LAASN.js} +1 -1
- package/dist/{chunk-6FEHRZO7.js → chunk-7PM4R4GD.js} +1 -1
- package/dist/{chunk-EBCLWOKF.js → chunk-AGE5XZKE.js} +1 -1
- package/dist/{chunk-2I3OHMTY.js → chunk-ALWVJ2RB.js} +1 -1
- package/dist/{chunk-ATXDKX6F.js → chunk-AVV5SYGO.js} +1 -1
- package/dist/{chunk-DZQYQN2X.js → chunk-BFGQG4WY.js} +7 -0
- package/dist/{chunk-VYP6ADCS.js → chunk-FM6QAGJC.js} +2 -2
- package/dist/{chunk-QPXI7YPH.js → chunk-G3MPHW5I.js} +1 -1
- package/dist/{chunk-I6PHA54S.js → chunk-IHQUBATH.js} +1 -1
- package/dist/{chunk-5K5E6ME6.js → chunk-ILBSZMJL.js} +1 -1
- package/dist/{chunk-BL6LRWXD.js → chunk-IOQIXRGM.js} +1 -1
- package/dist/{chunk-X3JEME6P.js → chunk-J5J63WDC.js} +1 -1
- package/dist/{chunk-GSPV3UIM.js → chunk-JIZ4ZIXX.js} +254 -72
- package/dist/{chunk-TFYY5APM.js → chunk-NA2KOR6G.js} +1 -1
- package/dist/{chunk-PRSKC7AD.js → chunk-NU5RWJ5B.js} +1 -1
- package/dist/{chunk-IFWUWHTW.js → chunk-OP7VCGQR.js} +1 -1
- package/dist/{chunk-GB5XPAA5.js → chunk-QKE5NDPF.js} +213 -38
- package/dist/{chunk-55NKZQOC.js → chunk-RSNC5T6C.js} +1 -1
- package/dist/{chunk-XXLUIXPO.js → chunk-UDJNZ3UG.js} +1 -1
- package/dist/{chunk-YPSJU3OH.js → chunk-UVS7MRJX.js} +2 -2
- package/dist/{chunk-P5WEZXJ2.js → chunk-UWGBI5WK.js} +58 -1
- package/dist/{chunk-RTFBVXCN.js → chunk-ZEPJF4JC.js} +1 -1
- package/dist/{content-retention-D2K4MKNO.d.ts → content-retention-B7ddtJ2y.d.ts} +1 -1
- package/dist/{credit-ledger-AOU6S34N.js → credit-ledger-KIQIE6I6.js} +3 -3
- package/dist/{credit-menu-C-8g2cHT.d.ts → credit-menu-BH3g9GQa.d.ts} +4 -4
- package/dist/{fx-Ql4nvdbh.d.ts → fx-DN26_J7o.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/internal/caller.d.ts +6 -6
- 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-DCvJQUX0.d.ts → job-store-Cq21JT37.d.ts} +1 -1
- package/dist/{lightning-backend-CY5ML3km.d.ts → lightning-backend-S8F8nf8q.d.ts} +1 -1
- package/dist/{memory-credit-ledger-4HWYINKJ.js → memory-credit-ledger-ATYFAYBG.js} +4 -4
- package/dist/{mpp-setup-2BKTPEK6.js → mpp-setup-ZTORW3VF.js} +3 -3
- package/dist/{payout-reporter-HQSIIBFL.js → payout-reporter-NN6K2IVQ.js} +2 -2
- package/dist/{postgres-consumed-credential-store-R7DNU6CM.js → postgres-consumed-credential-store-XJTE5PCA.js} +1 -1
- package/dist/{postgres-job-store-6VGFL6XE.js → postgres-job-store-AR6ZJUQV.js} +2 -2
- package/dist/{postgres-replay-store-SY26NUCI.js → postgres-replay-store-33K33QV7.js} +2 -2
- package/dist/{pricing-H2RA4X2R.js → pricing-G5IN5YJE.js} +3 -3
- package/dist/{revenue-reporter-X52GPZ5U.js → revenue-reporter-KDUZLD5F.js} +2 -2
- package/dist/server/index.d.ts +10 -10
- package/dist/server/index.js +27 -27
- package/dist/{step-cache-BOWAn_BH.d.ts → step-cache-CR12EZ1Q.d.ts} +108 -4
- package/dist/{tempo-charge-store-NKXJ2AXE.js → tempo-charge-store-XEGJYM3G.js} +2 -2
- package/dist/{tempo-session-store-ZSDQIWSU.js → tempo-session-store-3WTDOO7H.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +6 -6
- package/dist/{usd-MAtMp3DB.d.ts → usd-s_VZ_eS7.d.ts} +1 -1
- package/dist/{x402-Q4S36NHQ.js → x402-HQ5GRQZU.js} +3 -3
- package/package.json +1 -1
|
@@ -416,11 +416,18 @@ var diagnostic_catalogue_v1_default = {
|
|
|
416
416
|
"tempo_routine_sweep",
|
|
417
417
|
"tempo_routine_sweep_failed",
|
|
418
418
|
"tempo_session_advertised_refresh_failed",
|
|
419
|
+
"tempo_session_close_attempts_exhausted",
|
|
419
420
|
"tempo_session_close_challenge_failed",
|
|
421
|
+
"tempo_session_close_exit_resolved",
|
|
420
422
|
"tempo_session_close_failed",
|
|
421
423
|
"tempo_session_close_failure_reconcile_failed",
|
|
424
|
+
"tempo_session_close_reconcile_failed",
|
|
422
425
|
"tempo_session_close_recovery_failed",
|
|
426
|
+
"tempo_session_close_recovery_search_paused",
|
|
423
427
|
"tempo_session_close_recovery_unmatched",
|
|
428
|
+
"tempo_session_close_reservation_released",
|
|
429
|
+
"tempo_session_close_settlement_callback_failed",
|
|
430
|
+
"tempo_session_close_settlement_unreported",
|
|
424
431
|
"tempo_session_open_refused",
|
|
425
432
|
"tempo_session_settled",
|
|
426
433
|
"tempo_session_withheld",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyRetryToPool
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7PM4R4GD.js";
|
|
4
4
|
import {
|
|
5
5
|
x402NetworkByCaip2
|
|
6
6
|
} from "./chunk-BPYMEMQX.js";
|
|
7
7
|
import {
|
|
8
8
|
diagnosticConsole
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BFGQG4WY.js";
|
|
10
10
|
import {
|
|
11
11
|
withSdkInitLock
|
|
12
12
|
} from "./chunk-COYKJRMM.js";
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
installHostedTempoCloseGuard,
|
|
8
8
|
isTempoInsufficientFundsError,
|
|
9
9
|
resolveTempoNetwork
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-QKE5NDPF.js";
|
|
11
11
|
import {
|
|
12
12
|
x402EarnedCeiling
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-J5J63WDC.js";
|
|
14
14
|
import {
|
|
15
15
|
createX402FacilitatorAuthHeaders,
|
|
16
16
|
createX402FacilitatorClient,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
verifyWithFacilitator,
|
|
20
20
|
withPaymentDiagnostics,
|
|
21
21
|
withPaymentStage
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-AVV5SYGO.js";
|
|
23
23
|
import {
|
|
24
24
|
MemoryCreditLedger
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-AGE5XZKE.js";
|
|
26
26
|
import {
|
|
27
27
|
PostgresJobStore
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-G3MPHW5I.js";
|
|
29
29
|
import {
|
|
30
30
|
parseStoreValue
|
|
31
31
|
} from "./chunk-YG7G4DPZ.js";
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
humanizeZodIssues,
|
|
37
37
|
signedRequestInput,
|
|
38
38
|
zodIssuesOf
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-ILBSZMJL.js";
|
|
40
40
|
import {
|
|
41
41
|
MemoryJobStore,
|
|
42
42
|
UNCAPPABLE_ASK_TOTAL,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
isJobRetentionStore,
|
|
46
46
|
isReceiptIssuingStore,
|
|
47
47
|
isStreamableJobStore
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-IHQUBATH.js";
|
|
49
49
|
import {
|
|
50
50
|
ContentHoldError,
|
|
51
51
|
JobCancelledError,
|
|
@@ -85,7 +85,7 @@ import {
|
|
|
85
85
|
signReceipt,
|
|
86
86
|
toLockPubkey,
|
|
87
87
|
verifyChallenge
|
|
88
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-ZEPJF4JC.js";
|
|
89
89
|
import {
|
|
90
90
|
X402_BATCH_SETTLEMENT_SCHEME,
|
|
91
91
|
X402_DEFAULT_FACILITATOR,
|
|
@@ -113,13 +113,13 @@ import {
|
|
|
113
113
|
} from "./chunk-UKD3R2KU.js";
|
|
114
114
|
import {
|
|
115
115
|
createConsoleLogger
|
|
116
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-OP7VCGQR.js";
|
|
117
117
|
import {
|
|
118
118
|
loggers,
|
|
119
119
|
pinDimension,
|
|
120
120
|
withSpan,
|
|
121
121
|
withTraceContext
|
|
122
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-IOQIXRGM.js";
|
|
123
123
|
import {
|
|
124
124
|
StepCache
|
|
125
125
|
} from "./chunk-BQJIFRAZ.js";
|
|
@@ -136,7 +136,7 @@ import {
|
|
|
136
136
|
roundUsd,
|
|
137
137
|
satsToFiat,
|
|
138
138
|
validateCurrency
|
|
139
|
-
} from "./chunk-
|
|
139
|
+
} from "./chunk-2U6LAASN.js";
|
|
140
140
|
import {
|
|
141
141
|
CreditLedger,
|
|
142
142
|
CreditLedgerError,
|
|
@@ -152,10 +152,10 @@ import {
|
|
|
152
152
|
isNonChannelBitcoinRail,
|
|
153
153
|
netOwedSats,
|
|
154
154
|
x402SettlementPending
|
|
155
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-FM6QAGJC.js";
|
|
156
156
|
import {
|
|
157
157
|
withPostgresRecoveryContext
|
|
158
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-7PM4R4GD.js";
|
|
159
159
|
import {
|
|
160
160
|
DvmError,
|
|
161
161
|
x402NetworkByCaip2
|
|
@@ -163,7 +163,7 @@ import {
|
|
|
163
163
|
import {
|
|
164
164
|
createDiagnosticCapabilityAuthority,
|
|
165
165
|
diagnosticConsole
|
|
166
|
-
} from "./chunk-
|
|
166
|
+
} from "./chunk-BFGQG4WY.js";
|
|
167
167
|
import {
|
|
168
168
|
MemoryProcessedPaymentStore,
|
|
169
169
|
PostgresProcessedPaymentStore,
|
|
@@ -3883,7 +3883,7 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
3883
3883
|
x402Requirements
|
|
3884
3884
|
});
|
|
3885
3885
|
}
|
|
3886
|
-
const { verifyX402Payment } = await import("./x402-
|
|
3886
|
+
const { verifyX402Payment } = await import("./x402-HQ5GRQZU.js");
|
|
3887
3887
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
3888
3888
|
x402Payment,
|
|
3889
3889
|
x402Config,
|
|
@@ -4896,7 +4896,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
|
|
|
4896
4896
|
snapshot
|
|
4897
4897
|
});
|
|
4898
4898
|
}
|
|
4899
|
-
const { verifyX402Payment } = await import("./x402-
|
|
4899
|
+
const { verifyX402Payment } = await import("./x402-HQ5GRQZU.js");
|
|
4900
4900
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
4901
4901
|
body.content.x402_payment,
|
|
4902
4902
|
opts.x402Config,
|
|
@@ -5327,7 +5327,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
|
|
|
5327
5327
|
try {
|
|
5328
5328
|
const requiredMsats = job.pendingPaymentMsats ?? 0;
|
|
5329
5329
|
const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
|
|
5330
|
-
const { verifyX402Payment } = await import("./x402-
|
|
5330
|
+
const { verifyX402Payment } = await import("./x402-HQ5GRQZU.js");
|
|
5331
5331
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
5332
5332
|
body.content.x402_payment,
|
|
5333
5333
|
opts.x402Config,
|
|
@@ -7487,17 +7487,16 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7487
7487
|
await beginClose(expectedChannelId, record.drainId);
|
|
7488
7488
|
recoveryVersion = await deps.tempoSettlementReadiness?.captureRecoveryVersion();
|
|
7489
7489
|
if (deps.hostedSettlementReports) {
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
record
|
|
7490
|
+
hostedObligationId = await reserveHostedTempoClose(
|
|
7491
|
+
deps.hostedSettlementReports,
|
|
7492
|
+
rail.mpp,
|
|
7493
|
+
record,
|
|
7494
|
+
expectedChannelId
|
|
7494
7495
|
);
|
|
7495
|
-
if (reserved.replayed) throw new Error("Hosted Tempo close already has an original effect attempt");
|
|
7496
|
-
hostedObligationId = reserved.id;
|
|
7497
7496
|
if (!rail.mpp?.permitHostedTempoClose) {
|
|
7498
7497
|
throw new Error("Hosted Tempo close credential guard is unavailable");
|
|
7499
7498
|
}
|
|
7500
|
-
rail.mpp.permitHostedTempoClose(rail.mppCredential,
|
|
7499
|
+
rail.mpp.permitHostedTempoClose(rail.mppCredential, hostedObligationId);
|
|
7501
7500
|
}
|
|
7502
7501
|
}
|
|
7503
7502
|
});
|
|
@@ -7522,6 +7521,14 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7522
7521
|
}
|
|
7523
7522
|
const wrongChannel = tempoWrongChannelRefusal(error, record);
|
|
7524
7523
|
if (wrongChannel) return wrongChannel;
|
|
7524
|
+
if (error instanceof HostedTempoCloseAttemptsExhaustedError) {
|
|
7525
|
+
return jsonPaymentError("tempo_channel_exit_required", 409, {
|
|
7526
|
+
message: `Tempo drain ${record.drainId} reached its limit of cooperative close attempts that never reached the chain.`,
|
|
7527
|
+
display: "The service could not complete a cooperative close of this stablecoin channel after several attempts. Your funds stay in the channel; use the timed on-chain exit to recover them.",
|
|
7528
|
+
hint: "Start the channel's timed on-chain exit. Retrying this drain with another close credential is refused.",
|
|
7529
|
+
extra: { drain_registered: true, drain_id: record.drainId, channel_id: expectedChannelId }
|
|
7530
|
+
});
|
|
7531
|
+
}
|
|
7525
7532
|
const refused = tempoFloorRefusal(error, record);
|
|
7526
7533
|
if (refused) return refused;
|
|
7527
7534
|
if (tempoCloseCredentialNeedsRefresh(error)) {
|
|
@@ -7568,6 +7575,49 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7568
7575
|
});
|
|
7569
7576
|
}
|
|
7570
7577
|
}
|
|
7578
|
+
var HOSTED_TEMPO_CLOSE_STALE_MS = 3e5;
|
|
7579
|
+
var HOSTED_TEMPO_CLOSE_MAX_RELEASES = 3;
|
|
7580
|
+
var HOSTED_TEMPO_CLOSE_IN_FLIGHT = "Hosted Tempo close already has an original effect attempt";
|
|
7581
|
+
var HostedTempoCloseAttemptsExhaustedError = class extends Error {
|
|
7582
|
+
};
|
|
7583
|
+
async function reserveHostedTempoClose(reports, mpp, record, channelId) {
|
|
7584
|
+
const pending = await reports.pendingTempoClose(channelId, record.drainId);
|
|
7585
|
+
if (!pending) {
|
|
7586
|
+
const reserved = await reports.reserveTempo(channelId, "close", record.drainId);
|
|
7587
|
+
if (reserved.replayed) throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7588
|
+
return reserved.id;
|
|
7589
|
+
}
|
|
7590
|
+
if (pending.txHash || !mpp.getTempoSessionDrainState) throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7591
|
+
const state = await mpp.getTempoSessionDrainState(channelId);
|
|
7592
|
+
if (state.finalized || state.deposit === 0n || state.closeRequestedAt !== 0n) {
|
|
7593
|
+
throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7594
|
+
}
|
|
7595
|
+
const result = await reports.releaseStaleTempoClose(pending.id, channelId, record.drainId, {
|
|
7596
|
+
staleAfterMs: HOSTED_TEMPO_CLOSE_STALE_MS,
|
|
7597
|
+
maxReleases: HOSTED_TEMPO_CLOSE_MAX_RELEASES
|
|
7598
|
+
});
|
|
7599
|
+
if (result.outcome === "in_flight") throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7600
|
+
if (result.outcome === "exhausted") {
|
|
7601
|
+
loggers.tempo.warn("tempo_session_close_attempts_exhausted", {
|
|
7602
|
+
credit_id: record.creditId,
|
|
7603
|
+
drain_id: record.drainId,
|
|
7604
|
+
channel_id: channelId,
|
|
7605
|
+
obligation_id: pending.id,
|
|
7606
|
+
attempt: result.releases + 1
|
|
7607
|
+
});
|
|
7608
|
+
throw new HostedTempoCloseAttemptsExhaustedError(
|
|
7609
|
+
`Tempo drain ${record.drainId} exhausted its cooperative close attempts`
|
|
7610
|
+
);
|
|
7611
|
+
}
|
|
7612
|
+
loggers.tempo.warn("tempo_session_close_reservation_released", {
|
|
7613
|
+
credit_id: record.creditId,
|
|
7614
|
+
drain_id: record.drainId,
|
|
7615
|
+
channel_id: channelId,
|
|
7616
|
+
obligation_id: pending.id,
|
|
7617
|
+
attempt: result.releases + 1
|
|
7618
|
+
});
|
|
7619
|
+
return result.id;
|
|
7620
|
+
}
|
|
7571
7621
|
function tempoCloseCredentialNeedsRefresh(error) {
|
|
7572
7622
|
return error instanceof MppxErrors.InvalidChallengeError || error instanceof MppxErrors.PaymentExpiredError;
|
|
7573
7623
|
}
|
|
@@ -9931,6 +9981,8 @@ var HostedSettlementReportObligations = class {
|
|
|
9931
9981
|
created_at_ms BIGINT NOT NULL DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint,
|
|
9932
9982
|
updated_at_ms BIGINT NOT NULL DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
|
|
9933
9983
|
);
|
|
9984
|
+
ALTER TABLE sdk_hosted_settlement_report_obligations
|
|
9985
|
+
ADD COLUMN IF NOT EXISTS close_search_before BIGINT;
|
|
9934
9986
|
CREATE INDEX IF NOT EXISTS sdk_hosted_settlement_report_pending
|
|
9935
9987
|
ON sdk_hosted_settlement_report_obligations (dvm_id,scope,state)
|
|
9936
9988
|
WHERE state NOT IN ('delivered','no_report');
|
|
@@ -10037,53 +10089,109 @@ var HostedSettlementReportObligations = class {
|
|
|
10037
10089
|
});
|
|
10038
10090
|
}
|
|
10039
10091
|
async reserveTempo(channelId, operation, drainId) {
|
|
10040
|
-
|
|
10041
|
-
|
|
10092
|
+
return transaction(this.pool, (tx) => this.reserveTempoIn(tx, channelId, operation, drainId));
|
|
10093
|
+
}
|
|
10094
|
+
/**
|
|
10095
|
+
* Retire a close reservation that never recorded a transaction and reserve
|
|
10096
|
+
* the drain's next attempt, in one transaction, so the drain always keeps one
|
|
10097
|
+
* outstanding close obligation. A close from the retired attempt that lands
|
|
10098
|
+
* late is a close of the same channel: `recordSubmitted` on the retired row
|
|
10099
|
+
* writes it onto the successor, and recovery attaches it there if that
|
|
10100
|
+
* attempt never got to write, so it is reported once.
|
|
10101
|
+
*
|
|
10102
|
+
* The caller has just read the channel open on chain. The age bound only
|
|
10103
|
+
* spares a reservation young enough to be inside its first receipt wait; an
|
|
10104
|
+
* attempt slowed past it by a degraded RPC can still be running.
|
|
10105
|
+
*/
|
|
10106
|
+
async releaseStaleTempoClose(id, channelId, drainId, limits) {
|
|
10042
10107
|
return transaction(this.pool, async (tx) => {
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10108
|
+
await this.lockIdentity(tx);
|
|
10109
|
+
const row = await this.lockRow(tx, id);
|
|
10110
|
+
if (row.rail !== "tempo" || row.operation !== "close" || row.channel_id !== channelId.toLowerCase() || row.original.drainId !== drainId || row.state !== "reserved" || row.tx_hash) {
|
|
10111
|
+
return { outcome: "in_flight" };
|
|
10112
|
+
}
|
|
10113
|
+
const age = await tx.query(
|
|
10114
|
+
`SELECT updated_at_ms <= FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint - $2 AS stale
|
|
10115
|
+
FROM sdk_hosted_settlement_report_obligations WHERE id=$1`,
|
|
10116
|
+
[id, limits.staleAfterMs]
|
|
10046
10117
|
);
|
|
10047
|
-
if (!
|
|
10048
|
-
const
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
}
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
[channelId.toLowerCase()]
|
|
10060
|
-
);
|
|
10061
|
-
if (close.rows[0]?.drain_id !== drainId) throw new Error("Hosted Tempo close has no matching drain");
|
|
10062
|
-
}
|
|
10063
|
-
const highestVoucherAmount = nativeAmount(row.highestVoucherAmount);
|
|
10064
|
-
const settledOnChain = nativeAmount(row.settledOnChain);
|
|
10065
|
-
const spent = nativeAmount(row.spent);
|
|
10066
|
-
if (BigInt(settledOnChain) > BigInt(highestVoucherAmount) || BigInt(spent) > BigInt(highestVoucherAmount)) {
|
|
10067
|
-
throw new Error("Hosted Tempo settlement source has inconsistent amounts");
|
|
10068
|
-
}
|
|
10069
|
-
const original = operation === "settle" ? {
|
|
10070
|
-
highestVoucherAmount,
|
|
10071
|
-
settledOnChain,
|
|
10072
|
-
spent
|
|
10073
|
-
} : { drainId, highestVoucherAmount, settledOnChain, spent };
|
|
10074
|
-
return this.insert(
|
|
10075
|
-
tx,
|
|
10076
|
-
identity,
|
|
10077
|
-
"tempo",
|
|
10078
|
-
operation,
|
|
10079
|
-
config.network,
|
|
10080
|
-
config.escrow,
|
|
10081
|
-
channelId.toLowerCase(),
|
|
10082
|
-
original,
|
|
10083
|
-
operation === "close" ? [drainId] : [highestVoucherAmount]
|
|
10118
|
+
if (!age.rows[0]?.stale) return { outcome: "in_flight" };
|
|
10119
|
+
const released = Number((await tx.query(
|
|
10120
|
+
`SELECT COUNT(*)::text AS n FROM sdk_hosted_settlement_report_obligations
|
|
10121
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10122
|
+
AND original->>'drainId'=$4 AND state='no_report' AND tx_hash IS NULL`,
|
|
10123
|
+
[this.dvmId, this.scope, channelId.toLowerCase(), drainId]
|
|
10124
|
+
)).rows[0].n);
|
|
10125
|
+
if (released >= limits.maxReleases) return { outcome: "exhausted", releases: released };
|
|
10126
|
+
await tx.query(
|
|
10127
|
+
`UPDATE sdk_hosted_settlement_report_obligations SET state='no_report',
|
|
10128
|
+
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint WHERE id=$1`,
|
|
10129
|
+
[id]
|
|
10084
10130
|
);
|
|
10131
|
+
const next = await this.reserveTempoIn(tx, channelId, "close", drainId);
|
|
10132
|
+
return { outcome: "reserved", id: next.id, releases: released + 1 };
|
|
10085
10133
|
});
|
|
10086
10134
|
}
|
|
10135
|
+
/**
|
|
10136
|
+
* Resolve an unsubmitted close reservation whose channel the payer exited
|
|
10137
|
+
* on its own: no cooperative close of ours can land on a finalized channel.
|
|
10138
|
+
*/
|
|
10139
|
+
async resolveTempoCloseExit(id) {
|
|
10140
|
+
const result = await this.pool.query(
|
|
10141
|
+
`UPDATE sdk_hosted_settlement_report_obligations SET state='no_report',
|
|
10142
|
+
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
|
|
10143
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND rail='tempo' AND operation='close'
|
|
10144
|
+
AND state='reserved' AND tx_hash IS NULL`,
|
|
10145
|
+
[id, this.dvmId, this.scope]
|
|
10146
|
+
);
|
|
10147
|
+
return result.rowCount === 1;
|
|
10148
|
+
}
|
|
10149
|
+
async reserveTempoIn(tx, channelId, operation, drainId) {
|
|
10150
|
+
const config = this.tempo;
|
|
10151
|
+
if (!config || !hex(channelId, 32)) throw new Error("Hosted Tempo settlement configuration is unavailable");
|
|
10152
|
+
const state = await tx.query(
|
|
10153
|
+
"SELECT state_json FROM mpp_tempo_channels WHERE channel_key=$1",
|
|
10154
|
+
[channelId.toLowerCase()]
|
|
10155
|
+
);
|
|
10156
|
+
if (!state.rows[0]) throw new Error("Hosted Tempo channel is not durable");
|
|
10157
|
+
const identity = await this.lockIdentity(tx);
|
|
10158
|
+
const parsed = parseStoreValue(state.rows[0].state_json);
|
|
10159
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Hosted Tempo state is invalid");
|
|
10160
|
+
const row = parsed;
|
|
10161
|
+
if (row.chainId !== Number(config.network.split(":")[1]) || String(row.escrowContract).toLowerCase() !== config.escrow.toLowerCase() || String(row.payee).toLowerCase() !== config.payee.toLowerCase() || String(row.token).toLowerCase() !== config.token.toLowerCase()) {
|
|
10162
|
+
throw new Error("Hosted Tempo channel has another chain or recipient");
|
|
10163
|
+
}
|
|
10164
|
+
if (operation === "close") {
|
|
10165
|
+
if (!drainId) throw new Error("Hosted Tempo close requires the original drain");
|
|
10166
|
+
const close = await tx.query(
|
|
10167
|
+
"SELECT drain_id FROM mpp_tempo_close_intents WHERE channel_id=$1",
|
|
10168
|
+
[channelId.toLowerCase()]
|
|
10169
|
+
);
|
|
10170
|
+
if (close.rows[0]?.drain_id !== drainId) throw new Error("Hosted Tempo close has no matching drain");
|
|
10171
|
+
}
|
|
10172
|
+
const highestVoucherAmount = nativeAmount(row.highestVoucherAmount);
|
|
10173
|
+
const settledOnChain = nativeAmount(row.settledOnChain);
|
|
10174
|
+
const spent = nativeAmount(row.spent);
|
|
10175
|
+
if (BigInt(settledOnChain) > BigInt(highestVoucherAmount) || BigInt(spent) > BigInt(highestVoucherAmount)) {
|
|
10176
|
+
throw new Error("Hosted Tempo settlement source has inconsistent amounts");
|
|
10177
|
+
}
|
|
10178
|
+
const original = operation === "settle" ? {
|
|
10179
|
+
highestVoucherAmount,
|
|
10180
|
+
settledOnChain,
|
|
10181
|
+
spent
|
|
10182
|
+
} : { drainId, highestVoucherAmount, settledOnChain, spent };
|
|
10183
|
+
return this.insert(
|
|
10184
|
+
tx,
|
|
10185
|
+
identity,
|
|
10186
|
+
"tempo",
|
|
10187
|
+
operation,
|
|
10188
|
+
config.network,
|
|
10189
|
+
config.escrow,
|
|
10190
|
+
channelId.toLowerCase(),
|
|
10191
|
+
original,
|
|
10192
|
+
operation === "close" ? [drainId] : [highestVoucherAmount]
|
|
10193
|
+
);
|
|
10194
|
+
}
|
|
10087
10195
|
async findPendingTempo(channelId, operation) {
|
|
10088
10196
|
const result = await this.pool.query(
|
|
10089
10197
|
`SELECT * FROM sdk_hosted_settlement_report_obligations
|
|
@@ -10096,14 +10204,32 @@ var HostedSettlementReportObligations = class {
|
|
|
10096
10204
|
/** The original close obligation reserved for this drain, while its report is outstanding. */
|
|
10097
10205
|
async pendingTempoClose(channelId, drainId) {
|
|
10098
10206
|
const result = await this.pool.query(
|
|
10099
|
-
`SELECT id,tx_hash,created_at_ms FROM sdk_hosted_settlement_report_obligations
|
|
10207
|
+
`SELECT id,tx_hash,created_at_ms,close_search_before FROM sdk_hosted_settlement_report_obligations
|
|
10100
10208
|
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10101
10209
|
AND original->>'drainId'=$4 AND state NOT IN ('delivered','no_report')
|
|
10102
10210
|
ORDER BY created_at_ms DESC LIMIT 1`,
|
|
10103
10211
|
[this.dvmId, this.scope, channelId.toLowerCase(), drainId]
|
|
10104
10212
|
);
|
|
10105
10213
|
const row = result.rows[0];
|
|
10106
|
-
return row && {
|
|
10214
|
+
return row && {
|
|
10215
|
+
id: row.id,
|
|
10216
|
+
txHash: row.tx_hash,
|
|
10217
|
+
reservedAtMs: Number(row.created_at_ms),
|
|
10218
|
+
searchBefore: searchCursor(row.close_search_before)
|
|
10219
|
+
};
|
|
10220
|
+
}
|
|
10221
|
+
/**
|
|
10222
|
+
* Where the next search for this close obligation's transaction resumes. It
|
|
10223
|
+
* lives on the obligation, not the drain's close intent, because releasing
|
|
10224
|
+
* the drain deletes the intent while the obligation still needs the search.
|
|
10225
|
+
*/
|
|
10226
|
+
async recordTempoCloseSearch(id, resumeAt) {
|
|
10227
|
+
await this.pool.query(
|
|
10228
|
+
`UPDATE sdk_hosted_settlement_report_obligations
|
|
10229
|
+
SET close_search_before = LEAST(COALESCE(close_search_before, $4), $4)
|
|
10230
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND rail='tempo' AND operation='close'`,
|
|
10231
|
+
[id, this.dvmId, this.scope, resumeAt.toString()]
|
|
10232
|
+
);
|
|
10107
10233
|
}
|
|
10108
10234
|
async submittedIds(rail, operation, txHash, batchId) {
|
|
10109
10235
|
if (!hex(txHash, 32)) throw new Error("Hosted effect has no exact transaction hash");
|
|
@@ -10211,17 +10337,67 @@ var HostedSettlementReportObligations = class {
|
|
|
10211
10337
|
if (!hex(txHash, 32)) throw new Error("Hosted settlement has no transaction hash");
|
|
10212
10338
|
const write = this.x402SettlementStore ? this.x402Transaction.bind(this) : (operation) => transaction(this.pool, operation);
|
|
10213
10339
|
await write(async (tx) => {
|
|
10214
|
-
|
|
10340
|
+
let row = await this.lockRow(tx, id);
|
|
10341
|
+
if (row.state === "no_report" && !row.tx_hash) {
|
|
10342
|
+
row = await this.successorOfRetiredClose(tx, row);
|
|
10343
|
+
}
|
|
10215
10344
|
if (row.tx_hash && row.tx_hash.toLowerCase() !== txHash.toLowerCase()) {
|
|
10216
10345
|
throw new Error("Hosted settlement transaction replay conflicts");
|
|
10217
10346
|
}
|
|
10218
10347
|
await tx.query(
|
|
10219
10348
|
`UPDATE sdk_hosted_settlement_report_obligations SET tx_hash=COALESCE(tx_hash,$2),
|
|
10220
10349
|
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint WHERE id=$1`,
|
|
10221
|
-
[id, txHash.toLowerCase()]
|
|
10350
|
+
[row.id, txHash.toLowerCase()]
|
|
10222
10351
|
);
|
|
10223
10352
|
});
|
|
10224
10353
|
}
|
|
10354
|
+
/**
|
|
10355
|
+
* Close reservations that never recorded a transaction, oldest first. Each
|
|
10356
|
+
* one owes a payout report only if its close lands, so it stays unresolved
|
|
10357
|
+
* until a reconcile reads how its channel finalized.
|
|
10358
|
+
*/
|
|
10359
|
+
async unsubmittedTempoCloses(limit = 100) {
|
|
10360
|
+
const result = await this.pool.query(
|
|
10361
|
+
`SELECT id,channel_id,original->>'drainId' AS drain_id,created_at_ms,close_search_before
|
|
10362
|
+
FROM sdk_hosted_settlement_report_obligations
|
|
10363
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close'
|
|
10364
|
+
AND state='reserved' AND tx_hash IS NULL
|
|
10365
|
+
ORDER BY created_at_ms,id LIMIT $3`,
|
|
10366
|
+
[this.dvmId, this.scope, limit]
|
|
10367
|
+
);
|
|
10368
|
+
return result.rows.flatMap((row) => row.drain_id === null ? [] : [{
|
|
10369
|
+
id: row.id,
|
|
10370
|
+
channelId: row.channel_id,
|
|
10371
|
+
drainId: row.drain_id,
|
|
10372
|
+
reservedAtMs: Number(row.created_at_ms),
|
|
10373
|
+
searchBefore: searchCursor(row.close_search_before)
|
|
10374
|
+
}]);
|
|
10375
|
+
}
|
|
10376
|
+
/**
|
|
10377
|
+
* A retired close reservation's attempt can still be running when its
|
|
10378
|
+
* successor is reserved, and its close can land. The close is the same
|
|
10379
|
+
* channel's, so it belongs to the drain's outstanding obligation; writing it
|
|
10380
|
+
* onto the retired row would leave that obligation without a transaction and
|
|
10381
|
+
* the payout unreported.
|
|
10382
|
+
*/
|
|
10383
|
+
async successorOfRetiredClose(tx, retired) {
|
|
10384
|
+
const drainId = retired.original.drainId;
|
|
10385
|
+
if (retired.rail !== "tempo" || retired.operation !== "close" || typeof drainId !== "string") {
|
|
10386
|
+
throw new Error("Hosted settlement obligation was resolved without a transaction");
|
|
10387
|
+
}
|
|
10388
|
+
const successor = await tx.query(
|
|
10389
|
+
`SELECT id FROM sdk_hosted_settlement_report_obligations
|
|
10390
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10391
|
+
AND original->>'drainId'=$4 AND state NOT IN ('delivered','no_report')
|
|
10392
|
+
ORDER BY created_at_ms DESC LIMIT 1`,
|
|
10393
|
+
[this.dvmId, this.scope, retired.channel_id, drainId]
|
|
10394
|
+
);
|
|
10395
|
+
if (!successor.rows[0]) {
|
|
10396
|
+
throw new Error("Hosted Tempo close was retired and the drain has no outstanding obligation");
|
|
10397
|
+
}
|
|
10398
|
+
const locked = await this.lockRow(tx, successor.rows[0].id);
|
|
10399
|
+
return locked.state === "no_report" && !locked.tx_hash ? this.successorOfRetiredClose(tx, locked) : locked;
|
|
10400
|
+
}
|
|
10225
10401
|
async recoveryCandidates() {
|
|
10226
10402
|
const result = await this.pool.query(
|
|
10227
10403
|
`SELECT * FROM sdk_hosted_settlement_report_obligations
|
|
@@ -10683,6 +10859,9 @@ var HostedSettlementReportObligations = class {
|
|
|
10683
10859
|
return { id: row.id, replayed: true };
|
|
10684
10860
|
}
|
|
10685
10861
|
};
|
|
10862
|
+
function searchCursor(value) {
|
|
10863
|
+
return value === null ? null : BigInt(value);
|
|
10864
|
+
}
|
|
10686
10865
|
|
|
10687
10866
|
// src/sdk/server/hosting-channel-evidence.ts
|
|
10688
10867
|
import { createHash as createHash7 } from "crypto";
|
|
@@ -19584,7 +19763,7 @@ async function createDVMServer(descriptor, opts) {
|
|
|
19584
19763
|
}
|
|
19585
19764
|
}
|
|
19586
19765
|
if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
|
|
19587
|
-
const { RevenueReporter } = await import("./revenue-reporter-
|
|
19766
|
+
const { RevenueReporter } = await import("./revenue-reporter-KDUZLD5F.js");
|
|
19588
19767
|
const reporter = new RevenueReporter(
|
|
19589
19768
|
opts.db,
|
|
19590
19769
|
platformUrl,
|
|
@@ -19594,7 +19773,7 @@ async function createDVMServer(descriptor, opts) {
|
|
|
19594
19773
|
revenueReporter = reporter;
|
|
19595
19774
|
await reporter.init();
|
|
19596
19775
|
reporter.startRetryLoop();
|
|
19597
|
-
const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-
|
|
19776
|
+
const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-NN6K2IVQ.js");
|
|
19598
19777
|
payoutReporter = new PayoutReporter({
|
|
19599
19778
|
dvmId,
|
|
19600
19779
|
db: opts.db,
|
|
@@ -20051,6 +20230,9 @@ var DVMServer = class {
|
|
|
20051
20230
|
if (!certificateOnly && await control.close(command, support) === "conflict") {
|
|
20052
20231
|
return c.json({ error: "hosting_closure_conflict" }, 409);
|
|
20053
20232
|
}
|
|
20233
|
+
await this.opts.mpp?.reconcileTempoSessionCloses?.().catch(() => {
|
|
20234
|
+
diagnosticConsole.warn({ level: "tempo_routine_sweep_failed", dvm_id: this.opts.dvmId, error: void 0 });
|
|
20235
|
+
});
|
|
20054
20236
|
const evidence = await control.certificate({ ...command, action: "certificate" }, support);
|
|
20055
20237
|
if (!evidence) return c.json({ error: "hosting_closure_stale" }, 409);
|
|
20056
20238
|
return c.json(evidence);
|