@dvmkit/sdk 0.7.0-rc.1 → 0.7.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -2
- package/dist/{chunk-S3OY533W.js → chunk-2NUFE2PG.js} +47 -5
- package/dist/{chunk-RA2WIX7G.js → chunk-3E4C6W7W.js} +17 -6
- package/dist/{chunk-GY66THYI.js → chunk-53SNRGQQ.js} +372 -153
- package/dist/{chunk-CXCQLOOK.js → chunk-62FI3NJF.js} +42 -107
- package/dist/chunk-6HJEVCVI.js +17 -0
- package/dist/chunk-A262WVYX.js +6 -0
- package/dist/{chunk-IV6Q7NRW.js → chunk-C5RIQWG4.js} +1 -1
- package/dist/{chunk-GAOAYHZA.js → chunk-FBQVOOYF.js} +37 -6
- package/dist/{chunk-Z4ZT5LNT.js → chunk-GEI4MAXE.js} +3 -3
- package/dist/{chunk-OML32F6I.js → chunk-GLIAR7RN.js} +1 -1
- package/dist/{chunk-X7EZ5CLI.js → chunk-HCATFJDD.js} +3 -3
- package/dist/{chunk-BYCEKO65.js → chunk-HPCXF3DV.js} +220 -32
- package/dist/{chunk-6XF3RVVX.js → chunk-IRXIH775.js} +5 -5
- package/dist/{chunk-QR2EF2NI.js → chunk-KCQDXIJU.js} +1 -42
- package/dist/chunk-MDW7ZR7V.js +47 -0
- package/dist/{chunk-KA6VN5FK.js → chunk-MQYM4T66.js} +3 -3
- package/dist/{chunk-P6QON7ER.js → chunk-NV7Z7377.js} +1 -1
- package/dist/{chunk-KMED3H3A.js → chunk-OBBVDKLE.js} +476 -230
- package/dist/{chunk-EQLA4FFY.js → chunk-OHUXCAOM.js} +17 -6
- package/dist/{chunk-FWGWLPXF.js → chunk-PZ5IAWFD.js} +1 -1
- package/dist/{chunk-IXR5RFK7.js → chunk-R3TQSXWT.js} +191 -5
- package/dist/{chunk-VHNPVGSA.js → chunk-T5DIXOLR.js} +3 -3
- package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
- package/dist/chunk-WMGXJQI3.js +120 -0
- package/dist/{chunk-N5RZ5HVB.js → chunk-X6S2CHFM.js} +2 -2
- package/dist/{chunk-ETXDTUJO.js → chunk-ZCNPIILZ.js} +112 -1
- package/dist/{content-retention-DHyLdA6b.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
- package/dist/{credit-ledger-MCUXYKSJ.js → credit-ledger-MFIIOSZH.js} +4 -4
- package/dist/{credit-menu-DjnkeEUU.d.ts → credit-menu-D2t1-Tf6.d.ts} +13 -6
- package/dist/{fx-zMWFzfrU.d.ts → fx-Ql4nvdbh.d.ts} +10 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +248 -16
- package/dist/internal/caller.d.ts +5032 -8272
- package/dist/internal/caller.js +4302 -8084
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +30 -31
- package/dist/{job-store-DdLXBpol.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
- package/dist/{lightning-backend-Bw-qtAbt.d.ts → lightning-backend-CY5ML3km.d.ts} +2 -2
- package/dist/{memory-credit-ledger-SBSMGMDX.js → memory-credit-ledger-44NE4PZC.js} +5 -5
- package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-HRWUBULX.js} +4 -2
- package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-7P66T77I.js} +6 -4
- package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-IDGPFKIL.js} +3 -3
- package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-UKNJ2F6Z.js} +2 -2
- package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-PMA4ADXN.js} +2 -2
- package/dist/{pricing-XT2IAPNE.js → pricing-52XJHJ6K.js} +12 -12
- package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-HV7RMS54.js} +2 -2
- package/dist/server/index.d.ts +12 -11
- package/dist/server/index.js +47 -48
- package/dist/{step-cache-2MkvenJn.d.ts → step-cache-BOWAn_BH.d.ts} +84 -7
- package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-P2Z4RMU7.js} +2 -2
- package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-VSP7KLBS.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +7 -6
- package/dist/{usd-DI3d3gTK.d.ts → usd-MAtMp3DB.d.ts} +1 -1
- package/dist/{x402-FT4S4JS4.js → x402-3IQ3HXSG.js} +8 -26
- package/package.json +1 -1
- package/dist/chunk-AT6V3SY7.js +0 -102
- package/dist/chunk-EZXAJ6EI.js +0 -1082
- package/dist/chunk-HCLVYZUZ.js +0 -244
- package/dist/chunk-JEVLNAO3.js +0 -1668
- package/dist/chunk-NFMOPYBA.js +0 -357
- package/dist/chunk-O2OTZ5GP.js +0 -1869
- package/dist/chunk-OFVV37XC.js +0 -57
- package/dist/chunk-SZVXOQ75.js +0 -211
- package/dist/chunk-WBOKPC37.js +0 -795
- package/dist/tempo-lifecycle-GJ77YKVY.js +0 -15
- package/dist/tempo-wallet-X2GGAZ6X.js +0 -40
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
ContentHoldError,
|
|
3
3
|
applyContentHoldMutation
|
|
4
4
|
} from "./chunk-AFV77S4C.js";
|
|
5
|
+
import {
|
|
6
|
+
diagnosticConsole
|
|
7
|
+
} from "./chunk-ZCNPIILZ.js";
|
|
5
8
|
import {
|
|
6
9
|
withSdkInitLock
|
|
7
10
|
} from "./chunk-COYKJRMM.js";
|
|
8
|
-
import {
|
|
9
|
-
diagnosticConsole
|
|
10
|
-
} from "./chunk-ETXDTUJO.js";
|
|
11
11
|
|
|
12
12
|
// src/sdk/server/postgres-job-store.ts
|
|
13
13
|
import { createHash } from "crypto";
|
|
@@ -286,10 +286,20 @@ var PostgresJobStore = class {
|
|
|
286
286
|
CREATE INDEX IF NOT EXISTS idx_jobs_retention
|
|
287
287
|
ON jobs (last_activity_at, id)
|
|
288
288
|
WHERE redacted_at IS NULL AND status IN ('completed', 'failed', 'cancelled');
|
|
289
|
-
|
|
289
|
+
-- Terminal cost recovery runs at boot and every 30s on each machine.
|
|
290
|
+
-- Indexing only rows whose latest revision is not yet enqueued keeps each
|
|
291
|
+
-- pass proportional to that backlog; marking a row drops it from the
|
|
292
|
+
-- index. listUnreportedTerminalCosts' WHERE clause must imply this
|
|
293
|
+
-- predicate or the planner falls back to scanning terminal history.
|
|
294
|
+
DROP INDEX IF EXISTS idx_jobs_pending_cost_report;
|
|
295
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_unreported_terminal_cost
|
|
290
296
|
ON jobs (dvm_id, id)
|
|
291
297
|
WHERE cost_revision IS NOT NULL
|
|
292
|
-
AND
|
|
298
|
+
AND (cost_reported_revision IS NULL OR cost_reported_revision < cost_revision)
|
|
299
|
+
AND (
|
|
300
|
+
status IN ('failed', 'cancelled')
|
|
301
|
+
OR (status = 'completed' AND paid_msats = 0)
|
|
302
|
+
);
|
|
293
303
|
-- the idempotent-replay lookup. payment_tx_hash carries the
|
|
294
304
|
-- caller's X-Cashu-Request-Id on the accumulator path, so a retried paid
|
|
295
305
|
-- submit finds the job its payment already created.
|
|
@@ -689,7 +699,8 @@ var PostgresJobStore = class {
|
|
|
689
699
|
}
|
|
690
700
|
/**
|
|
691
701
|
* Read terminal zero-revenue jobs whose latest declared-cost revision has
|
|
692
|
-
* not yet reached the durable reporter outbox.
|
|
702
|
+
* not yet reached the durable reporter outbox. Keep the filter in step with
|
|
703
|
+
* `idx_jobs_unreported_terminal_cost`, which bounds each pass to the backlog.
|
|
693
704
|
*/
|
|
694
705
|
async listUnreportedTerminalCosts(args) {
|
|
695
706
|
const { rows } = await this.pool.query(
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loggers
|
|
3
|
+
} from "./chunk-KCQDXIJU.js";
|
|
1
4
|
import {
|
|
2
5
|
diagnosticConsole
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ZCNPIILZ.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,
|
|
@@ -557,12 +560,25 @@ function buildMppMethods(opts, env) {
|
|
|
557
560
|
onSessionSettlement: opts.tempoSession.onSessionSettlement
|
|
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 withAuthenticationOnlyValidation(method) {
|
|
570
|
+
if (method.validate) return method;
|
|
571
|
+
const validate = async ({ credential, request }) => ({
|
|
572
|
+
challenge: credential.challenge,
|
|
573
|
+
credential,
|
|
574
|
+
details: void 0,
|
|
575
|
+
intent: method.intent,
|
|
576
|
+
method: method.name,
|
|
577
|
+
request,
|
|
578
|
+
...credential.source !== void 0 && { source: credential.source }
|
|
579
|
+
});
|
|
580
|
+
return { ...method, validate };
|
|
581
|
+
}
|
|
566
582
|
function resolveTempoNetwork(env) {
|
|
567
583
|
const testnet = env.DVMKIT_TEMPO_TESTNET === "1" || env.DVMKIT_TEMPO_TESTNET === "true";
|
|
568
584
|
const rawChainId = env.DVMKIT_TEMPO_CHAIN_ID?.trim();
|
|
@@ -612,6 +628,9 @@ function wrapMppx(mppx) {
|
|
|
612
628
|
};
|
|
613
629
|
return Object.assign(server, { issueChallenge });
|
|
614
630
|
}
|
|
631
|
+
var TEMPO_CLOSE_LOG_WINDOW = 5000n;
|
|
632
|
+
var TEMPO_CLOSE_LOG_SLACK_MS = 10 * 60 * 1e3;
|
|
633
|
+
var TEMPO_CLOSE_LOG_MAX_WINDOWS = 500;
|
|
615
634
|
var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
|
|
616
635
|
getChannelStatesBatch: TempoSession.Precompile.Chain.getChannelStatesBatch,
|
|
617
636
|
settle: TempoSession.Server.settle,
|
|
@@ -621,7 +640,32 @@ var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
|
|
|
621
640
|
"ChannelClosed",
|
|
622
641
|
channelId
|
|
623
642
|
)
|
|
624
|
-
)
|
|
643
|
+
),
|
|
644
|
+
findChannelClose: async (client, channelId, escrowContract, notBeforeMs) => {
|
|
645
|
+
const { getBlock, getBlockNumber, getLogs } = await import("viem/actions");
|
|
646
|
+
const event = TempoSession.Precompile.escrowAbi.find(
|
|
647
|
+
(item) => item.type === "event" && item.name === "ChannelClosed"
|
|
648
|
+
);
|
|
649
|
+
if (!event || event.type !== "event") throw new Error("Tempo escrow ABI has no ChannelClosed event");
|
|
650
|
+
let toBlock = await getBlockNumber(client);
|
|
651
|
+
for (let window = 0; window < TEMPO_CLOSE_LOG_MAX_WINDOWS; window += 1) {
|
|
652
|
+
const fromBlock = toBlock >= TEMPO_CLOSE_LOG_WINDOW ? toBlock - TEMPO_CLOSE_LOG_WINDOW + 1n : 0n;
|
|
653
|
+
const logs = await getLogs(client, {
|
|
654
|
+
address: escrowContract,
|
|
655
|
+
event,
|
|
656
|
+
args: { channelId },
|
|
657
|
+
fromBlock,
|
|
658
|
+
toBlock
|
|
659
|
+
});
|
|
660
|
+
const closed = logs.find((log) => log.transactionHash);
|
|
661
|
+
if (closed?.transactionHash) return closed.transactionHash;
|
|
662
|
+
if (fromBlock === 0n) return null;
|
|
663
|
+
const start = await getBlock(client, { blockNumber: fromBlock });
|
|
664
|
+
if (Number(start.timestamp) * 1e3 < notBeforeMs - TEMPO_CLOSE_LOG_SLACK_MS) return null;
|
|
665
|
+
toBlock = fromBlock - 1n;
|
|
666
|
+
}
|
|
667
|
+
throw new Error(`Tempo close search for channel ${channelId} exceeded its block budget`);
|
|
668
|
+
}
|
|
625
669
|
};
|
|
626
670
|
function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
627
671
|
let creditLedger;
|
|
@@ -642,6 +686,102 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
642
686
|
}
|
|
643
687
|
if (store) {
|
|
644
688
|
Object.assign(mppx, {
|
|
689
|
+
assertTempoSessionCloseVoucher: async (voucher) => {
|
|
690
|
+
const descriptor = readChannelDescriptor(voucher.descriptor);
|
|
691
|
+
if (!descriptor) {
|
|
692
|
+
throw new MppxErrors.VerificationFailedError({
|
|
693
|
+
reason: "descriptor required for TIP-1034 session action"
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
const details = isStateRecord(voucher.request.methodDetails) ? voucher.request.methodDetails : {};
|
|
697
|
+
const stored = await store.get(voucher.channelId.toLowerCase());
|
|
698
|
+
const state = readTempoSessionState(stored);
|
|
699
|
+
const chainId = typeof details.chainId === "number" ? details.chainId : state?.chainId;
|
|
700
|
+
const escrow = typeof details.escrowContract === "string" ? details.escrowContract : state?.escrowContract;
|
|
701
|
+
if (chainId === void 0 || escrow === void 0) {
|
|
702
|
+
throw new MppxErrors.VerificationFailedError({ reason: "invalid session request" });
|
|
703
|
+
}
|
|
704
|
+
const computed = TempoSession.Precompile.Channel.computeId({
|
|
705
|
+
...descriptor,
|
|
706
|
+
chainId,
|
|
707
|
+
escrow
|
|
708
|
+
});
|
|
709
|
+
if (computed.toLowerCase() !== voucher.channelId.toLowerCase()) {
|
|
710
|
+
throw new MppxErrors.VerificationFailedError({
|
|
711
|
+
reason: "channel descriptor does not match channelId"
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (!sameHex(descriptor.payee, voucher.request.recipient)) {
|
|
715
|
+
throw new MppxErrors.VerificationFailedError({
|
|
716
|
+
reason: "channel descriptor payee does not match server destination"
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
if (!sameHex(descriptor.token, voucher.request.currency)) {
|
|
720
|
+
throw new MppxErrors.VerificationFailedError({
|
|
721
|
+
reason: "channel descriptor token does not match server token"
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (!sameHex(descriptor.operator, details.operator ?? ZERO_ADDRESS)) {
|
|
725
|
+
throw new MppxErrors.VerificationFailedError({
|
|
726
|
+
reason: "channel descriptor operator does not match server operator"
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
if (!state || !isStateRecord(stored)) {
|
|
730
|
+
throw new MppxErrors.ChannelNotFoundError({ reason: "channel not found" });
|
|
731
|
+
}
|
|
732
|
+
if (stored.backend !== "precompile") {
|
|
733
|
+
throw new MppxErrors.VerificationFailedError({
|
|
734
|
+
reason: "channel is not precompile-backed"
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
const storedDescriptor = readChannelDescriptor(stored.descriptor);
|
|
738
|
+
if (!storedDescriptor || !sameDescriptor(descriptor, storedDescriptor)) {
|
|
739
|
+
throw new MppxErrors.VerificationFailedError({
|
|
740
|
+
reason: "credential descriptor does not match stored channel"
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
const signer = stored.authorizedSigner;
|
|
744
|
+
if (typeof signer !== "string" || !HEX_ADDRESS_RE.test(signer)) {
|
|
745
|
+
throw new MppxErrors.ChannelNotFoundError({ reason: "channel not found" });
|
|
746
|
+
}
|
|
747
|
+
if (state.finalized) {
|
|
748
|
+
throw new MppxErrors.ChannelClosedError({ reason: "channel is already finalized" });
|
|
749
|
+
}
|
|
750
|
+
if (state.closeRequestedAt !== 0n) {
|
|
751
|
+
throw new MppxErrors.ChannelClosedError({ reason: "channel has a pending close request" });
|
|
752
|
+
}
|
|
753
|
+
if (state.deposit === 0n && (voucher.cumulativeAmount !== 0n || state.spent !== 0n)) {
|
|
754
|
+
throw new MppxErrors.ChannelClosedError({ reason: "channel deposit is zero (settled)" });
|
|
755
|
+
}
|
|
756
|
+
if (voucher.cumulativeAmount < state.spent) {
|
|
757
|
+
throw new MppxErrors.VerificationFailedError({
|
|
758
|
+
reason: `close voucher amount must be >= ${state.spent} (spent)`
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
if (voucher.cumulativeAmount < state.settledOnChain) {
|
|
762
|
+
throw new MppxErrors.VerificationFailedError({
|
|
763
|
+
reason: `close voucher amount must be >= ${state.settledOnChain} (on-chain settled)`
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const valid = TempoSession.Precompile.Voucher.verifyVoucher(
|
|
767
|
+
escrow,
|
|
768
|
+
chainId,
|
|
769
|
+
{
|
|
770
|
+
channelId: voucher.channelId,
|
|
771
|
+
cumulativeAmount: voucher.cumulativeAmount,
|
|
772
|
+
signature: voucher.signature
|
|
773
|
+
},
|
|
774
|
+
signer
|
|
775
|
+
);
|
|
776
|
+
if (!valid) {
|
|
777
|
+
throw new MppxErrors.InvalidSignatureError({ reason: "invalid voucher signature" });
|
|
778
|
+
}
|
|
779
|
+
if (maxBigInt(state.spent, state.settledOnChain) > state.deposit) {
|
|
780
|
+
throw new MppxErrors.AmountExceedsDepositError({
|
|
781
|
+
reason: "close capture amount exceeds on-chain deposit"
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
},
|
|
645
785
|
getTempoSessionSpent: async (channelId) => {
|
|
646
786
|
const state = readTempoSessionState(await store.get(channelId.toLowerCase()));
|
|
647
787
|
if (!state) throw new Error(`Tempo session channel ${channelId} not found`);
|
|
@@ -984,7 +1124,32 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
|
|
|
984
1124
|
finalized = chainState.deposit === 0n;
|
|
985
1125
|
}
|
|
986
1126
|
if (!finalized) return null;
|
|
987
|
-
const
|
|
1127
|
+
const pending = await config.hostedObligations?.pendingTempoClose(channelId, drainId);
|
|
1128
|
+
let reference = pending?.txHash ?? void 0;
|
|
1129
|
+
if (pending && !reference) {
|
|
1130
|
+
const client = await getClient({ chainId: current.chainId });
|
|
1131
|
+
const txHash = await chainOps.findChannelClose(
|
|
1132
|
+
client,
|
|
1133
|
+
current.channelId,
|
|
1134
|
+
current.escrowContract,
|
|
1135
|
+
pending.reservedAtMs
|
|
1136
|
+
);
|
|
1137
|
+
if (txHash) {
|
|
1138
|
+
await config.hostedObligations.recordSubmitted(pending.id, txHash);
|
|
1139
|
+
reference = txHash;
|
|
1140
|
+
} else {
|
|
1141
|
+
loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
|
|
1142
|
+
channel_id: current.channelId,
|
|
1143
|
+
drain_id: drainId,
|
|
1144
|
+
obligation_id: pending.id
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const recovered = await store.confirmClose(
|
|
1149
|
+
channelId,
|
|
1150
|
+
drainId,
|
|
1151
|
+
reference ?? `channel:${channelId}`
|
|
1152
|
+
);
|
|
988
1153
|
return recovered.reference;
|
|
989
1154
|
};
|
|
990
1155
|
const getClose = store.getClose?.bind(store) ?? (() => Promise.resolve(null));
|
|
@@ -1028,6 +1193,27 @@ function maxBigInt(left, right) {
|
|
|
1028
1193
|
function isStateRecord(value) {
|
|
1029
1194
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1030
1195
|
}
|
|
1196
|
+
var ZERO_ADDRESS = `0x${"00".repeat(20)}`;
|
|
1197
|
+
var DESCRIPTOR_ADDRESSES = ["payer", "payee", "operator", "token", "authorizedSigner"];
|
|
1198
|
+
var DESCRIPTOR_WORDS = ["salt", "expiringNonceHash"];
|
|
1199
|
+
function readChannelDescriptor(value) {
|
|
1200
|
+
if (!isStateRecord(value)) return void 0;
|
|
1201
|
+
for (const field of DESCRIPTOR_ADDRESSES) {
|
|
1202
|
+
if (typeof value[field] !== "string" || !HEX_ADDRESS_RE.test(value[field])) return void 0;
|
|
1203
|
+
}
|
|
1204
|
+
for (const field of DESCRIPTOR_WORDS) {
|
|
1205
|
+
if (typeof value[field] !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(value[field])) {
|
|
1206
|
+
return void 0;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return value;
|
|
1210
|
+
}
|
|
1211
|
+
function sameHex(left, right) {
|
|
1212
|
+
return typeof left === "string" && typeof right === "string" && left.toLowerCase() === right.toLowerCase();
|
|
1213
|
+
}
|
|
1214
|
+
function sameDescriptor(left, right) {
|
|
1215
|
+
return DESCRIPTOR_ADDRESSES.every((field) => sameHex(left[field], right[field])) && DESCRIPTOR_WORDS.every((field) => sameHex(left[field], right[field]));
|
|
1216
|
+
}
|
|
1031
1217
|
function tempoSettlementDue(args) {
|
|
1032
1218
|
const { channel, chainSettled, forcedClose, intervalMs } = args;
|
|
1033
1219
|
const settlementBoundary = Date.parse(channel.lastSettlementAt ?? channel.createdAt ?? "");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
diagnosticConsole
|
|
3
|
+
} from "./chunk-ZCNPIILZ.js";
|
|
1
4
|
import {
|
|
2
5
|
withSdkInitLock
|
|
3
6
|
} from "./chunk-COYKJRMM.js";
|
|
4
|
-
import {
|
|
5
|
-
diagnosticConsole
|
|
6
|
-
} from "./chunk-ETXDTUJO.js";
|
|
7
7
|
|
|
8
8
|
// src/sdk/server/postgres-replay-store.ts
|
|
9
9
|
var DEFAULT_TABLE_NAME = "signed_request_replays";
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
canonicaliseForSigning
|
|
3
|
-
} from "./chunk-OFVV37XC.js";
|
|
4
|
-
|
|
5
1
|
// src/sdk/types.ts
|
|
6
2
|
function isZodSchema(value) {
|
|
7
3
|
return typeof value === "object" && value !== null && "_zod" in value && "parse" in value && "safeParse" in value;
|
|
@@ -16,6 +12,58 @@ var DEFAULT_CREDIT_MAX = "$5.00";
|
|
|
16
12
|
var DEFAULT_CREDIT_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
17
13
|
var DEFAULT_JOB_RETENTION_DAYS = 730;
|
|
18
14
|
|
|
15
|
+
// src/lib/cast-auth/canonical-json.ts
|
|
16
|
+
function canonicalize(value) {
|
|
17
|
+
return JSON.stringify(sortDeep(value));
|
|
18
|
+
}
|
|
19
|
+
function canonicaliseForSigning(value) {
|
|
20
|
+
return new TextEncoder().encode(canonicalize(value));
|
|
21
|
+
}
|
|
22
|
+
function sortDeep(value) {
|
|
23
|
+
if (value === null) return null;
|
|
24
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
25
|
+
if (typeof value === "number") {
|
|
26
|
+
if (!Number.isFinite(value)) {
|
|
27
|
+
throw new TypeError(`canonicalize: non-finite number is not JSON-serialisable`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) return value.map(sortDeep);
|
|
32
|
+
if (typeof value === "object") {
|
|
33
|
+
const sorted = {};
|
|
34
|
+
for (const key of Object.keys(value).sort()) {
|
|
35
|
+
const v = value[key];
|
|
36
|
+
if (v === void 0) continue;
|
|
37
|
+
sorted[key] = sortDeep(v);
|
|
38
|
+
}
|
|
39
|
+
return sorted;
|
|
40
|
+
}
|
|
41
|
+
throw new TypeError(`canonicalize: unsupported value of type ${typeof value}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/lib/cast-auth/verify.ts
|
|
45
|
+
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
46
|
+
function verifyCanonicalSignedRequest(payload, signatureHex, pubkeyHex) {
|
|
47
|
+
if (!/^[0-9a-f]{64}$/i.test(pubkeyHex) || !/^[0-9a-f]{128}$/i.test(signatureHex)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const data = canonicaliseForSigning(payload);
|
|
51
|
+
const signature = hexToBytes(signatureHex);
|
|
52
|
+
const pubkey = hexToBytes(pubkeyHex);
|
|
53
|
+
try {
|
|
54
|
+
return schnorr.verify(signature, data, pubkey);
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function hexToBytes(hex) {
|
|
60
|
+
const out = new Uint8Array(hex.length / 2);
|
|
61
|
+
for (let i = 0; i < out.length; i++) {
|
|
62
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
19
67
|
// src/lib/signed-request-statement.ts
|
|
20
68
|
var SIGNED_REQUEST_AUTH_ID = "secp256k1-schnorr-v2";
|
|
21
69
|
var SIGNED_REQUEST_STATEMENT_VERSION = 2;
|
|
@@ -81,6 +129,9 @@ export {
|
|
|
81
129
|
DEFAULT_CREDIT_MAX,
|
|
82
130
|
DEFAULT_CREDIT_TTL_SECONDS,
|
|
83
131
|
DEFAULT_JOB_RETENTION_DAYS,
|
|
132
|
+
canonicalize,
|
|
133
|
+
canonicaliseForSigning,
|
|
134
|
+
verifyCanonicalSignedRequest,
|
|
84
135
|
SIGNED_REQUEST_AUTH_ID,
|
|
85
136
|
SIGNED_REQUEST_STATEMENT_VERSION,
|
|
86
137
|
signedRequestStatementHeader,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
X402_DEFAULT_NETWORK,
|
|
3
|
+
resolveRpcHttpTransportOptions,
|
|
4
|
+
resolveRpcOverride
|
|
5
|
+
} from "./chunk-62FI3NJF.js";
|
|
6
|
+
import {
|
|
7
|
+
redactUrl,
|
|
8
|
+
redactUrlsInText
|
|
9
|
+
} from "./chunk-FUJ36YDV.js";
|
|
10
|
+
import {
|
|
11
|
+
DvmError,
|
|
12
|
+
x402NetworkByCaip2,
|
|
13
|
+
x402SupportedNetworksHint
|
|
14
|
+
} from "./chunk-BPYMEMQX.js";
|
|
15
|
+
|
|
16
|
+
// src/lib/x402/asset.ts
|
|
17
|
+
function resolveX402Asset(network = X402_DEFAULT_NETWORK) {
|
|
18
|
+
const spec = x402NetworkByCaip2(network);
|
|
19
|
+
if (!spec) return null;
|
|
20
|
+
return {
|
|
21
|
+
address: spec.usdc,
|
|
22
|
+
symbol: "USDC",
|
|
23
|
+
// "native" is the load-bearing word: every contract in the table is
|
|
24
|
+
// Circle's own USDC, and the dollars that read as zero here are the
|
|
25
|
+
// bridged ones.
|
|
26
|
+
label: "native USDC",
|
|
27
|
+
unit: "USDC",
|
|
28
|
+
networkLabel: spec.label,
|
|
29
|
+
confusable: spec.confusable
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/lib/evm-private-key.ts
|
|
34
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
35
|
+
import { hexToBytes } from "@noble/hashes/utils.js";
|
|
36
|
+
var EVM_PRIVATE_KEY_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
37
|
+
function isValidEvmPrivateKey(value) {
|
|
38
|
+
return EVM_PRIVATE_KEY_RE.test(value) && secp256k1.utils.isValidSecretKey(hexToBytes(value.slice(2)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/lib/x402/wallet.ts
|
|
42
|
+
async function ensureAddress(wallet) {
|
|
43
|
+
if (!isValidEvmPrivateKey(wallet.privateKey)) throw invalidX402PrivateKey();
|
|
44
|
+
if (wallet.address) return wallet;
|
|
45
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
46
|
+
const account = privateKeyToAccount(wallet.privateKey);
|
|
47
|
+
return { ...wallet, address: account.address };
|
|
48
|
+
}
|
|
49
|
+
async function generateX402PrivateKey() {
|
|
50
|
+
const { generatePrivateKey } = await import("viem/accounts");
|
|
51
|
+
return generatePrivateKey();
|
|
52
|
+
}
|
|
53
|
+
function invalidX402PrivateKey() {
|
|
54
|
+
return new DvmError(
|
|
55
|
+
"invalid_key",
|
|
56
|
+
"Invalid secp256k1 private key.",
|
|
57
|
+
"Reconnect with `dvm wallet x402-connect --key-file <path>`, set DVM_X402_KEY, or pipe a valid 0x-prefixed 32-byte secp256k1 private key through stdin."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
async function getX402Balance(wallet) {
|
|
61
|
+
const w = await ensureAddress(wallet);
|
|
62
|
+
const spec = x402NetworkByCaip2(w.network);
|
|
63
|
+
if (!spec) {
|
|
64
|
+
throw new DvmError(
|
|
65
|
+
"x402_unsupported_network",
|
|
66
|
+
`The x402 wallet is configured for ${w.network}, a chain this rail can't settle on.`,
|
|
67
|
+
`${x402SupportedNetworksHint()} Re-pin the connected wallet without handling its key: 'dvm wallet x402-network ${X402_DEFAULT_NETWORK}'.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const chains = await import("viem/chains");
|
|
71
|
+
const { createPublicClient, http, erc20Abi } = await import("viem");
|
|
72
|
+
const chain = chains[spec.viemChain];
|
|
73
|
+
const { url: rpcUrl, source: rpcSource } = resolveRpcOverride(
|
|
74
|
+
process.env.DVM_X402_RPC_URL,
|
|
75
|
+
chain?.rpcUrls.default.http[0]
|
|
76
|
+
);
|
|
77
|
+
try {
|
|
78
|
+
const transportOptions = resolveRpcHttpTransportOptions(
|
|
79
|
+
rpcSource,
|
|
80
|
+
process.env.DVM_X402_RPC_HEADERS_JSON
|
|
81
|
+
);
|
|
82
|
+
const client = createPublicClient({
|
|
83
|
+
chain,
|
|
84
|
+
transport: transportOptions ? http(rpcUrl, transportOptions) : http(rpcUrl)
|
|
85
|
+
});
|
|
86
|
+
const balance = await client.readContract({
|
|
87
|
+
address: spec.usdc,
|
|
88
|
+
abi: erc20Abi,
|
|
89
|
+
functionName: "balanceOf",
|
|
90
|
+
args: [w.address]
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
address: w.address,
|
|
94
|
+
usdc: Number(balance) / 1e6,
|
|
95
|
+
network: w.network,
|
|
96
|
+
tokenAddress: spec.usdc,
|
|
97
|
+
// redacted here rather than at a display surface, so the clear
|
|
98
|
+
// endpoint never leaves this function on the success path either.
|
|
99
|
+
rpcUrlRedacted: rpcUrl ? redactUrl(rpcUrl) : void 0,
|
|
100
|
+
rpcSource
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const cause = redactUrlsInText(err instanceof Error ? err.message : String(err));
|
|
104
|
+
throw new DvmError(
|
|
105
|
+
"x402_rpc_error",
|
|
106
|
+
`x402 USDC balance lookup on ${spec.label} failed: ${cause}`,
|
|
107
|
+
// A caller who set the override doesn't need to be told to set it. Which
|
|
108
|
+
// knob is theirs to turn depends on whose endpoint this was.
|
|
109
|
+
rpcSource === "override" ? `DVM_X402_RPC_URL sent this read to ${redactUrl(rpcUrl)}. Correct it, or unset it to fall back to ${spec.label}'s bundled endpoint.` : `Verify connectivity to ${spec.label}'s bundled endpoint${rpcUrl ? ` (${redactUrl(rpcUrl)})` : ""}, or send the read somewhere else with DVM_X402_RPC_URL.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
resolveX402Asset,
|
|
116
|
+
isValidEvmPrivateKey,
|
|
117
|
+
ensureAddress,
|
|
118
|
+
generateX402PrivateKey,
|
|
119
|
+
getX402Balance
|
|
120
|
+
};
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
signedRequestHeaderMatchesDomain,
|
|
5
5
|
signedRequestStatementHeader,
|
|
6
6
|
testOnlySignedRequestDomain
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UKD3R2KU.js";
|
|
8
8
|
import {
|
|
9
9
|
diagnosticConsole
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZCNPIILZ.js";
|
|
11
11
|
|
|
12
12
|
// src/sdk/server/zod-error.ts
|
|
13
13
|
function humanizeZodError(err, opts) {
|