@dvmkit/sdk 0.1.4-rc.7 → 0.1.5-rc.7
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 +10 -2
- package/dist/{chunk-ANFX5HEG.js → chunk-2UUXIIOC.js} +43 -13
- package/dist/{chunk-C6JHBLMW.js → chunk-BIP6G74V.js} +1 -1
- package/dist/{chunk-EDDYHZ6W.js → chunk-CEOAHV2I.js} +0 -30
- package/dist/{chunk-TSWKITGR.js → chunk-CGKZDODG.js} +8 -1
- package/dist/{chunk-DBCLBYHP.js → chunk-KVEHHC7W.js} +11 -1
- package/dist/{chunk-VRQDX5P4.js → chunk-KXCKFIJI.js} +30 -0
- package/dist/{chunk-QK3VJNCK.js → chunk-NJFOV36R.js} +339 -212
- package/dist/{chunk-5GFED3GJ.js → chunk-NPIW5VR5.js} +40 -6
- package/dist/{chunk-7AKBC4PW.js → chunk-TQWGQCNV.js} +1 -1
- package/dist/{credit-menu-C1ezIFlJ.d.ts → credit-menu-B-e3vZGo.d.ts} +64 -21
- package/dist/{fx-C6dl2LVI.d.ts → fx-CGRJE8rm.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/internal/caller.d.ts +31 -11
- package/dist/internal/caller.js +23 -11
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +8 -8
- package/dist/internal/server.js +4 -4
- package/dist/{job-store-DHnW4Cg_.d.ts → job-store-Cnlv9pOx.d.ts} +1 -1
- package/dist/{lightning-backend-Ci1nogk_.d.ts → lightning-backend-KvQM0YHi.d.ts} +1 -1
- package/dist/{memory-credit-ledger-OP24Z2KO.js → memory-credit-ledger-MNUOTQO5.js} +1 -1
- package/dist/{mpp-setup-MOBWGTWJ.js → mpp-setup-SPBOF5AM.js} +11 -1
- package/dist/server/index.d.ts +12 -13
- package/dist/server/index.js +215 -51
- package/dist/{step-cache-BLPZNizw.d.ts → step-cache-CwM_Q8rK.d.ts} +1 -0
- package/dist/{tempo-lifecycle-SQL3KLEZ.js → tempo-lifecycle-DFIXQ54Q.js} +3 -3
- package/dist/{tempo-wallet-QOLEIPCV.js → tempo-wallet-4QKSV65O.js} +10 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/{usd-BgOfZlk6.d.ts → usd-nYZSb7KQ.d.ts} +1 -1
- package/dist/{x402-T2C5MX3T.js → x402-5H27DCBE.js} +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ import dvm from "./handler.mjs";
|
|
|
40
40
|
await serve(dvm, { devMode: true });
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Submit a local request. `devMode` makes Postgres optional and skips payment verification when no Cashu
|
|
43
|
+
Submit a local request. `devMode` makes Postgres optional and skips payment verification only when no Cashu, x402, or Tempo rail is configured:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
46
|
curl -sS http://localhost:8080/v1/job \
|
|
@@ -76,6 +76,14 @@ Run that file with `node --test handler.test.mjs`. The repository carries the co
|
|
|
76
76
|
|
|
77
77
|
The generated [API reference](./docs/API.md) lists every declaration under these three entries. Deep imports from `dist/` are unsupported.
|
|
78
78
|
|
|
79
|
+
## Verified payments in development
|
|
80
|
+
|
|
81
|
+
`serve()` returns a `paymentModes` snapshot describing whether each configured rail is verifying, unavailable, or absent. A priced development server skips verification only when it has no configured payment rail. Configuring an unusable rail fails closed instead of turning the request into a synthetic development payment.
|
|
82
|
+
|
|
83
|
+
Cashu development without Postgres is limited to known test or loopback mints. It uses a fresh lock identity and process-local accounting on every boot, so proofs, balances, and jobs do not survive restart. Production Cashu remains PostgreSQL-backed. Both paths require valid mint-provided NUT-12 DLEQ evidence on every proof before accepting its value; older tokens without that evidence must be replaced with fresh tokens from a compatible mint.
|
|
84
|
+
|
|
85
|
+
Builders can pass `onPaidJobCompleted` to observe settled customer charges after successful completion. The event contains `jobId`, verified rail, checked integer `amountMicro`, the credit currency, and whether configuration identifies test funds. Reconciliation can repeat an event, so consumers deduplicate by `jobId`. Free jobs, synthetic development payments, failed jobs, and released draws do not emit.
|
|
86
|
+
|
|
79
87
|
## Production next steps
|
|
80
88
|
|
|
81
89
|
- Use Postgres through `DATABASE_URL`; production boot refuses an in-memory job store unless you deliberately supply another durable implementation.
|
|
@@ -88,7 +96,7 @@ The public [SDK guide](https://dvmkit.com/docs/sdk) covers signed requests, cust
|
|
|
88
96
|
|
|
89
97
|
## Repository development
|
|
90
98
|
|
|
91
|
-
Use the Node.js release in `.nvmrc` (Node 22). Install with `npm ci`, then run the same gate CI runs with `npm run verify
|
|
99
|
+
Use the Node.js release in `.nvmrc` (Node 22). Install with `npm ci`, then run the same gate CI runs with `npm run verify`. The gate checks repository metadata in the checkout, copies only tracked and unignored source into an isolated directory, installs its locked dependencies, and runs lint, build, typecheck (including the release scripts under `checkJs`), unit and database tests, API, package, consumer, packed-artifact safety, and release checks there. The clean source tree owns the complete build and test pass, so verification never depends on the checkout's `node_modules` or pre-existing `dist` and does not repeat the full suite; after every check passes, its verified `dist` replaces the checkout's build output for release packaging.
|
|
92
100
|
|
|
93
101
|
`DATABASE_URL` is optional locally. Without it the database suite is skipped rather than passed, and `verify` says so in its summary, because a skipped suite verified nothing and must not be reported as green. Point `DATABASE_URL` at a scratch Postgres to run it, and pass `npm run verify -- --require-database` to make a missing one a failure, which is what CI does.
|
|
94
102
|
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
isValidEvmPrivateKey,
|
|
3
3
|
loadConfig,
|
|
4
4
|
updateConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CGKZDODG.js";
|
|
6
6
|
import {
|
|
7
7
|
DvmError,
|
|
8
8
|
resolveRpcHttpTransportOptions,
|
|
@@ -16,8 +16,24 @@ import {
|
|
|
16
16
|
// src/lib/payment-rails/tempo-wallet.ts
|
|
17
17
|
var TEMPO_USDC_MAINNET = "0x20C000000000000000000000b9537d11c60E8b50";
|
|
18
18
|
var TEMPO_CHAIN_ID = 4217;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
var TEMPO_MODERATO_CHAIN_ID = 42431;
|
|
20
|
+
var TEMPO_PATH_USD_MODERATO = "0x20c0000000000000000000000000000000000000";
|
|
21
|
+
var TEMPO_MODERATO_RPC = "https://rpc.moderato.tempo.xyz";
|
|
22
|
+
function resolveTempoNetwork(network = "mainnet") {
|
|
23
|
+
return network === "moderato" ? {
|
|
24
|
+
network,
|
|
25
|
+
chainId: TEMPO_MODERATO_CHAIN_ID,
|
|
26
|
+
rpcUrl: TEMPO_MODERATO_RPC,
|
|
27
|
+
defaultToken: TEMPO_PATH_USD_MODERATO
|
|
28
|
+
} : {
|
|
29
|
+
network,
|
|
30
|
+
chainId: TEMPO_CHAIN_ID,
|
|
31
|
+
rpcUrl: "https://rpc.tempo.xyz",
|
|
32
|
+
defaultToken: TEMPO_USDC_MAINNET
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function resolveTempoAsset(env = process.env, network = "mainnet") {
|
|
36
|
+
const address = env.DVM_TEMPO_CURRENCY ?? resolveTempoNetwork(network).defaultToken;
|
|
21
37
|
if (!HEX_ADDRESS_RE.test(address)) {
|
|
22
38
|
throw new DvmError(
|
|
23
39
|
"tempo_config_invalid",
|
|
@@ -58,7 +74,9 @@ function tempoWrongAssetHint(asset) {
|
|
|
58
74
|
return `This rail settles only in ${asset.label} (${asset.address}) on Tempo. Another Tempo dollar \u2014 USDT0 or any other stablecoin \u2014 is a different contract and will never show up here, however much of it your wallet says you hold. If you funded with the wrong one, swap it to ${asset.label} on Tempo (an on-chain swap, fees are sub-cent) or send ${asset.label} to this address.`;
|
|
59
75
|
}
|
|
60
76
|
function tempoTokenSymbol(address) {
|
|
61
|
-
|
|
77
|
+
if (address.toLowerCase() === TEMPO_USDC_MAINNET.toLowerCase()) return "USDC.e";
|
|
78
|
+
if (address.toLowerCase() === TEMPO_PATH_USD_MODERATO.toLowerCase()) return "pathUSD";
|
|
79
|
+
return null;
|
|
62
80
|
}
|
|
63
81
|
var HEX_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
64
82
|
async function connectedTempoAccount() {
|
|
@@ -83,7 +101,7 @@ async function generateTempoKey(opts = {}) {
|
|
|
83
101
|
);
|
|
84
102
|
}
|
|
85
103
|
if (existing && opts.force) {
|
|
86
|
-
const { assertTempoWalletIdle } = await import("./tempo-lifecycle-
|
|
104
|
+
const { assertTempoWalletIdle } = await import("./tempo-lifecycle-DFIXQ54Q.js");
|
|
87
105
|
await assertTempoWalletIdle("replace");
|
|
88
106
|
}
|
|
89
107
|
const { privateKeyToAccount, generatePrivateKey } = await import("viem/accounts");
|
|
@@ -91,22 +109,30 @@ async function generateTempoKey(opts = {}) {
|
|
|
91
109
|
const account = privateKeyToAccount(privateKey);
|
|
92
110
|
const generated = {
|
|
93
111
|
privateKey,
|
|
94
|
-
address: account.address.toLowerCase()
|
|
112
|
+
address: account.address.toLowerCase(),
|
|
113
|
+
network: opts.network ?? "mainnet"
|
|
95
114
|
};
|
|
96
115
|
await opts.beforePersist?.(generated);
|
|
97
116
|
await updateConfig({
|
|
98
117
|
reason: "tempo_wallet_generate",
|
|
99
|
-
patch: () => ({
|
|
118
|
+
patch: () => ({
|
|
119
|
+
set: {
|
|
120
|
+
tempo: { method: "tempo", apiKey: privateKey, network: generated.network }
|
|
121
|
+
}
|
|
122
|
+
})
|
|
100
123
|
});
|
|
101
124
|
return generated;
|
|
102
125
|
}
|
|
103
126
|
async function getTempoUsdcBalance(account) {
|
|
104
|
-
const
|
|
127
|
+
const accountNetwork = account.network ?? "mainnet";
|
|
128
|
+
const network = resolveTempoNetwork(accountNetwork);
|
|
129
|
+
const tokenAddress = resolveTempoAsset(process.env, accountNetwork).address;
|
|
105
130
|
const { createPublicClient, http, erc20Abi } = await import("viem");
|
|
106
|
-
const { tempo } = await import("viem/chains");
|
|
131
|
+
const { tempo, tempoModerato } = await import("viem/chains");
|
|
132
|
+
const chain = accountNetwork === "moderato" ? tempoModerato : tempo;
|
|
107
133
|
const { url: rpcUrl, source: rpcSource } = resolveRpcOverride(
|
|
108
134
|
process.env.DVM_TEMPO_RPC_URL,
|
|
109
|
-
|
|
135
|
+
network.rpcUrl
|
|
110
136
|
);
|
|
111
137
|
try {
|
|
112
138
|
const transportOptions = resolveRpcHttpTransportOptions(
|
|
@@ -114,7 +140,7 @@ async function getTempoUsdcBalance(account) {
|
|
|
114
140
|
process.env.DVM_TEMPO_RPC_HEADERS_JSON
|
|
115
141
|
);
|
|
116
142
|
const client = createPublicClient({
|
|
117
|
-
chain
|
|
143
|
+
chain,
|
|
118
144
|
transport: transportOptions ? http(rpcUrl, transportOptions) : http(rpcUrl)
|
|
119
145
|
});
|
|
120
146
|
const raw = await client.readContract({
|
|
@@ -128,7 +154,7 @@ async function getTempoUsdcBalance(account) {
|
|
|
128
154
|
rawBaseUnits: raw,
|
|
129
155
|
usdc: Number(raw) / 1e6,
|
|
130
156
|
tokenAddress,
|
|
131
|
-
chainId:
|
|
157
|
+
chainId: network.chainId,
|
|
132
158
|
// internal-review: redacted here rather than at a display surface, so the clear
|
|
133
159
|
// endpoint never leaves this function on the success path either.
|
|
134
160
|
rpcUrlRedacted: redactUrl(rpcUrl),
|
|
@@ -160,7 +186,7 @@ async function tempoAccountFromConfig(config) {
|
|
|
160
186
|
if (!apiKey || !isValidEvmPrivateKey(apiKey)) return null;
|
|
161
187
|
const privateKey = apiKey;
|
|
162
188
|
const address = await deriveTempoAddress(privateKey);
|
|
163
|
-
return { privateKey, address };
|
|
189
|
+
return { privateKey, address, network: config.tempo.network ?? "mainnet" };
|
|
164
190
|
}
|
|
165
191
|
function invalidTempoPrivateKey() {
|
|
166
192
|
return new DvmError(
|
|
@@ -173,6 +199,10 @@ function invalidTempoPrivateKey() {
|
|
|
173
199
|
export {
|
|
174
200
|
TEMPO_USDC_MAINNET,
|
|
175
201
|
TEMPO_CHAIN_ID,
|
|
202
|
+
TEMPO_MODERATO_CHAIN_ID,
|
|
203
|
+
TEMPO_PATH_USD_MODERATO,
|
|
204
|
+
TEMPO_MODERATO_RPC,
|
|
205
|
+
resolveTempoNetwork,
|
|
176
206
|
resolveTempoAsset,
|
|
177
207
|
tempoTokenLabel,
|
|
178
208
|
tempoTokenReference,
|
|
@@ -2,34 +2,6 @@ import {
|
|
|
2
2
|
callerLoggers
|
|
3
3
|
} from "./chunk-66HGCPBU.js";
|
|
4
4
|
|
|
5
|
-
// src/lib/mints.ts
|
|
6
|
-
var RECOMMENDED_MINTS = [
|
|
7
|
-
{
|
|
8
|
-
name: "Coinos",
|
|
9
|
-
description: "Primary Cashu mint accepted by dvmkit DVMs",
|
|
10
|
-
url: "https://mint.coinos.io"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
name: "Voltz Mint",
|
|
14
|
-
description: "Fallback Cashu mint accepted by dvmkit DVMs",
|
|
15
|
-
url: "https://mint.lnvoltz.com"
|
|
16
|
-
}
|
|
17
|
-
];
|
|
18
|
-
function isTestMintUrl(url) {
|
|
19
|
-
let host;
|
|
20
|
-
try {
|
|
21
|
-
host = new URL(url).hostname.toLowerCase();
|
|
22
|
-
} catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
|
|
26
|
-
if (LOOPBACK_HOSTS.has(host)) return true;
|
|
27
|
-
if (host.endsWith(".localhost")) return true;
|
|
28
|
-
return TEST_MINT_HOSTS.has(host);
|
|
29
|
-
}
|
|
30
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]);
|
|
31
|
-
var TEST_MINT_HOSTS = /* @__PURE__ */ new Set(["testnut.cashu.space"]);
|
|
32
|
-
|
|
33
5
|
// src/lib/x402/env.ts
|
|
34
6
|
import { isAddress } from "viem";
|
|
35
7
|
import { privateKeyToAccount } from "viem/accounts";
|
|
@@ -981,8 +953,6 @@ var NwcBackend = class {
|
|
|
981
953
|
};
|
|
982
954
|
|
|
983
955
|
export {
|
|
984
|
-
RECOMMENDED_MINTS,
|
|
985
|
-
isTestMintUrl,
|
|
986
956
|
X402_SELF_RELAY_KEY,
|
|
987
957
|
X402_RECEIVER_AUTHORIZER_KEY,
|
|
988
958
|
X402_BATCH_SETTLEMENT_KEY,
|
|
@@ -181,6 +181,13 @@ function assertValidConfig(config, fields) {
|
|
|
181
181
|
const value = owner[leaf];
|
|
182
182
|
if (value === void 0 || !matchesKind(value, kind)) throw invalidFieldError(path, kind);
|
|
183
183
|
}
|
|
184
|
+
if ((!scope || scope.has("tempo")) && isPlainObject(record.tempo) && record.tempo.network !== void 0 && record.tempo.network !== "mainnet" && record.tempo.network !== "moderato") {
|
|
185
|
+
throw new DvmError(
|
|
186
|
+
"config_invalid",
|
|
187
|
+
`The caller configuration field 'tempo.network' must be 'mainnet' or 'moderato'.`,
|
|
188
|
+
`Correct tempo.network in ${CONFIG_FILE}.${rollbackSuffix()}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
184
191
|
}
|
|
185
192
|
function readRawConfig() {
|
|
186
193
|
if (!existsSync(CONFIG_FILE)) return null;
|
|
@@ -308,7 +315,7 @@ var PROTECTED_FIELDS = {
|
|
|
308
315
|
var CONFIG_PATH_SHAPE = {
|
|
309
316
|
float: { statedBudget: {}, connectedAt: {}, alias: {}, methods: {} },
|
|
310
317
|
x402: { privateKey: {}, network: {} },
|
|
311
|
-
tempo: { method: {}, accountId: {}, apiKey: {} },
|
|
318
|
+
tempo: { method: {}, accountId: {}, apiKey: {}, network: {} },
|
|
312
319
|
credit: {
|
|
313
320
|
targetJobs: {},
|
|
314
321
|
posture: {},
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
|
|
20
20
|
// src/sdk/server/memory-credit-ledger.ts
|
|
21
21
|
import { randomUUID } from "crypto";
|
|
22
|
-
var MemoryCreditLedger = class {
|
|
22
|
+
var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
23
23
|
/**
|
|
24
24
|
* Per-process and lost on restart, so a DVM backed by this ledger never
|
|
25
25
|
* advertises the funding menu outside devMode (internal-review) — funding on one
|
|
@@ -32,6 +32,16 @@ var MemoryCreditLedger = class {
|
|
|
32
32
|
tempoLosses = /* @__PURE__ */ new Map();
|
|
33
33
|
x402Losses = /* @__PURE__ */ new Map();
|
|
34
34
|
x402Settlements;
|
|
35
|
+
/** Internal disposable transaction snapshot. The caller must serialize every ledger access. */
|
|
36
|
+
forkForTransaction() {
|
|
37
|
+
const fork = new _MemoryCreditLedger();
|
|
38
|
+
for (const name of ["credits", "fundings", "invoices", "tempoLosses", "x402Losses"]) {
|
|
39
|
+
const target = fork[name];
|
|
40
|
+
for (const [key, value] of this[name]) target.set(key, structuredClone(value));
|
|
41
|
+
}
|
|
42
|
+
fork.x402Settlements = this.x402Settlements;
|
|
43
|
+
return fork;
|
|
44
|
+
}
|
|
35
45
|
/** See {@link CreditLedger.useX402SettlementGate} — same contract (internal-review). */
|
|
36
46
|
useX402SettlementGate(gate) {
|
|
37
47
|
this.x402Settlements = gate;
|
|
@@ -29,6 +29,34 @@ function isX402SettlementReconciliationReason(value) {
|
|
|
29
29
|
return value === "settlement_not_on_chain" || value === "chain_unreachable" || value === "settlement_unbookmarked" || value === "facilitator_channel_state_unavailable";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// src/lib/mints.ts
|
|
33
|
+
var RECOMMENDED_MINTS = [
|
|
34
|
+
{
|
|
35
|
+
name: "Coinos",
|
|
36
|
+
description: "Primary Cashu mint accepted by dvmkit DVMs",
|
|
37
|
+
url: "https://mint.coinos.io"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "Voltz Mint",
|
|
41
|
+
description: "Fallback Cashu mint accepted by dvmkit DVMs",
|
|
42
|
+
url: "https://mint.lnvoltz.com"
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
function isTestMintUrl(url) {
|
|
46
|
+
let host;
|
|
47
|
+
try {
|
|
48
|
+
host = new URL(url).hostname.toLowerCase();
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
|
|
53
|
+
if (LOOPBACK_HOSTS.has(host)) return true;
|
|
54
|
+
if (host.endsWith(".localhost")) return true;
|
|
55
|
+
return TEST_MINT_HOSTS.has(host);
|
|
56
|
+
}
|
|
57
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]);
|
|
58
|
+
var TEST_MINT_HOSTS = /* @__PURE__ */ new Set(["testnut.cashu.space", "dvmkit-testmint.fly.dev"]);
|
|
59
|
+
|
|
32
60
|
// src/lib/secret-backup.ts
|
|
33
61
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
34
62
|
function backupSecretFile(file, nextContent) {
|
|
@@ -1666,6 +1694,8 @@ export {
|
|
|
1666
1694
|
verifyCashuToken,
|
|
1667
1695
|
normaliseMintUrl,
|
|
1668
1696
|
TEMPO_SESSION_UPDATED_REASON,
|
|
1697
|
+
RECOMMENDED_MINTS,
|
|
1698
|
+
isTestMintUrl,
|
|
1669
1699
|
backupSecretFile,
|
|
1670
1700
|
readAgentMnemonic,
|
|
1671
1701
|
assertAgentMnemonicReplaceable,
|