@dvmkit/sdk 0.7.0-rc.9 → 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/NOTICE +1 -1
- package/README.md +57 -261
- package/dist/{chunk-5CSR33B5.js → chunk-2U6LAASN.js} +1 -1
- package/dist/chunk-66HGCPBU.js +25 -0
- package/dist/{chunk-QM2OS3NJ.js → chunk-7PM4R4GD.js} +1 -1
- package/dist/{chunk-YE53E4BT.js → chunk-AGE5XZKE.js} +1 -1
- package/dist/{chunk-RJFBBEWQ.js → chunk-ALWVJ2RB.js} +1 -1
- package/dist/{chunk-FYETJDBB.js → chunk-AVV5SYGO.js} +2 -2
- package/dist/{chunk-5PLQOC4B.js → chunk-BFGQG4WY.js} +9 -1
- package/dist/{chunk-IOKEKMIU.js → chunk-FM6QAGJC.js} +2 -2
- package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
- package/dist/{chunk-6JZDFYQL.js → chunk-G3MPHW5I.js} +1 -1
- package/dist/{chunk-2HAFZ5GC.js → chunk-IHQUBATH.js} +2 -2
- package/dist/{chunk-YS6PWVCZ.js → chunk-ILBSZMJL.js} +1 -1
- package/dist/{chunk-RGSWVNQG.js → chunk-IOQIXRGM.js} +2 -43
- package/dist/{chunk-YNHXOONR.js → chunk-J5J63WDC.js} +1 -1
- package/dist/{chunk-G7IXOVKV.js → chunk-JIZ4ZIXX.js} +331 -102
- package/dist/{chunk-ZLWPEIE2.js → chunk-NA2KOR6G.js} +1 -1
- package/dist/{chunk-2DE32RAW.js → chunk-NU5RWJ5B.js} +1 -1
- package/dist/chunk-OP7VCGQR.js +47 -0
- package/dist/{chunk-QN3CQOE2.js → chunk-QKE5NDPF.js} +368 -7
- package/dist/{chunk-7RZEJJTN.js → chunk-RSNC5T6C.js} +2 -2
- package/dist/{chunk-2GTLF74N.js → chunk-UDJNZ3UG.js} +1 -1
- package/dist/{chunk-WGK5KZ6S.js → chunk-UVS7MRJX.js} +3 -3
- package/dist/{chunk-HU6ZMQDN.js → chunk-UWGBI5WK.js} +58 -1
- package/dist/{chunk-OFSOLHRX.js → chunk-WMGXJQI3.js} +1 -1
- package/dist/{chunk-5TIFEMHJ.js → chunk-ZEPJF4JC.js} +2 -2
- package/dist/{content-retention-U7dJM7Ph.d.ts → content-retention-B7ddtJ2y.d.ts} +1 -1
- package/dist/{credit-ledger-F2LWQU6N.js → credit-ledger-KIQIE6I6.js} +3 -3
- package/dist/{credit-menu-CUJdCfpt.d.ts → credit-menu-BH3g9GQa.d.ts} +5 -5
- package/dist/{fx-BvG2Wnjk.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 +10 -8
- package/dist/internal/caller.js +12 -9
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +22 -20
- package/dist/{job-store-DjgN2AOk.d.ts → job-store-Cq21JT37.d.ts} +1 -1
- package/dist/{lightning-backend-DeasHNOP.d.ts → lightning-backend-S8F8nf8q.d.ts} +1 -1
- package/dist/{memory-credit-ledger-XOOWJTBZ.js → memory-credit-ledger-ATYFAYBG.js} +4 -4
- package/dist/{mpp-setup-KGX6PLAF.js → mpp-setup-ZTORW3VF.js} +4 -2
- package/dist/{payout-reporter-W3CCLYCN.js → payout-reporter-NN6K2IVQ.js} +2 -2
- package/dist/{postgres-consumed-credential-store-LUJ5CUJS.js → postgres-consumed-credential-store-XJTE5PCA.js} +1 -1
- package/dist/{postgres-job-store-IX7IUXF4.js → postgres-job-store-AR6ZJUQV.js} +2 -2
- package/dist/{postgres-replay-store-Q6MEQDWZ.js → postgres-replay-store-33K33QV7.js} +2 -2
- package/dist/{pricing-ZUEFO3CM.js → pricing-G5IN5YJE.js} +3 -3
- package/dist/{revenue-reporter-NRS2FANA.js → revenue-reporter-KDUZLD5F.js} +2 -2
- package/dist/server/index.d.ts +10 -10
- package/dist/server/index.js +30 -28
- package/dist/{step-cache-C9JRSFhM.d.ts → step-cache-CR12EZ1Q.d.ts} +136 -4
- package/dist/{tempo-charge-store-NZJTJCUO.js → tempo-charge-store-XEGJYM3G.js} +2 -2
- package/dist/{tempo-session-store-ENFDLDKC.js → tempo-session-store-3WTDOO7H.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +8 -6
- package/dist/{usd-DXDkHn_8.d.ts → usd-s_VZ_eS7.d.ts} +1 -1
- package/dist/{x402-T7WVKDD4.js → x402-HQ5GRQZU.js} +6 -5
- package/package.json +18 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emitDiagnostic,
|
|
3
|
+
projectDiagnosticFields
|
|
4
|
+
} from "./chunk-BFGQG4WY.js";
|
|
5
|
+
|
|
6
|
+
// src/sdk/logger.ts
|
|
7
|
+
function createConsoleLogger(_jobId) {
|
|
8
|
+
return {
|
|
9
|
+
debug(_message, data) {
|
|
10
|
+
emitDiagnostic("debug", "diagnostic.message", data);
|
|
11
|
+
},
|
|
12
|
+
info(_message, data) {
|
|
13
|
+
emitDiagnostic("info", "diagnostic.message", data);
|
|
14
|
+
},
|
|
15
|
+
warn(_message, data) {
|
|
16
|
+
emitDiagnostic("warn", "diagnostic.message", data);
|
|
17
|
+
},
|
|
18
|
+
error(_message, data) {
|
|
19
|
+
emitDiagnostic("error", "diagnostic.message", data);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function createStdoutLogger(context) {
|
|
24
|
+
const emit = (level) => (_message, data) => {
|
|
25
|
+
emitDiagnostic(level, "diagnostic.message", {
|
|
26
|
+
...projectDiagnosticFields(context),
|
|
27
|
+
...projectDiagnosticFields(data)
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
debug: emit("debug"),
|
|
32
|
+
info: emit("info"),
|
|
33
|
+
warn: emit("warn"),
|
|
34
|
+
error: emit("error")
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createNoopLogger() {
|
|
38
|
+
const noop = () => {
|
|
39
|
+
};
|
|
40
|
+
return { debug: noop, info: noop, warn: noop, error: noop };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
createConsoleLogger,
|
|
45
|
+
createStdoutLogger,
|
|
46
|
+
createNoopLogger
|
|
47
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loggers
|
|
3
|
+
} from "./chunk-IOQIXRGM.js";
|
|
1
4
|
import {
|
|
2
5
|
diagnosticConsole
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BFGQG4WY.js";
|
|
4
7
|
|
|
5
8
|
// src/sdk/server/mpp-setup.ts
|
|
6
9
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -533,7 +536,7 @@ function buildMppMethods(opts, env) {
|
|
|
533
536
|
})
|
|
534
537
|
);
|
|
535
538
|
if (opts.tempoSession) {
|
|
536
|
-
methods.push(
|
|
539
|
+
methods.push(withAuthenticationOnlyValidation(
|
|
537
540
|
tempo.session({
|
|
538
541
|
recipient: opts.tempoRecipient,
|
|
539
542
|
account: opts.tempoSession.account,
|
|
@@ -554,15 +557,51 @@ function buildMppMethods(opts, env) {
|
|
|
554
557
|
chainId: opts.tempoSession.chainId ?? chainId,
|
|
555
558
|
...opts.tempoSession.getClient && { getClient: opts.tempoSession.getClient },
|
|
556
559
|
...opts.tempoSession.onSessionSettlement && {
|
|
557
|
-
onSessionSettlement: opts.tempoSession
|
|
560
|
+
onSessionSettlement: claimingCloseSettlement(opts.tempoSession)
|
|
558
561
|
}
|
|
559
562
|
})
|
|
560
|
-
);
|
|
563
|
+
));
|
|
561
564
|
}
|
|
562
565
|
}
|
|
563
566
|
if (methods.length === 0 || !tempoConfiguration) return void 0;
|
|
564
567
|
return { methods, tempoConfiguration };
|
|
565
568
|
}
|
|
569
|
+
function claimingCloseSettlement(session) {
|
|
570
|
+
const callback = session.onSessionSettlement;
|
|
571
|
+
const store = session.store;
|
|
572
|
+
if (session.hostedObligations || !store.getClose || !store.claimCloseReference) return callback;
|
|
573
|
+
return async (context) => {
|
|
574
|
+
if (context.trigger === "close") {
|
|
575
|
+
const intent = await store.getClose(context.channelId);
|
|
576
|
+
if (intent) {
|
|
577
|
+
let claimed = true;
|
|
578
|
+
try {
|
|
579
|
+
claimed = (await store.claimCloseReference(
|
|
580
|
+
context.channelId,
|
|
581
|
+
intent.drainId,
|
|
582
|
+
context.txHash
|
|
583
|
+
)).claimed;
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
if (!claimed) return;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
await callback(context);
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function withAuthenticationOnlyValidation(method) {
|
|
593
|
+
if (method.validate) return method;
|
|
594
|
+
const validate = async ({ credential, request }) => ({
|
|
595
|
+
challenge: credential.challenge,
|
|
596
|
+
credential,
|
|
597
|
+
details: void 0,
|
|
598
|
+
intent: method.intent,
|
|
599
|
+
method: method.name,
|
|
600
|
+
request,
|
|
601
|
+
...credential.source !== void 0 && { source: credential.source }
|
|
602
|
+
});
|
|
603
|
+
return { ...method, validate };
|
|
604
|
+
}
|
|
566
605
|
function resolveTempoNetwork(env) {
|
|
567
606
|
const testnet = env.DVMKIT_TEMPO_TESTNET === "1" || env.DVMKIT_TEMPO_TESTNET === "true";
|
|
568
607
|
const rawChainId = env.DVMKIT_TEMPO_CHAIN_ID?.trim();
|
|
@@ -612,6 +651,9 @@ function wrapMppx(mppx) {
|
|
|
612
651
|
};
|
|
613
652
|
return Object.assign(server, { issueChallenge });
|
|
614
653
|
}
|
|
654
|
+
var TEMPO_CLOSE_LOG_WINDOW = 5000n;
|
|
655
|
+
var TEMPO_CLOSE_LOG_SLACK_MS = 10 * 60 * 1e3;
|
|
656
|
+
var TEMPO_CLOSE_LOG_MAX_WINDOWS = 500;
|
|
615
657
|
var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
|
|
616
658
|
getChannelStatesBatch: TempoSession.Precompile.Chain.getChannelStatesBatch,
|
|
617
659
|
settle: TempoSession.Server.settle,
|
|
@@ -621,7 +663,57 @@ var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
|
|
|
621
663
|
"ChannelClosed",
|
|
622
664
|
channelId
|
|
623
665
|
)
|
|
624
|
-
)
|
|
666
|
+
),
|
|
667
|
+
findChannelClose: async (client, channelId, escrowContract, notBeforeMs, startBlock) => {
|
|
668
|
+
const { getBlock, getBlockNumber, getLogs, getTransaction } = await import("viem/actions");
|
|
669
|
+
const eventNamed = (name) => {
|
|
670
|
+
const event = TempoSession.Precompile.escrowAbi.find(
|
|
671
|
+
(item) => item.type === "event" && item.name === name
|
|
672
|
+
);
|
|
673
|
+
if (!event || event.type !== "event") throw new Error(`Tempo escrow ABI has no ${name} event`);
|
|
674
|
+
return event;
|
|
675
|
+
};
|
|
676
|
+
const closedEvent = eventNamed("ChannelClosed");
|
|
677
|
+
const requestedEvent = eventNamed("CloseRequested");
|
|
678
|
+
const cancelledEvent = eventNamed("CloseRequestCancelled");
|
|
679
|
+
const beforeBound = async (blockNumber) => Number((await getBlock(client, { blockNumber })).timestamp) * 1e3 < notBeforeMs - TEMPO_CLOSE_LOG_SLACK_MS;
|
|
680
|
+
if (startBlock !== void 0 && await beforeBound(startBlock)) {
|
|
681
|
+
return { outcome: "none", stoppedAt: startBlock };
|
|
682
|
+
}
|
|
683
|
+
let toBlock = startBlock ?? await getBlockNumber(client);
|
|
684
|
+
for (let window = 0; window < TEMPO_CLOSE_LOG_MAX_WINDOWS; window += 1) {
|
|
685
|
+
const fromBlock = toBlock >= TEMPO_CLOSE_LOG_WINDOW ? toBlock - TEMPO_CLOSE_LOG_WINDOW + 1n : 0n;
|
|
686
|
+
const range = { address: escrowContract, args: { channelId }, fromBlock, toBlock };
|
|
687
|
+
const [closed, requested, cancelled] = await Promise.all([
|
|
688
|
+
getLogs(client, { ...range, event: closedEvent }),
|
|
689
|
+
getLogs(client, { ...range, event: requestedEvent }),
|
|
690
|
+
getLogs(client, { ...range, event: cancelledEvent })
|
|
691
|
+
]);
|
|
692
|
+
const newest = [
|
|
693
|
+
...closed.map((log) => ({ log, kind: "closed" })),
|
|
694
|
+
...requested.map((log) => ({ log, kind: "requested" })),
|
|
695
|
+
...cancelled.map((log) => ({ log, kind: "cancelled" }))
|
|
696
|
+
].filter(({ log }) => log.transactionHash && log.blockNumber !== null).sort((left, right) => left.log.blockNumber === right.log.blockNumber ? Number(right.log.logIndex ?? 0) - Number(left.log.logIndex ?? 0) : right.log.blockNumber > left.log.blockNumber ? 1 : -1)[0];
|
|
697
|
+
if (newest?.kind === "closed") {
|
|
698
|
+
const transaction = await getTransaction(client, {
|
|
699
|
+
hash: newest.log.transactionHash
|
|
700
|
+
});
|
|
701
|
+
return { outcome: "closed", txHash: newest.log.transactionHash, sender: transaction.from };
|
|
702
|
+
}
|
|
703
|
+
if (newest?.kind === "requested") {
|
|
704
|
+
const graceEnd = newest.log.args.closeGraceEnd;
|
|
705
|
+
const head = await getBlock(client, { blockTag: "latest" });
|
|
706
|
+
if (graceEnd === void 0 || head.timestamp < graceEnd) return { outcome: "none" };
|
|
707
|
+
return { outcome: "exit_requested" };
|
|
708
|
+
}
|
|
709
|
+
if (newest) return { outcome: "none", stoppedAt: 0n };
|
|
710
|
+
if (fromBlock === 0n || await beforeBound(fromBlock)) {
|
|
711
|
+
return { outcome: "none", stoppedAt: fromBlock };
|
|
712
|
+
}
|
|
713
|
+
toBlock = fromBlock - 1n;
|
|
714
|
+
}
|
|
715
|
+
return { outcome: "paused", resumeAt: toBlock };
|
|
716
|
+
}
|
|
625
717
|
};
|
|
626
718
|
function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
627
719
|
let creditLedger;
|
|
@@ -642,6 +734,102 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
642
734
|
}
|
|
643
735
|
if (store) {
|
|
644
736
|
Object.assign(mppx, {
|
|
737
|
+
assertTempoSessionCloseVoucher: async (voucher) => {
|
|
738
|
+
const descriptor = readChannelDescriptor(voucher.descriptor);
|
|
739
|
+
if (!descriptor) {
|
|
740
|
+
throw new MppxErrors.VerificationFailedError({
|
|
741
|
+
reason: "descriptor required for TIP-1034 session action"
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
const details = isStateRecord(voucher.request.methodDetails) ? voucher.request.methodDetails : {};
|
|
745
|
+
const stored = await store.get(voucher.channelId.toLowerCase());
|
|
746
|
+
const state = readTempoSessionState(stored);
|
|
747
|
+
const chainId = typeof details.chainId === "number" ? details.chainId : state?.chainId;
|
|
748
|
+
const escrow = typeof details.escrowContract === "string" ? details.escrowContract : state?.escrowContract;
|
|
749
|
+
if (chainId === void 0 || escrow === void 0) {
|
|
750
|
+
throw new MppxErrors.VerificationFailedError({ reason: "invalid session request" });
|
|
751
|
+
}
|
|
752
|
+
const computed = TempoSession.Precompile.Channel.computeId({
|
|
753
|
+
...descriptor,
|
|
754
|
+
chainId,
|
|
755
|
+
escrow
|
|
756
|
+
});
|
|
757
|
+
if (computed.toLowerCase() !== voucher.channelId.toLowerCase()) {
|
|
758
|
+
throw new MppxErrors.VerificationFailedError({
|
|
759
|
+
reason: "channel descriptor does not match channelId"
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
if (!sameHex(descriptor.payee, voucher.request.recipient)) {
|
|
763
|
+
throw new MppxErrors.VerificationFailedError({
|
|
764
|
+
reason: "channel descriptor payee does not match server destination"
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
if (!sameHex(descriptor.token, voucher.request.currency)) {
|
|
768
|
+
throw new MppxErrors.VerificationFailedError({
|
|
769
|
+
reason: "channel descriptor token does not match server token"
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (!sameHex(descriptor.operator, details.operator ?? ZERO_ADDRESS)) {
|
|
773
|
+
throw new MppxErrors.VerificationFailedError({
|
|
774
|
+
reason: "channel descriptor operator does not match server operator"
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
if (!state || !isStateRecord(stored)) {
|
|
778
|
+
throw new MppxErrors.ChannelNotFoundError({ reason: "channel not found" });
|
|
779
|
+
}
|
|
780
|
+
if (stored.backend !== "precompile") {
|
|
781
|
+
throw new MppxErrors.VerificationFailedError({
|
|
782
|
+
reason: "channel is not precompile-backed"
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const storedDescriptor = readChannelDescriptor(stored.descriptor);
|
|
786
|
+
if (!storedDescriptor || !sameDescriptor(descriptor, storedDescriptor)) {
|
|
787
|
+
throw new MppxErrors.VerificationFailedError({
|
|
788
|
+
reason: "credential descriptor does not match stored channel"
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
const signer = stored.authorizedSigner;
|
|
792
|
+
if (typeof signer !== "string" || !HEX_ADDRESS_RE.test(signer)) {
|
|
793
|
+
throw new MppxErrors.ChannelNotFoundError({ reason: "channel not found" });
|
|
794
|
+
}
|
|
795
|
+
if (state.finalized) {
|
|
796
|
+
throw new MppxErrors.ChannelClosedError({ reason: "channel is already finalized" });
|
|
797
|
+
}
|
|
798
|
+
if (state.closeRequestedAt !== 0n) {
|
|
799
|
+
throw new MppxErrors.ChannelClosedError({ reason: "channel has a pending close request" });
|
|
800
|
+
}
|
|
801
|
+
if (state.deposit === 0n && (voucher.cumulativeAmount !== 0n || state.spent !== 0n)) {
|
|
802
|
+
throw new MppxErrors.ChannelClosedError({ reason: "channel deposit is zero (settled)" });
|
|
803
|
+
}
|
|
804
|
+
if (voucher.cumulativeAmount < state.spent) {
|
|
805
|
+
throw new MppxErrors.VerificationFailedError({
|
|
806
|
+
reason: `close voucher amount must be >= ${state.spent} (spent)`
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
if (voucher.cumulativeAmount < state.settledOnChain) {
|
|
810
|
+
throw new MppxErrors.VerificationFailedError({
|
|
811
|
+
reason: `close voucher amount must be >= ${state.settledOnChain} (on-chain settled)`
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const valid = TempoSession.Precompile.Voucher.verifyVoucher(
|
|
815
|
+
escrow,
|
|
816
|
+
chainId,
|
|
817
|
+
{
|
|
818
|
+
channelId: voucher.channelId,
|
|
819
|
+
cumulativeAmount: voucher.cumulativeAmount,
|
|
820
|
+
signature: voucher.signature
|
|
821
|
+
},
|
|
822
|
+
signer
|
|
823
|
+
);
|
|
824
|
+
if (!valid) {
|
|
825
|
+
throw new MppxErrors.InvalidSignatureError({ reason: "invalid voucher signature" });
|
|
826
|
+
}
|
|
827
|
+
if (maxBigInt(state.spent, state.settledOnChain) > state.deposit) {
|
|
828
|
+
throw new MppxErrors.AmountExceedsDepositError({
|
|
829
|
+
reason: "close capture amount exceeds on-chain deposit"
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
},
|
|
645
833
|
getTempoSessionSpent: async (channelId) => {
|
|
646
834
|
const state = readTempoSessionState(await store.get(channelId.toLowerCase()));
|
|
647
835
|
if (!state) throw new Error(`Tempo session channel ${channelId} not found`);
|
|
@@ -963,6 +1151,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
963
1151
|
}
|
|
964
1152
|
}
|
|
965
1153
|
}
|
|
1154
|
+
await reconcileUnsubmittedCloses();
|
|
966
1155
|
return { scanned: rows.length, closeRequested, settled, inFlight };
|
|
967
1156
|
};
|
|
968
1157
|
const recoverTempoSessionClose = async (channelId, drainId) => {
|
|
@@ -970,7 +1159,8 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
970
1159
|
const intent = await store.getClose(channelId);
|
|
971
1160
|
if (intent?.drainId !== drainId) return null;
|
|
972
1161
|
if (intent.reference) return intent.reference;
|
|
973
|
-
const
|
|
1162
|
+
const stored = await store.get(channelId);
|
|
1163
|
+
const current = readTempoSessionState(stored);
|
|
974
1164
|
if (!current) throw new Error(`Tempo session channel ${channelId} not found`);
|
|
975
1165
|
let finalized = current.finalized;
|
|
976
1166
|
if (!finalized) {
|
|
@@ -984,9 +1174,150 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
984
1174
|
finalized = chainState.deposit === 0n;
|
|
985
1175
|
}
|
|
986
1176
|
if (!finalized) return null;
|
|
987
|
-
const
|
|
1177
|
+
const pending = await config.hostedObligations?.pendingTempoClose(channelId, drainId);
|
|
1178
|
+
const selfHostedReport = !config.hostedObligations && !!config.onSessionSettlement && !!store.claimCloseReference && !!store.getCloseRecovery;
|
|
1179
|
+
let reference = pending?.txHash ?? void 0;
|
|
1180
|
+
if (pending && !reference || selfHostedReport) {
|
|
1181
|
+
const recovery = await store.getCloseRecovery?.(channelId);
|
|
1182
|
+
const notBeforeMs = pending?.reservedAtMs ?? recovery?.updatedAtMs;
|
|
1183
|
+
if (notBeforeMs === void 0) throw new Error(`Tempo session close intent ${channelId} not found`);
|
|
1184
|
+
const obligations = config.hostedObligations;
|
|
1185
|
+
const found = await findFinalizedClose(current, stored, drainId, notBeforeMs, pending ? {
|
|
1186
|
+
from: pending.searchBefore,
|
|
1187
|
+
save: (resumeAt) => obligations.recordTempoCloseSearch(pending.id, resumeAt)
|
|
1188
|
+
} : {
|
|
1189
|
+
from: recovery?.searchBefore ?? null,
|
|
1190
|
+
save: (resumeAt) => store.recordCloseSearch?.(channelId, drainId, resumeAt)
|
|
1191
|
+
});
|
|
1192
|
+
if (pending) {
|
|
1193
|
+
reference = await settleUnsubmittedClose(current.channelId, drainId, pending.id, found);
|
|
1194
|
+
} else if (found.outcome === "ours") {
|
|
1195
|
+
const claimed = await store.claimCloseReference(channelId, drainId, found.txHash);
|
|
1196
|
+
if (claimed.claimed) {
|
|
1197
|
+
await reportRecoveredClose(current, found.txHash, recovery?.settledBeforeClose ?? null);
|
|
1198
|
+
}
|
|
1199
|
+
return claimed.reference;
|
|
1200
|
+
} else if (found.outcome === "none") {
|
|
1201
|
+
loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
|
|
1202
|
+
channel_id: current.channelId,
|
|
1203
|
+
drain_id: drainId
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const recovered = await store.confirmClose(
|
|
1208
|
+
channelId,
|
|
1209
|
+
drainId,
|
|
1210
|
+
reference ?? `channel:${channelId}`
|
|
1211
|
+
);
|
|
988
1212
|
return recovered.reference;
|
|
989
1213
|
};
|
|
1214
|
+
const findFinalizedClose = async (current, stored, drainId, notBeforeMs, cursor) => {
|
|
1215
|
+
const client = await getClient({ chainId: current.chainId });
|
|
1216
|
+
const search = await chainOps.findChannelClose(
|
|
1217
|
+
client,
|
|
1218
|
+
current.channelId,
|
|
1219
|
+
current.escrowContract,
|
|
1220
|
+
notBeforeMs,
|
|
1221
|
+
cursor.from ?? void 0
|
|
1222
|
+
);
|
|
1223
|
+
if (search.outcome === "paused") {
|
|
1224
|
+
await cursor.save(search.resumeAt);
|
|
1225
|
+
loggers.tempo.warn("tempo_session_close_recovery_search_paused", {
|
|
1226
|
+
channel_id: current.channelId,
|
|
1227
|
+
drain_id: drainId,
|
|
1228
|
+
resume_block: search.resumeAt.toString()
|
|
1229
|
+
});
|
|
1230
|
+
throw new Error(`Tempo close search for channel ${current.channelId} continues on the next poll`);
|
|
1231
|
+
}
|
|
1232
|
+
if (search.outcome === "closed") {
|
|
1233
|
+
return cooperativeCloseSenders(stored, config.account.address).has(search.sender.toLowerCase()) ? { outcome: "ours", txHash: search.txHash } : { outcome: "exit" };
|
|
1234
|
+
}
|
|
1235
|
+
if (search.outcome === "exit_requested") return { outcome: "exit" };
|
|
1236
|
+
if (search.stoppedAt !== void 0) await cursor.save(search.stoppedAt);
|
|
1237
|
+
return { outcome: "none" };
|
|
1238
|
+
};
|
|
1239
|
+
const settleUnsubmittedClose = async (channelId, drainId, obligationId, found) => {
|
|
1240
|
+
const obligations = config.hostedObligations;
|
|
1241
|
+
if (found.outcome === "ours") {
|
|
1242
|
+
await obligations.recordSubmitted(obligationId, found.txHash);
|
|
1243
|
+
return found.txHash;
|
|
1244
|
+
}
|
|
1245
|
+
if (found.outcome === "exit") {
|
|
1246
|
+
if (await obligations.resolveTempoCloseExit(obligationId)) {
|
|
1247
|
+
loggers.tempo.warn("tempo_session_close_exit_resolved", {
|
|
1248
|
+
channel_id: channelId,
|
|
1249
|
+
drain_id: drainId,
|
|
1250
|
+
obligation_id: obligationId
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
return void 0;
|
|
1254
|
+
}
|
|
1255
|
+
loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
|
|
1256
|
+
channel_id: channelId,
|
|
1257
|
+
drain_id: drainId,
|
|
1258
|
+
obligation_id: obligationId
|
|
1259
|
+
});
|
|
1260
|
+
return void 0;
|
|
1261
|
+
};
|
|
1262
|
+
const reconcileUnsubmittedCloses = async () => {
|
|
1263
|
+
const obligations = config.hostedObligations;
|
|
1264
|
+
if (!obligations) return;
|
|
1265
|
+
for (const row of await obligations.unsubmittedTempoCloses()) {
|
|
1266
|
+
try {
|
|
1267
|
+
const stored = await store.get(row.channelId);
|
|
1268
|
+
const current = readTempoSessionState(stored);
|
|
1269
|
+
if (!current) continue;
|
|
1270
|
+
if (!current.finalized) {
|
|
1271
|
+
const client = await getClient({ chainId: current.chainId });
|
|
1272
|
+
const [chainState] = await chainOps.getChannelStatesBatch(
|
|
1273
|
+
client,
|
|
1274
|
+
[current.channelId],
|
|
1275
|
+
current.escrowContract
|
|
1276
|
+
);
|
|
1277
|
+
await terminalizeLostBacking(current, chainState);
|
|
1278
|
+
if (!await writeBackChainState(current, chainState)) continue;
|
|
1279
|
+
}
|
|
1280
|
+
const found = await findFinalizedClose(current, stored, row.drainId, row.reservedAtMs, {
|
|
1281
|
+
from: row.searchBefore,
|
|
1282
|
+
save: (resumeAt) => obligations.recordTempoCloseSearch(row.id, resumeAt)
|
|
1283
|
+
});
|
|
1284
|
+
await settleUnsubmittedClose(current.channelId, row.drainId, row.id, found);
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
loggers.tempo.warn("tempo_session_close_reconcile_failed", {
|
|
1287
|
+
channel_id: row.channelId,
|
|
1288
|
+
drain_id: row.drainId,
|
|
1289
|
+
obligation_id: row.id,
|
|
1290
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
const reportRecoveredClose = async (channel, txHash, settledBeforeClose) => {
|
|
1296
|
+
const client = await getClient({ chainId: channel.chainId });
|
|
1297
|
+
const { settledToPayee } = await chainOps.readChannelClose(client, channel.channelId, txHash);
|
|
1298
|
+
if (settledBeforeClose === null || settledToPayee < settledBeforeClose) {
|
|
1299
|
+
loggers.tempo.warn("tempo_session_close_settlement_unreported", {
|
|
1300
|
+
channel_id: channel.channelId,
|
|
1301
|
+
tx_hash: txHash
|
|
1302
|
+
});
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
try {
|
|
1306
|
+
await config.onSessionSettlement({
|
|
1307
|
+
txHash,
|
|
1308
|
+
channelId: channel.channelId,
|
|
1309
|
+
trigger: "close",
|
|
1310
|
+
amount: settledToPayee,
|
|
1311
|
+
delta: settledToPayee - settledBeforeClose
|
|
1312
|
+
});
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
loggers.tempo.warn("tempo_session_close_settlement_callback_failed", {
|
|
1315
|
+
channel_id: channel.channelId,
|
|
1316
|
+
tx_hash: txHash,
|
|
1317
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
990
1321
|
const getClose = store.getClose?.bind(store) ?? (() => Promise.resolve(null));
|
|
991
1322
|
const beginClose = store.beginClose?.bind(store);
|
|
992
1323
|
const withCloseIntent = store.withCloseIntent?.bind(store);
|
|
@@ -995,6 +1326,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
995
1326
|
const confirmClose = store.confirmClose?.bind(store);
|
|
996
1327
|
return Object.assign(mppx, {
|
|
997
1328
|
sweepTempoSessions,
|
|
1329
|
+
...config?.hostedObligations && { reconcileTempoSessionCloses: reconcileUnsubmittedCloses },
|
|
998
1330
|
...beginClose && {
|
|
999
1331
|
beginTempoSessionClose: async (channelId, drainId) => {
|
|
1000
1332
|
await beginClose(channelId, drainId);
|
|
@@ -1014,6 +1346,14 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
1014
1346
|
}
|
|
1015
1347
|
});
|
|
1016
1348
|
}
|
|
1349
|
+
function cooperativeCloseSenders(stored, fallback) {
|
|
1350
|
+
const record = stored && typeof stored === "object" ? stored : {};
|
|
1351
|
+
const descriptor = record.descriptor && typeof record.descriptor === "object" ? record.descriptor : {};
|
|
1352
|
+
const senders = new Set(
|
|
1353
|
+
[record.operator, record.payee, descriptor.operator, descriptor.payee].filter((value) => typeof value === "string" && HEX_ADDRESS_RE.test(value)).map((value) => value.toLowerCase())
|
|
1354
|
+
);
|
|
1355
|
+
return senders.size > 0 ? senders : /* @__PURE__ */ new Set([fallback.toLowerCase()]);
|
|
1356
|
+
}
|
|
1017
1357
|
function readTempoSessionState(value) {
|
|
1018
1358
|
if (!value || typeof value !== "object") return void 0;
|
|
1019
1359
|
const state = value;
|
|
@@ -1028,6 +1368,27 @@ function maxBigInt(left, right) {
|
|
|
1028
1368
|
function isStateRecord(value) {
|
|
1029
1369
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1030
1370
|
}
|
|
1371
|
+
var ZERO_ADDRESS = `0x${"00".repeat(20)}`;
|
|
1372
|
+
var DESCRIPTOR_ADDRESSES = ["payer", "payee", "operator", "token", "authorizedSigner"];
|
|
1373
|
+
var DESCRIPTOR_WORDS = ["salt", "expiringNonceHash"];
|
|
1374
|
+
function readChannelDescriptor(value) {
|
|
1375
|
+
if (!isStateRecord(value)) return void 0;
|
|
1376
|
+
for (const field of DESCRIPTOR_ADDRESSES) {
|
|
1377
|
+
if (typeof value[field] !== "string" || !HEX_ADDRESS_RE.test(value[field])) return void 0;
|
|
1378
|
+
}
|
|
1379
|
+
for (const field of DESCRIPTOR_WORDS) {
|
|
1380
|
+
if (typeof value[field] !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(value[field])) {
|
|
1381
|
+
return void 0;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return value;
|
|
1385
|
+
}
|
|
1386
|
+
function sameHex(left, right) {
|
|
1387
|
+
return typeof left === "string" && typeof right === "string" && left.toLowerCase() === right.toLowerCase();
|
|
1388
|
+
}
|
|
1389
|
+
function sameDescriptor(left, right) {
|
|
1390
|
+
return DESCRIPTOR_ADDRESSES.every((field) => sameHex(left[field], right[field])) && DESCRIPTOR_WORDS.every((field) => sameHex(left[field], right[field]));
|
|
1391
|
+
}
|
|
1031
1392
|
function tempoSettlementDue(args) {
|
|
1032
1393
|
const { channel, chainSettled, forcedClose, intervalMs } = args;
|
|
1033
1394
|
const settlementBoundary = Date.parse(channel.lastSettlementAt ?? channel.createdAt ?? "");
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
callerLoggers
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-66HGCPBU.js";
|
|
4
4
|
import {
|
|
5
5
|
diagnosticConsole,
|
|
6
6
|
projectSpanRow
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BFGQG4WY.js";
|
|
8
8
|
|
|
9
9
|
// src/lib/x402/env.ts
|
|
10
10
|
import { isAddress } from "viem";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
createX402FacilitatorAuthHeaders,
|
|
3
3
|
settleWithFacilitator,
|
|
4
4
|
verifyWithFacilitator
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-AVV5SYGO.js";
|
|
6
6
|
import {
|
|
7
7
|
buildPaymentRequirements,
|
|
8
8
|
decodePayment,
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
} from "./chunk-62FI3NJF.js";
|
|
13
13
|
import {
|
|
14
14
|
callerLoggers
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-66HGCPBU.js";
|
|
16
16
|
import {
|
|
17
17
|
diagnosticConsole
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-BFGQG4WY.js";
|
|
19
19
|
|
|
20
20
|
// src/lib/x402/verify.ts
|
|
21
21
|
async function verifyX402Payment(paymentHeader, config, requiredUsdcMicro, resource, btcUsdRate, exactVersions = { v1: true, v2: true }) {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-YG7G4DPZ.js";
|
|
5
5
|
import {
|
|
6
6
|
withPostgresTransactionRecovery
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7PM4R4GD.js";
|
|
8
8
|
import {
|
|
9
9
|
withSdkInitLock
|
|
10
10
|
} from "./chunk-COYKJRMM.js";
|
|
@@ -69,6 +69,11 @@ var PostgresTempoSessionStore = class {
|
|
|
69
69
|
`ALTER TABLE mpp_tempo_close_intents
|
|
70
70
|
ADD COLUMN IF NOT EXISTS forecast_pending BOOLEAN NOT NULL DEFAULT FALSE`
|
|
71
71
|
);
|
|
72
|
+
await this.pool.query(
|
|
73
|
+
`ALTER TABLE mpp_tempo_close_intents
|
|
74
|
+
ADD COLUMN IF NOT EXISTS close_search_before BIGINT,
|
|
75
|
+
ADD COLUMN IF NOT EXISTS settled_before_close NUMERIC(78,0)`
|
|
76
|
+
);
|
|
72
77
|
});
|
|
73
78
|
}
|
|
74
79
|
async get(key) {
|
|
@@ -273,6 +278,17 @@ var PostgresTempoSessionStore = class {
|
|
|
273
278
|
if (rows[0].forecast_pending !== false) {
|
|
274
279
|
throw new Error(`Tempo session drain ${drainId} refund forecast is still pending`);
|
|
275
280
|
}
|
|
281
|
+
const channel = await tx.query(
|
|
282
|
+
"SELECT state_json FROM mpp_tempo_channels WHERE channel_key = $1",
|
|
283
|
+
[channelId.toLowerCase()]
|
|
284
|
+
);
|
|
285
|
+
const state = channel.rows[0] ? parseStoreValue(channel.rows[0].state_json) : null;
|
|
286
|
+
const settled = state?.settledOnChain;
|
|
287
|
+
await tx.query(
|
|
288
|
+
`UPDATE mpp_tempo_close_intents SET settled_before_close = $3
|
|
289
|
+
WHERE channel_id = $1 AND drain_id = $2`,
|
|
290
|
+
[channelId.toLowerCase(), drainId, typeof settled === "bigint" ? settled.toString() : null]
|
|
291
|
+
);
|
|
276
292
|
return record;
|
|
277
293
|
});
|
|
278
294
|
}
|
|
@@ -357,6 +373,47 @@ var PostgresTempoSessionStore = class {
|
|
|
357
373
|
if (!rows[0]) throw new Error(`Tempo session close intent not found`);
|
|
358
374
|
return closeRecord(rows[0]);
|
|
359
375
|
}
|
|
376
|
+
/** Set a close's reference only if none is set, reporting whether this call set it. */
|
|
377
|
+
async claimCloseReference(channelId, drainId, reference) {
|
|
378
|
+
const { rows } = await this.pool.query(
|
|
379
|
+
`UPDATE mpp_tempo_close_intents
|
|
380
|
+
SET reference = $3, updated_at = $4
|
|
381
|
+
WHERE channel_id = $1 AND drain_id = $2 AND forecast_pending = FALSE
|
|
382
|
+
AND reference IS NULL
|
|
383
|
+
RETURNING reference`,
|
|
384
|
+
[channelId.toLowerCase(), drainId, reference, Date.now()]
|
|
385
|
+
);
|
|
386
|
+
if (rows[0]) return { reference: rows[0].reference, claimed: true };
|
|
387
|
+
const current = await this.getClose(channelId);
|
|
388
|
+
if (current?.drainId !== drainId || current.reference === null) {
|
|
389
|
+
throw new Error(`Tempo session close intent not found`);
|
|
390
|
+
}
|
|
391
|
+
return { reference: current.reference, claimed: false };
|
|
392
|
+
}
|
|
393
|
+
async getCloseRecovery(channelId) {
|
|
394
|
+
const { rows } = await this.pool.query(
|
|
395
|
+
`SELECT updated_at, close_search_before, settled_before_close
|
|
396
|
+
FROM mpp_tempo_close_intents
|
|
397
|
+
WHERE channel_id = $1`,
|
|
398
|
+
[channelId.toLowerCase()]
|
|
399
|
+
);
|
|
400
|
+
const row = rows[0];
|
|
401
|
+
if (!row) return null;
|
|
402
|
+
return {
|
|
403
|
+
updatedAtMs: Number(row.updated_at),
|
|
404
|
+
searchBefore: row.close_search_before === null ? null : BigInt(row.close_search_before),
|
|
405
|
+
settledBeforeClose: row.settled_before_close === null ? null : BigInt(row.settled_before_close)
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/** Record where a paused close search resumes; the cursor only moves back. */
|
|
409
|
+
async recordCloseSearch(channelId, drainId, resumeAt) {
|
|
410
|
+
await this.pool.query(
|
|
411
|
+
`UPDATE mpp_tempo_close_intents
|
|
412
|
+
SET close_search_before = LEAST(COALESCE(close_search_before, $3), $3)
|
|
413
|
+
WHERE channel_id = $1 AND drain_id = $2`,
|
|
414
|
+
[channelId.toLowerCase(), drainId, resumeAt.toString()]
|
|
415
|
+
);
|
|
416
|
+
}
|
|
360
417
|
async runCloseIntentTransaction(channelId, drainId, operation) {
|
|
361
418
|
return this.transaction("tempo_session.close_intent", async (client) => {
|
|
362
419
|
const record = await this.writeCloseIntent(client, channelId, drainId);
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-UKD3R2KU.js";
|
|
4
4
|
import {
|
|
5
5
|
callerLoggers
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-66HGCPBU.js";
|
|
7
7
|
import {
|
|
8
8
|
DvmError
|
|
9
9
|
} from "./chunk-BPYMEMQX.js";
|
|
10
10
|
import {
|
|
11
11
|
diagnosticConsole
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BFGQG4WY.js";
|
|
13
13
|
|
|
14
14
|
// src/lib/cashu/lock-pubkey.ts
|
|
15
15
|
function toLockPubkey(raw) {
|