@jaw.id/cli 0.1.26 → 0.2.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/base-command.js +3 -1
- package/dist/base-command.js.map +1 -1
- package/dist/commands/config/set.js +140 -12
- package/dist/commands/config/set.js.map +1 -1
- package/dist/commands/config/show.js +3 -1
- package/dist/commands/config/show.js.map +1 -1
- package/dist/commands/config/write.js +6 -4
- package/dist/commands/config/write.js.map +1 -1
- package/dist/commands/disconnect.js +24 -10
- package/dist/commands/disconnect.js.map +1 -1
- package/dist/commands/mcp/index.js +2426 -94
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/rpc/call.js +197 -45
- package/dist/commands/rpc/call.js.map +1 -1
- package/dist/commands/session/add.js +1547 -0
- package/dist/commands/session/add.js.map +1 -0
- package/dist/commands/session/revoke.js +181 -54
- package/dist/commands/session/revoke.js.map +1 -1
- package/dist/commands/session/setup.js +516 -65
- package/dist/commands/session/setup.js.map +1 -1
- package/dist/commands/session/status.js +315 -6
- package/dist/commands/session/status.js.map +1 -1
- package/dist/commands/version.js +3 -1
- package/dist/commands/version.js.map +1 -1
- package/dist/commands/x402/log.js +344 -0
- package/dist/commands/x402/log.js.map +1 -0
- package/dist/commands/x402/pay.js +2122 -0
- package/dist/commands/x402/pay.js.map +1 -0
- package/dist/commands/x402/status.js +1047 -0
- package/dist/commands/x402/status.js.map +1 -0
- package/dist/index.js +41 -14
- package/dist/index.js.map +1 -1
- package/dist/lib/bridge-singleton.js +41 -14
- package/dist/lib/bridge-singleton.js.map +1 -1
- package/dist/lib/config.js +26 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/keystore.js +13 -2
- package/dist/lib/keystore.js.map +1 -1
- package/dist/lib/paths.js +3 -1
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/payment-lock.js +121 -0
- package/dist/lib/payment-lock.js.map +1 -0
- package/dist/lib/session-bridge.js +148 -24
- package/dist/lib/session-bridge.js.map +1 -1
- package/dist/lib/session-config.js +78 -11
- package/dist/lib/session-config.js.map +1 -1
- package/dist/lib/terminal.js +22 -0
- package/dist/lib/terminal.js.map +1 -0
- package/dist/lib/validation.js +3 -3
- package/dist/lib/validation.js.map +1 -1
- package/dist/lib/ws-bridge.js +22 -10
- package/dist/lib/ws-bridge.js.map +1 -1
- package/dist/mcp/handlers/config.js +73 -6
- package/dist/mcp/handlers/config.js.map +1 -1
- package/dist/mcp/handlers/daemon.js +43 -12
- package/dist/mcp/handlers/daemon.js.map +1 -1
- package/dist/mcp/handlers/resources.js +119 -0
- package/dist/mcp/handlers/resources.js.map +1 -1
- package/dist/mcp/handlers/rpc.js +269 -60
- package/dist/mcp/handlers/rpc.js.map +1 -1
- package/dist/mcp/helpers.js +50 -3
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/server.js +2426 -94
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +43 -3
- package/dist/mcp/tools.js.map +1 -1
- package/dist/x402/log-view.js +160 -0
- package/dist/x402/log-view.js.map +1 -0
- package/dist/x402/status-report.js +90 -0
- package/dist/x402/status-report.js.map +1 -0
- package/oclif.manifest.json +405 -11
- package/package.json +5 -2
|
@@ -3,6 +3,8 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import 'crypto';
|
|
6
|
+
import { parseAbi, zeroAddress, createPublicClient, http } from 'viem';
|
|
7
|
+
import { polygonAmoy, polygon, baseSepolia, base } from 'viem/chains';
|
|
6
8
|
|
|
7
9
|
// src/base-command.ts
|
|
8
10
|
var JAW_DIR = path.join(os.homedir(), ".jaw");
|
|
@@ -12,7 +14,9 @@ var PATHS = {
|
|
|
12
14
|
session: path.join(JAW_DIR, "session.json"),
|
|
13
15
|
relay: path.join(JAW_DIR, "relay.json"),
|
|
14
16
|
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
15
|
-
sessionConfig: path.join(JAW_DIR, "session-config.json")
|
|
17
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json"),
|
|
18
|
+
x402Log: path.join(JAW_DIR, "x402-log.jsonl"),
|
|
19
|
+
paymentLock: path.join(JAW_DIR, "x402-payment.lock")
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
// src/lib/config.ts
|
|
@@ -141,6 +145,63 @@ var BaseCommand = class extends Command {
|
|
|
141
145
|
function keystoreExists() {
|
|
142
146
|
return fs.existsSync(PATHS.keystore);
|
|
143
147
|
}
|
|
148
|
+
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
149
|
+
var SELECTOR_RE = /^0x[0-9a-fA-F]{8}$/;
|
|
150
|
+
var HEX_RE = /^0x[0-9a-fA-F]+$/;
|
|
151
|
+
var ALLOWANCE_RE = /^(0x[0-9a-fA-F]+|[0-9]+)$/;
|
|
152
|
+
var SPEND_UNITS = /* @__PURE__ */ new Set(["minute", "hour", "day", "week", "month", "year", "forever"]);
|
|
153
|
+
function isPositiveInt(value) {
|
|
154
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
155
|
+
}
|
|
156
|
+
function parseGrantedPermission(raw) {
|
|
157
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
158
|
+
const r = raw;
|
|
159
|
+
const { account, spender, salt } = r;
|
|
160
|
+
if (typeof account !== "string" || !ADDRESS_RE.test(account)) return void 0;
|
|
161
|
+
if (typeof spender !== "string" || !ADDRESS_RE.test(spender)) return void 0;
|
|
162
|
+
if (typeof salt !== "string" || !HEX_RE.test(salt)) return void 0;
|
|
163
|
+
if (!isPositiveInt(r.start) || !isPositiveInt(r.end)) return void 0;
|
|
164
|
+
if (!Array.isArray(r.calls) || r.calls.length === 0) return void 0;
|
|
165
|
+
const calls = [];
|
|
166
|
+
for (const entry of r.calls) {
|
|
167
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
168
|
+
const { target, selector } = entry;
|
|
169
|
+
if (typeof target !== "string" || !ADDRESS_RE.test(target)) return void 0;
|
|
170
|
+
if (typeof selector !== "string" || !SELECTOR_RE.test(selector)) return void 0;
|
|
171
|
+
calls.push({ target, selector });
|
|
172
|
+
}
|
|
173
|
+
if (!Array.isArray(r.spends)) return void 0;
|
|
174
|
+
const spends = [];
|
|
175
|
+
for (const entry of r.spends) {
|
|
176
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
177
|
+
const { token, allowance, unit, multiplier } = entry;
|
|
178
|
+
if (typeof token !== "string" || !ADDRESS_RE.test(token)) return void 0;
|
|
179
|
+
if (typeof allowance !== "string" || !ALLOWANCE_RE.test(allowance)) return void 0;
|
|
180
|
+
if (typeof unit !== "string" || !SPEND_UNITS.has(unit)) return void 0;
|
|
181
|
+
if (!isPositiveInt(multiplier) || multiplier > 65535) return void 0;
|
|
182
|
+
spends.push({ token, allowance, unit, multiplier });
|
|
183
|
+
}
|
|
184
|
+
return { account, spender, start: r.start, end: r.end, salt, calls, spends };
|
|
185
|
+
}
|
|
186
|
+
function isLegacySession(config) {
|
|
187
|
+
return config.mode !== "eip7702";
|
|
188
|
+
}
|
|
189
|
+
function liveOrphans(orphans, now = Date.now() / 1e3) {
|
|
190
|
+
return (orphans ?? []).filter((orphan) => orphan.expiry > now);
|
|
191
|
+
}
|
|
192
|
+
function writeSessionConfig(config) {
|
|
193
|
+
ensureDir(PATHS.root);
|
|
194
|
+
const temp = `${PATHS.sessionConfig}.${process.pid}.tmp`;
|
|
195
|
+
fs.writeFileSync(temp, JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
196
|
+
fs.chmodSync(temp, 384);
|
|
197
|
+
fs.renameSync(temp, PATHS.sessionConfig);
|
|
198
|
+
}
|
|
199
|
+
function saveRecoveredPermission(config, permission) {
|
|
200
|
+
const current = tryLoadSessionConfig();
|
|
201
|
+
if (!current || current.permissionId !== config.permissionId) return false;
|
|
202
|
+
writeSessionConfig({ ...current, permission });
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
144
205
|
function loadSessionConfig() {
|
|
145
206
|
if (!fs.existsSync(PATHS.sessionConfig)) {
|
|
146
207
|
throw new Error("No session configured. Run `jaw session setup` first.");
|
|
@@ -152,6 +213,230 @@ function loadSessionConfig() {
|
|
|
152
213
|
throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \`jaw session setup\` to recreate it.`);
|
|
153
214
|
}
|
|
154
215
|
}
|
|
216
|
+
function tryLoadSessionConfig() {
|
|
217
|
+
try {
|
|
218
|
+
return loadSessionConfig();
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/x402/asset-registry.ts
|
|
225
|
+
var USDC_BY_NETWORK = {
|
|
226
|
+
"eip155:8453": {
|
|
227
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
228
|
+
chainId: 8453,
|
|
229
|
+
wireNetwork: "eip155:8453",
|
|
230
|
+
usdcName: "USD Coin",
|
|
231
|
+
usdcVersion: "2",
|
|
232
|
+
decimals: 6
|
|
233
|
+
},
|
|
234
|
+
"eip155:84532": {
|
|
235
|
+
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
236
|
+
chainId: 84532,
|
|
237
|
+
wireNetwork: "eip155:84532",
|
|
238
|
+
usdcName: "USDC",
|
|
239
|
+
usdcVersion: "2",
|
|
240
|
+
decimals: 6
|
|
241
|
+
},
|
|
242
|
+
"eip155:137": {
|
|
243
|
+
address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
244
|
+
chainId: 137,
|
|
245
|
+
wireNetwork: "eip155:137",
|
|
246
|
+
usdcName: "USD Coin",
|
|
247
|
+
usdcVersion: "2",
|
|
248
|
+
decimals: 6
|
|
249
|
+
},
|
|
250
|
+
"eip155:80002": {
|
|
251
|
+
address: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
|
|
252
|
+
chainId: 80002,
|
|
253
|
+
wireNetwork: "eip155:80002",
|
|
254
|
+
usdcName: "USDC",
|
|
255
|
+
usdcVersion: "2",
|
|
256
|
+
decimals: 6
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// src/x402/balance.ts
|
|
261
|
+
var JAW_RPC_URL = "https://api.justaname.id/proxy/v1/rpc";
|
|
262
|
+
var CHAINS = {
|
|
263
|
+
[base.id]: base,
|
|
264
|
+
[baseSepolia.id]: baseSepolia,
|
|
265
|
+
[polygon.id]: polygon,
|
|
266
|
+
[polygonAmoy.id]: polygonAmoy
|
|
267
|
+
};
|
|
268
|
+
for (const chainId of Object.values(USDC_BY_NETWORK).map((a) => a.chainId)) {
|
|
269
|
+
if (!CHAINS[chainId]) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`x402 balance: USDC registry has chain ${chainId} but no viem chain is mapped for it in balance.ts`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
var clients = /* @__PURE__ */ new Map();
|
|
276
|
+
function rpcTransport(chainId, apiKey) {
|
|
277
|
+
if (!apiKey) return http();
|
|
278
|
+
return http(`${JAW_RPC_URL}?chainId=${chainId}&api-key=${apiKey}`);
|
|
279
|
+
}
|
|
280
|
+
function publicClientFor(chainId) {
|
|
281
|
+
const chain = CHAINS[chainId];
|
|
282
|
+
if (!chain) throw new Error(`x402: no viem chain configured for chainId ${chainId}`);
|
|
283
|
+
const apiKey = loadConfig().apiKey;
|
|
284
|
+
const key = `${chainId}:${apiKey ?? ""}`;
|
|
285
|
+
let client = clients.get(key);
|
|
286
|
+
if (!client) {
|
|
287
|
+
client = createPublicClient({ chain, transport: rpcTransport(chainId, apiKey) });
|
|
288
|
+
clients.set(key, client);
|
|
289
|
+
}
|
|
290
|
+
return client;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/x402/permission-onchain.ts
|
|
294
|
+
var PERMISSION_MANAGER_ABI = parseAbi([
|
|
295
|
+
"struct CallPermission { address target; bytes4 selector; address checker; }",
|
|
296
|
+
"struct SpendLimit { address token; uint160 allowance; uint8 unit; uint16 multiplier; }",
|
|
297
|
+
"struct Permission { address account; address spender; uint48 start; uint48 end; uint256 salt; CallPermission[] calls; SpendLimit[] spends; }",
|
|
298
|
+
"struct PeriodSpend { uint48 start; uint48 end; uint160 spend; }",
|
|
299
|
+
"function getHash(Permission permission) view returns (bytes32)",
|
|
300
|
+
"function isApproved(Permission permission) view returns (bool)",
|
|
301
|
+
"function isRevoked(Permission permission) view returns (bool)",
|
|
302
|
+
"function getCurrentPeriod(Permission permission, SpendLimit spendLimit) view returns (PeriodSpend)",
|
|
303
|
+
// Carried so the two time-bound reverts can be told apart from a node that
|
|
304
|
+
// did not answer. Everything else the manager can revert with decodes to an
|
|
305
|
+
// unnamed error, which is treated as unavailable rather than guessed at.
|
|
306
|
+
"error JustaPermissionManager_BeforePermissionStart(uint48 currentTimestamp, uint48 start)",
|
|
307
|
+
"error JustaPermissionManager_AfterPermissionEnd(uint48 currentTimestamp, uint48 end)"
|
|
308
|
+
]);
|
|
309
|
+
var PERIOD_UNIT_ENUM = {
|
|
310
|
+
minute: 0,
|
|
311
|
+
hour: 1,
|
|
312
|
+
day: 2,
|
|
313
|
+
week: 3,
|
|
314
|
+
month: 4,
|
|
315
|
+
forever: 5
|
|
316
|
+
};
|
|
317
|
+
function toContractSpendLimit(spend) {
|
|
318
|
+
const unit = spend.unit === "year" ? "month" : spend.unit;
|
|
319
|
+
const multiplier = spend.unit === "year" ? spend.multiplier * 12 : spend.multiplier;
|
|
320
|
+
if (!Object.hasOwn(PERIOD_UNIT_ENUM, unit)) return null;
|
|
321
|
+
return {
|
|
322
|
+
token: spend.token,
|
|
323
|
+
allowance: BigInt(spend.allowance),
|
|
324
|
+
unit: PERIOD_UNIT_ENUM[unit],
|
|
325
|
+
multiplier
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function toContractPermission(permission) {
|
|
329
|
+
const spends = [];
|
|
330
|
+
for (const spend of permission.spends) {
|
|
331
|
+
const converted = toContractSpendLimit(spend);
|
|
332
|
+
if (!converted) return null;
|
|
333
|
+
spends.push(converted);
|
|
334
|
+
}
|
|
335
|
+
let salt;
|
|
336
|
+
try {
|
|
337
|
+
salt = BigInt(permission.salt);
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
account: permission.account,
|
|
343
|
+
spender: permission.spender,
|
|
344
|
+
start: permission.start,
|
|
345
|
+
end: permission.end,
|
|
346
|
+
salt,
|
|
347
|
+
calls: permission.calls.map((call) => ({
|
|
348
|
+
target: call.target,
|
|
349
|
+
selector: call.selector,
|
|
350
|
+
checker: zeroAddress
|
|
351
|
+
})),
|
|
352
|
+
spends
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
356
|
+
async function within(work, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
357
|
+
let timer;
|
|
358
|
+
try {
|
|
359
|
+
const expired = new Promise((_, reject) => {
|
|
360
|
+
timer = setTimeout(() => reject(new Error("timed out")), timeoutMs);
|
|
361
|
+
});
|
|
362
|
+
return await Promise.race([work, expired]);
|
|
363
|
+
} finally {
|
|
364
|
+
clearTimeout(timer);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function managerAddress(override) {
|
|
368
|
+
if (override) return override;
|
|
369
|
+
const { PERMISSIONS_MANAGER_ADDRESS } = await import('@jaw.id/core');
|
|
370
|
+
return PERMISSIONS_MANAGER_ADDRESS;
|
|
371
|
+
}
|
|
372
|
+
function reader(chainId, deps) {
|
|
373
|
+
if (deps.readContract) return deps.readContract;
|
|
374
|
+
try {
|
|
375
|
+
const client = publicClientFor(chainId);
|
|
376
|
+
return (args) => client.readContract(args);
|
|
377
|
+
} catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async function readPermissionState(target, deps = {}) {
|
|
382
|
+
if (!target.permission) return { status: "unavailable" };
|
|
383
|
+
const permission = toContractPermission(target.permission);
|
|
384
|
+
if (!permission) return { status: "unavailable" };
|
|
385
|
+
const read = reader(target.chainId, deps);
|
|
386
|
+
if (!read) return { status: "unavailable" };
|
|
387
|
+
try {
|
|
388
|
+
const address = await managerAddress(deps.manager);
|
|
389
|
+
const [hash, approved, revoked] = await within(
|
|
390
|
+
Promise.all([
|
|
391
|
+
read({ address, abi: PERMISSION_MANAGER_ABI, functionName: "getHash", args: [permission] }),
|
|
392
|
+
read({ address, abi: PERMISSION_MANAGER_ABI, functionName: "isApproved", args: [permission] }),
|
|
393
|
+
read({ address, abi: PERMISSION_MANAGER_ABI, functionName: "isRevoked", args: [permission] })
|
|
394
|
+
]),
|
|
395
|
+
deps.timeoutMs
|
|
396
|
+
);
|
|
397
|
+
if (typeof hash !== "string" || hash.toLowerCase() !== target.permissionId.toLowerCase()) {
|
|
398
|
+
return { status: "mismatch" };
|
|
399
|
+
}
|
|
400
|
+
return { status: "ok", approved: approved === true, revoked: revoked === true };
|
|
401
|
+
} catch {
|
|
402
|
+
return { status: "unavailable" };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function readLiveness(session, deps = {}) {
|
|
406
|
+
const state = await readPermissionState(session, deps);
|
|
407
|
+
if (state.status === "unavailable") return "unknown";
|
|
408
|
+
if (state.status === "mismatch") return "mismatch";
|
|
409
|
+
if (state.revoked) return "revoked";
|
|
410
|
+
return state.approved ? "active" : "unapproved";
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/x402/permission-recovery.ts
|
|
414
|
+
var RECOVERY_TIMEOUT_MS = 5e3;
|
|
415
|
+
async function recoverPermission(session, apiKey, deps = {}) {
|
|
416
|
+
if (session.permission) return session.permission;
|
|
417
|
+
if (!apiKey) return void 0;
|
|
418
|
+
const fetchPermission = deps.fetchPermission ?? (async (id, key) => {
|
|
419
|
+
const { getPermissionFromRelay } = await import('@jaw.id/core');
|
|
420
|
+
return getPermissionFromRelay(id, key);
|
|
421
|
+
});
|
|
422
|
+
let timer;
|
|
423
|
+
try {
|
|
424
|
+
const expired = new Promise((_, reject) => {
|
|
425
|
+
timer = setTimeout(() => reject(new Error("timed out")), deps.timeoutMs ?? RECOVERY_TIMEOUT_MS);
|
|
426
|
+
});
|
|
427
|
+
const relayed = await Promise.race([fetchPermission(session.permissionId, apiKey), expired]);
|
|
428
|
+
const permission = parseGrantedPermission(relayed);
|
|
429
|
+
if (!permission) return void 0;
|
|
430
|
+
if (permission.account.toLowerCase() !== session.ownerAddress.toLowerCase() || permission.spender.toLowerCase() !== session.sessionAddress.toLowerCase() || permission.end !== session.expiry) {
|
|
431
|
+
return void 0;
|
|
432
|
+
}
|
|
433
|
+
return saveRecoveredPermission(session, permission) ? permission : void 0;
|
|
434
|
+
} catch {
|
|
435
|
+
return void 0;
|
|
436
|
+
} finally {
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
155
440
|
|
|
156
441
|
// src/commands/session/status.ts
|
|
157
442
|
var SessionStatus = class _SessionStatus extends BaseCommand {
|
|
@@ -169,11 +454,17 @@ var SessionStatus = class _SessionStatus extends BaseCommand {
|
|
|
169
454
|
const config = loadSessionConfig();
|
|
170
455
|
const now = Date.now() / 1e3;
|
|
171
456
|
const isExpired = config.expiry <= now;
|
|
457
|
+
const permission = await recoverPermission(config, loadConfig().apiKey);
|
|
458
|
+
const current = permission ? { ...config, permission } : config;
|
|
459
|
+
const liveness = await readLiveness(current);
|
|
460
|
+
const stillLive = liveOrphans(config.orphanedPermissions, now);
|
|
172
461
|
if (format === "json") {
|
|
173
462
|
this.outputResult(
|
|
174
463
|
{
|
|
175
|
-
...
|
|
176
|
-
expired: isExpired
|
|
464
|
+
...current,
|
|
465
|
+
expired: isExpired,
|
|
466
|
+
permissionOnChain: liveness,
|
|
467
|
+
...stillLive.length > 0 ? { stillLiveOnChain: stillLive } : {}
|
|
177
468
|
},
|
|
178
469
|
format
|
|
179
470
|
);
|
|
@@ -184,22 +475,40 @@ var SessionStatus = class _SessionStatus extends BaseCommand {
|
|
|
184
475
|
this.log("Session expired.\n");
|
|
185
476
|
this.log(` Session address: ${config.sessionAddress}`);
|
|
186
477
|
this.log(` Owner address: ${config.ownerAddress}`);
|
|
187
|
-
this.log(` Permission ID: ${config.permissionId}`);
|
|
478
|
+
this.log(` Permission ID: ${config.permissionId}${onChainNote(liveness)}`);
|
|
188
479
|
this.log(` Chain: ${config.chainId}`);
|
|
189
480
|
this.log(` Expired: ${new Date(config.expiry * 1e3).toISOString()} (${ago} days ago)`);
|
|
481
|
+
if (stillLive.length > 0) this.log(stillLiveLine(stillLive.length));
|
|
190
482
|
this.log("\nRun `jaw session setup` to create a new session.");
|
|
191
483
|
} else {
|
|
192
484
|
const remaining = Math.floor((config.expiry - now) / 86400);
|
|
193
485
|
this.log("Session active.\n");
|
|
194
486
|
this.log(` Session address: ${config.sessionAddress}`);
|
|
487
|
+
if (isLegacySession(config)) {
|
|
488
|
+
this.log(" (separate from the session key: created by an older CLI)");
|
|
489
|
+
} else {
|
|
490
|
+
this.log(" (the session key EOA, and the x402 payer)");
|
|
491
|
+
}
|
|
195
492
|
this.log(` Owner address: ${config.ownerAddress}`);
|
|
196
|
-
this.log(` Permission ID: ${config.permissionId}`);
|
|
493
|
+
this.log(` Permission ID: ${config.permissionId}${onChainNote(liveness)}`);
|
|
197
494
|
this.log(` Chain: ${config.chainId}`);
|
|
198
495
|
this.log(` Expires: ${new Date(config.expiry * 1e3).toISOString()}`);
|
|
199
|
-
this.log(
|
|
496
|
+
this.log(
|
|
497
|
+
liveness === "revoked" ? " Status: Revoked on chain. Run `jaw session setup` to create a new session." : ` Status: Valid (${remaining} days remaining)`
|
|
498
|
+
);
|
|
499
|
+
if (stillLive.length > 0) this.log(stillLiveLine(stillLive.length));
|
|
200
500
|
}
|
|
201
501
|
}
|
|
202
502
|
};
|
|
503
|
+
function onChainNote(liveness) {
|
|
504
|
+
if (liveness === "revoked") return " (REVOKED on chain)";
|
|
505
|
+
if (liveness === "unapproved") return " (not approved on chain)";
|
|
506
|
+
if (liveness === "mismatch") return " (does not match the stored permission)";
|
|
507
|
+
return "";
|
|
508
|
+
}
|
|
509
|
+
function stillLiveLine(count) {
|
|
510
|
+
return ` Still live: ${count} permission${count === 1 ? "" : "s"} from earlier sessions (\`jaw session revoke\` removes them too)`;
|
|
511
|
+
}
|
|
203
512
|
|
|
204
513
|
export { SessionStatus as default };
|
|
205
514
|
//# sourceMappingURL=status.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/paths.ts","../../../src/lib/config.ts","../../../src/lib/output.ts","../../../src/base-command.ts","../../../src/lib/keystore.ts","../../../src/lib/session-config.ts","../../../src/commands/session/status.ts"],"names":["fs2","fs3"],"mappings":"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","file":"status.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig } from './types.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\n/** Paymaster URLs embed provider API keys as query params (e.g. Pimlico's ?apikey=...). */\nfunction redactUrlSecrets(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of [...parsed.searchParams.keys()]) {\n parsed.searchParams.set(key, '***');\n }\n return parsed.toString();\n } catch {\n return '***';\n }\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n ...(config.paymasters && {\n paymasters: Object.fromEntries(\n Object.entries(config.paymasters).map(([chainId, pm]) => [\n chainId,\n // context is a free-form object (usually just a sponsorshipPolicyId) but a\n // provider could stash a token there, so mask it rather than hand it to the agent.\n { ...pm, url: redactUrlSecrets(pm.url), context: pm.context ? '***' : undefined },\n ])\n ),\n }),\n };\n}\n\nexport function setConfigValue(key: keyof JawConfig, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import type { OutputFormat } from './types.js';\n\nexport function formatOutput(data: unknown, format: OutputFormat): string {\n if (format === 'json') {\n return JSON.stringify(data, replaceBigInt, 2);\n }\n return formatHuman(data);\n}\n\nfunction replaceBigInt(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n}\n\nfunction formatHuman(data: unknown, indent = 0): string {\n if (data === null || data === undefined) {\n return 'null';\n }\n\n if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') {\n return String(data);\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) return '(empty)';\n return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join('\\n');\n }\n\n if (typeof data === 'object') {\n const entries = Object.entries(data as Record<string, unknown>);\n if (entries.length === 0) return '(empty)';\n const pad = ' '.repeat(indent);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([key, val]) => {\n const paddedKey = key.padEnd(maxKeyLen);\n const valStr = typeof val === 'object' && val !== null ? '\\n' + formatHuman(val, indent + 2) : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n","import { Command, Flags } from '@oclif/core';\nimport type { OutputFormat } from './lib/types.js';\nimport { loadConfig } from './lib/config.js';\nimport { formatOutput } from './lib/output.js';\n\nexport abstract class BaseCommand extends Command {\n static override baseFlags = {\n output: Flags.string({\n char: 'o',\n description: 'Output format',\n options: ['json', 'human'],\n default: 'human',\n env: 'JAW_OUTPUT',\n }),\n chain: Flags.integer({\n char: 'c',\n description: 'Chain ID',\n env: 'JAW_CHAIN_ID',\n }),\n 'api-key': Flags.string({\n description: 'JAW API key',\n env: 'JAW_API_KEY',\n }),\n yes: Flags.boolean({\n char: 'y',\n description: 'Skip confirmations (for AI agents)',\n default: false,\n }),\n quiet: Flags.boolean({\n char: 'q',\n description: 'Suppress non-essential output',\n default: false,\n }),\n };\n\n protected resolveApiKey(flags: { 'api-key'?: string }): string {\n const apiKey = flags['api-key'] ?? loadConfig().apiKey;\n if (!apiKey) {\n this.error('API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`');\n }\n return apiKey;\n }\n\n protected resolveChainId(flags: { chain?: number }): number {\n const chainId = flags.chain ?? loadConfig().defaultChain;\n if (!chainId) {\n this.error('Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`');\n }\n return chainId;\n }\n\n protected outputResult(data: unknown, format: OutputFormat): void {\n const output = formatOutput(data, format);\n this.log(output);\n }\n}\n","import * as crypto from 'node:crypto';\nimport * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\ninterface KeystoreFile {\n version: 2;\n privateKey: string;\n address: string;\n createdAt: string;\n}\n\n/**\n * Generate a random secp256k1 private key as 0x-prefixed hex.\n */\nexport function generateSessionKey(): `0x${string}` {\n const bytes = crypto.randomBytes(32);\n return `0x${bytes.toString('hex')}` as `0x${string}`;\n}\n\n/**\n * Save private key to keystore.json.\n * On-chain PermissionManager is the real security boundary — the session key\n * can only act within its granted permission scope regardless of local access.\n */\nexport function saveKeystore(privateKeyHex: string, address: string): void {\n const keystore: KeystoreFile = {\n version: 2,\n privateKey: privateKeyHex,\n address,\n createdAt: new Date().toISOString(),\n };\n\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.keystore, JSON.stringify(keystore, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n // `mode` is only honored on file creation, not overwrite — re-apply explicitly.\n fs.chmodSync(PATHS.keystore, 0o600);\n}\n\n/**\n * Load private key hex from keystore.json.\n */\nexport function loadSessionKey(): string {\n if (!fs.existsSync(PATHS.keystore)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n\n const contents = fs.readFileSync(PATHS.keystore, 'utf-8');\n let parsed: KeystoreFile;\n try {\n parsed = JSON.parse(contents) as KeystoreFile;\n } catch {\n throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n return parsed.privateKey;\n}\n\n/**\n * Delete keystore.json.\n */\nexport function deleteKeystore(): void {\n if (fs.existsSync(PATHS.keystore)) {\n fs.unlinkSync(PATHS.keystore);\n }\n}\n\n/**\n * Check if keystore.json exists.\n */\nexport function keystoreExists(): boolean {\n return fs.existsSync(PATHS.keystore);\n}\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\nexport interface SessionConfig {\n ownerAddress: string;\n sessionAddress: string;\n permissionId: string;\n chainId: number;\n expiry: number;\n createdAt: string;\n}\n\nexport function saveSessionConfig(input: Omit<SessionConfig, 'createdAt'>): void {\n const config: SessionConfig = {\n ...input,\n createdAt: new Date().toISOString(),\n };\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.sessionConfig, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n fs.chmodSync(PATHS.sessionConfig, 0o600);\n}\n\nexport function loadSessionConfig(): SessionConfig {\n if (!fs.existsSync(PATHS.sessionConfig)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n const raw = fs.readFileSync(PATHS.sessionConfig, 'utf-8');\n try {\n return JSON.parse(raw) as SessionConfig;\n } catch {\n throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n}\n\nexport function deleteSessionConfig(): void {\n if (fs.existsSync(PATHS.sessionConfig)) {\n fs.unlinkSync(PATHS.sessionConfig);\n }\n}\n","import { BaseCommand } from '../../base-command.js';\nimport { keystoreExists } from '../../lib/keystore.js';\nimport { loadSessionConfig } from '../../lib/session-config.js';\nimport type { OutputFormat } from '../../lib/types.js';\n\nexport default class SessionStatus extends BaseCommand {\n static override description = 'Show current session status (address, permissions, expiry).';\n\n static override flags = {\n ...BaseCommand.baseFlags,\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(SessionStatus);\n const format = flags.output as OutputFormat;\n\n if (!keystoreExists()) {\n this.log('No session configured. Run `jaw session setup`.');\n return;\n }\n\n const config = loadSessionConfig();\n const now = Date.now() / 1000;\n const isExpired = config.expiry <= now;\n\n if (format === 'json') {\n this.outputResult(\n {\n ...config,\n expired: isExpired,\n },\n format\n );\n return;\n }\n\n if (isExpired) {\n const ago = Math.floor((now - config.expiry) / 86400);\n this.log('Session expired.\\n');\n this.log(` Session address: ${config.sessionAddress}`);\n this.log(` Owner address: ${config.ownerAddress}`);\n this.log(` Permission ID: ${config.permissionId}`);\n this.log(` Chain: ${config.chainId}`);\n this.log(` Expired: ${new Date(config.expiry * 1000).toISOString()} (${ago} days ago)`);\n this.log('\\nRun `jaw session setup` to create a new session.');\n } else {\n const remaining = Math.floor((config.expiry - now) / 86400);\n this.log('Session active.\\n');\n this.log(` Session address: ${config.sessionAddress}`);\n this.log(` Owner address: ${config.ownerAddress}`);\n this.log(` Permission ID: ${config.permissionId}`);\n this.log(` Chain: ${config.chainId}`);\n this.log(` Expires: ${new Date(config.expiry * 1000).toISOString()}`);\n this.log(` Status: Valid (${remaining} days remaining)`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/paths.ts","../../../src/lib/config.ts","../../../src/lib/output.ts","../../../src/base-command.ts","../../../src/lib/keystore.ts","../../../src/lib/session-config.ts","../../../src/x402/asset-registry.ts","../../../src/x402/balance.ts","../../../src/x402/permission-onchain.ts","../../../src/x402/permission-recovery.ts","../../../src/commands/session/status.ts"],"names":["fs2","fs3"],"mappings":"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","file":"status.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n x402Log: path.join(JAW_DIR, 'x402-log.jsonl'),\n paymentLock: path.join(JAW_DIR, 'x402-payment.lock'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig, SettableConfigKey } from './types.js';\nimport type { X402Policy, X402PolicyKey } from '../x402/policy.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\n/** Paymaster URLs embed provider API keys as query params (e.g. Pimlico's ?apikey=...). */\nfunction redactUrlSecrets(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of [...parsed.searchParams.keys()]) {\n parsed.searchParams.set(key, '***');\n }\n return parsed.toString();\n } catch {\n return '***';\n }\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n ...(config.paymasters && {\n paymasters: Object.fromEntries(\n Object.entries(config.paymasters).map(([chainId, pm]) => [\n chainId,\n // context is a free-form object (usually just a sponsorshipPolicyId) but a\n // provider could stash a token there, so mask it rather than hand it to the agent.\n { ...pm, url: redactUrlSecrets(pm.url), context: pm.context ? '***' : undefined },\n ])\n ),\n }),\n };\n}\n\n/**\n * Set one field of the `x402` payment policy. Array fields (allow-lists) are\n * comma-split. Intentionally NOT reachable from the MCP tool surface: an agent\n * must not be able to raise its own spending caps — only a human at the CLI.\n */\nexport function setX402PolicyValue(key: X402PolicyKey, value: string): void {\n const config = loadConfig();\n const x402: X402Policy = { ...(config.x402 ?? {}) };\n if (key === 'maxAmountPerPayment' || key === 'maxTotalPerSession' || key === 'topUpFloat') {\n // These are base-unit amounts read via BigInt() on the hot payment path;\n // reject anything that isn't a non-negative integer here so a bad value\n // can never turn into a per-payment failure later.\n if (!/^\\d+$/.test(value.trim())) {\n throw new Error(`${key} must be a non-negative integer (base units), got: ${value}`);\n }\n x402[key] = value.trim();\n } else {\n x402[key] = value\n .split(',')\n .map((v) => v.trim())\n .filter(Boolean);\n }\n saveConfig({ ...config, x402 });\n}\n\nexport function setConfigValue(key: SettableConfigKey, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n // Numeric keys: coerce + validate here so EVERY caller is safe. The MCP tool\n // passes raw strings (its schema types value as a string), so without this a\n // `sessionExpiry: \"abc\"` would land as a string and turn `expiry * 86400`\n // into NaN, and a `defaultChain` string would ride into an RPC URL verbatim.\n let toStore: string | number = value;\n if (key === 'defaultChain' || key === 'sessionExpiry') {\n // Strict: reject anything parseInt would silently truncate ('1.5' -> 1,\n // '0x10' -> 0, '10abc' -> 10). Only a bare positive decimal integer passes.\n const n = typeof value === 'number' ? value : /^\\d+$/.test(value.trim()) ? parseInt(value.trim(), 10) : NaN;\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(`${key} must be a positive integer, got: ${JSON.stringify(value)}`);\n }\n toStore = n;\n }\n const config = loadConfig();\n const updated = { ...config, [key]: toStore };\n saveConfig(updated);\n}\n","import type { OutputFormat } from './types.js';\n\nexport function formatOutput(data: unknown, format: OutputFormat): string {\n if (format === 'json') {\n return JSON.stringify(data, replaceBigInt, 2);\n }\n return formatHuman(data);\n}\n\nfunction replaceBigInt(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n}\n\nfunction formatHuman(data: unknown, indent = 0): string {\n if (data === null || data === undefined) {\n return 'null';\n }\n\n if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') {\n return String(data);\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) return '(empty)';\n return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join('\\n');\n }\n\n if (typeof data === 'object') {\n const entries = Object.entries(data as Record<string, unknown>);\n if (entries.length === 0) return '(empty)';\n const pad = ' '.repeat(indent);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([key, val]) => {\n const paddedKey = key.padEnd(maxKeyLen);\n const valStr = typeof val === 'object' && val !== null ? '\\n' + formatHuman(val, indent + 2) : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n","import { Command, Flags } from '@oclif/core';\nimport type { OutputFormat } from './lib/types.js';\nimport { loadConfig } from './lib/config.js';\nimport { formatOutput } from './lib/output.js';\n\nexport abstract class BaseCommand extends Command {\n static override baseFlags = {\n output: Flags.string({\n char: 'o',\n description: 'Output format',\n options: ['json', 'human'],\n default: 'human',\n env: 'JAW_OUTPUT',\n }),\n chain: Flags.integer({\n char: 'c',\n description: 'Chain ID',\n env: 'JAW_CHAIN_ID',\n }),\n 'api-key': Flags.string({\n description: 'JAW API key',\n env: 'JAW_API_KEY',\n }),\n yes: Flags.boolean({\n char: 'y',\n description: 'Skip confirmations (for AI agents)',\n default: false,\n }),\n quiet: Flags.boolean({\n char: 'q',\n description: 'Suppress non-essential output',\n default: false,\n }),\n };\n\n protected resolveApiKey(flags: { 'api-key'?: string }): string {\n const apiKey = flags['api-key'] ?? loadConfig().apiKey;\n if (!apiKey) {\n this.error('API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`');\n }\n return apiKey;\n }\n\n protected resolveChainId(flags: { chain?: number }): number {\n const chainId = flags.chain ?? loadConfig().defaultChain;\n if (!chainId) {\n this.error('Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`');\n }\n return chainId;\n }\n\n protected outputResult(data: unknown, format: OutputFormat): void {\n const output = formatOutput(data, format);\n this.log(output);\n }\n}\n","import * as crypto from 'node:crypto';\nimport * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\ninterface KeystoreFile {\n version: 2;\n privateKey: string;\n address: string;\n createdAt: string;\n}\n\n/**\n * Generate a random secp256k1 private key as 0x-prefixed hex.\n */\nexport function generateSessionKey(): `0x${string}` {\n const bytes = crypto.randomBytes(32);\n return `0x${bytes.toString('hex')}` as `0x${string}`;\n}\n\n/**\n * Save private key to keystore.json.\n * On-chain PermissionManager is the real security boundary — the session key\n * can only act within its granted permission scope regardless of local access.\n */\nexport function saveKeystore(privateKeyHex: string, address: string): void {\n const keystore: KeystoreFile = {\n version: 2,\n privateKey: privateKeyHex,\n address,\n createdAt: new Date().toISOString(),\n };\n\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.keystore, JSON.stringify(keystore, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n // `mode` is only honored on file creation, not overwrite — re-apply explicitly.\n fs.chmodSync(PATHS.keystore, 0o600);\n}\n\n/**\n * Load private key hex from keystore.json.\n */\nexport function loadSessionKey(): string {\n if (!fs.existsSync(PATHS.keystore)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n\n const contents = fs.readFileSync(PATHS.keystore, 'utf-8');\n let parsed: KeystoreFile;\n try {\n parsed = JSON.parse(contents) as KeystoreFile;\n } catch {\n throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n return parsed.privateKey;\n}\n\n/**\n * Load the session address from keystore.json, or null if it cannot be read.\n * Used to name the key in messages, so a corrupt keystore degrades the message\n * rather than the command.\n */\nexport function tryLoadKeystoreAddress(): string | null {\n try {\n if (!fs.existsSync(PATHS.keystore)) return null;\n const parsed = JSON.parse(fs.readFileSync(PATHS.keystore, 'utf-8')) as KeystoreFile;\n return parsed.address ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Delete keystore.json.\n */\nexport function deleteKeystore(): void {\n if (fs.existsSync(PATHS.keystore)) {\n fs.unlinkSync(PATHS.keystore);\n }\n}\n\n/**\n * Check if keystore.json exists.\n */\nexport function keystoreExists(): boolean {\n return fs.existsSync(PATHS.keystore);\n}\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\n/**\n * How the session account address is derived, as it appears on disk.\n *\n * 'eip7702' is the only one written now: the session key EOA itself, upgraded\n * in place via a delegation attached to its first userOp, so the session\n * account and the x402 payer are one address.\n *\n * 'counterfactual' is what earlier versions wrote, and what a config from\n * before the field existed means: a CREATE2 prediction from the account\n * factory, a second address that holds nothing and so cannot be charged for\n * the gas of the ops it sends. Still in the union because those files exist and\n * `SessionBridge` has to recognise them; `SessionSetup` never writes it.\n */\nexport type SessionMode = 'counterfactual' | 'eip7702';\n\n/**\n * The granted permission as `JustaPermissionManager` stores it.\n *\n * Every view on the manager takes `Permission calldata` and hashes it inside:\n * `isApproved`, `isRevoked` and `getCurrentPeriod` all do, and none of them\n * accept an id. A session holding only `permissionId` therefore holds the one\n * field the chain will not answer about, which is why nothing local could tell\n * that a permission had been revoked from another device.\n *\n * The grant response already carries all of it, so this costs a wider write\n * rather than a network call. Optional on read for two reasons: configs written\n * before this field exist, and a wallet running an older core answers with the\n * id alone. Consumers fall back to the local file, which is what those sessions\n * already meant.\n */\nexport interface GrantedPermission {\n account: string;\n spender: string;\n /**\n * Unix seconds the permission starts at. Also the anchor the contract steps\n * its period windows from, and what the local policy anchors on. A summary\n * field used to hold an approximation of it taken from the local clock.\n */\n start: number;\n /** Unix seconds the permission ends at. */\n end: number;\n /** Hex, as the grant returned it. Widened to a bigint at encode time. */\n salt: string;\n calls: Array<{ target: string; selector: string }>;\n spends: Array<{ token: string; allowance: string; unit: string; multiplier: number }>;\n}\n\nconst ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\nconst SELECTOR_RE = /^0x[0-9a-fA-F]{8}$/;\nconst HEX_RE = /^0x[0-9a-fA-F]+$/;\n/** Decimal or hex, matching what the SDK hands to `BigInt()`. */\nconst ALLOWANCE_RE = /^(0x[0-9a-fA-F]+|[0-9]+)$/;\n/** The units a grant may carry. `year` has no on-chain enum and is rewritten before encoding. */\nconst SPEND_UNITS = new Set(['minute', 'hour', 'day', 'week', 'month', 'year', 'forever']);\n\nfunction isPositiveInt(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value > 0;\n}\n\n/**\n * The permission struct out of a `wallet_grantPermissions` response, or\n * undefined when the response does not carry a usable one.\n *\n * Undefined rather than a throw, on every malformed field. By the time this\n * runs the grant is already on chain, and refusing to write the session over a\n * field that arrived in an unexpected shape would strand a live permission with\n * no local record of it. A session that skips this keeps behaving the way every\n * session behaved before the field existed.\n *\n * Strict about what it does accept: a struct that is wrong in any part hashes\n * to something other than the granted permission, so every on-chain read made\n * from it would quietly answer about a permission that does not exist.\n */\nexport function parseGrantedPermission(raw: unknown): GrantedPermission | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined;\n const r = raw as Record<string, unknown>;\n\n const { account, spender, salt } = r;\n if (typeof account !== 'string' || !ADDRESS_RE.test(account)) return undefined;\n if (typeof spender !== 'string' || !ADDRESS_RE.test(spender)) return undefined;\n if (typeof salt !== 'string' || !HEX_RE.test(salt)) return undefined;\n if (!isPositiveInt(r.start) || !isPositiveInt(r.end)) return undefined;\n\n if (!Array.isArray(r.calls) || r.calls.length === 0) return undefined;\n const calls: GrantedPermission['calls'] = [];\n for (const entry of r.calls) {\n if (typeof entry !== 'object' || entry === null) return undefined;\n const { target, selector } = entry as Record<string, unknown>;\n if (typeof target !== 'string' || !ADDRESS_RE.test(target)) return undefined;\n // The response builds the selector from the signature, so an entry without\n // one cannot be reconstructed: the signature it came from is not returned.\n if (typeof selector !== 'string' || !SELECTOR_RE.test(selector)) return undefined;\n calls.push({ target, selector });\n }\n\n if (!Array.isArray(r.spends)) return undefined;\n const spends: GrantedPermission['spends'] = [];\n for (const entry of r.spends) {\n if (typeof entry !== 'object' || entry === null) return undefined;\n const { token, allowance, unit, multiplier } = entry as Record<string, unknown>;\n if (typeof token !== 'string' || !ADDRESS_RE.test(token)) return undefined;\n if (typeof allowance !== 'string' || !ALLOWANCE_RE.test(allowance)) return undefined;\n if (typeof unit !== 'string' || !SPEND_UNITS.has(unit)) return undefined;\n // `multiplier` is a uint16 on chain and defaults to 1 in the grant, but the\n // encoded struct has to carry the number the permission was hashed with,\n // so an absent one is a struct we cannot rebuild rather than a 1.\n if (!isPositiveInt(multiplier) || multiplier > 65535) return undefined;\n spends.push({ token, allowance, unit, multiplier });\n }\n\n return { account, spender, start: r.start, end: r.end, salt, calls, spends };\n}\n\n/**\n * Whether this session predates the CLI settling on one account derivation, so\n * its permission belongs to an address separate from the session key and no op\n * it sends can be charged for its own gas.\n *\n * Named rather than compared inline: three callers ask this, and each one\n * spelled as `mode !== 'eip7702'` reads like a check for a variant among\n * several, when the only question is whether the session is still usable.\n */\nexport function isLegacySession(config: Pick<SessionConfig, 'mode'>): boolean {\n return config.mode !== 'eip7702';\n}\n\n/**\n * A permission this CLI granted and then stopped tracking.\n *\n * `session setup` replaces a session rather than adding to it, and it does not\n * always revoke what it replaces: the interactive path takes no for an answer,\n * and `--yes` never revokes at all. The session key is reused by default, so\n * the address the session signs with then holds two live grants while the\n * config names one. Its real authority is the sum of both, `x402 status`\n * reports only the new one, and `session revoke` could not reach the old one\n * at all, because the id lived in the file that was overwritten.\n *\n * Keeping the id is what makes it reachable again. Nothing here revokes on its\n * own: setup already asked.\n */\nexport interface OrphanedPermission {\n id: string;\n chainId: number;\n /** Unix seconds. */\n expiry: number;\n}\n\nexport interface SessionConfig {\n ownerAddress: string;\n sessionAddress: string;\n permissionId: string;\n chainId: number;\n expiry: number;\n createdAt: string;\n mode?: SessionMode;\n /** The struct the on-chain reads need. Absent on older sessions; see the type. */\n permission?: GrantedPermission;\n /** Permissions this key still holds that the session no longer names. */\n orphanedPermissions?: OrphanedPermission[];\n /**\n * Whether the permission this session names has already been revoked, while\n * something else it holds has not.\n *\n * Its own field rather than a rewritten `expiry`, which is what this was\n * first. `expiry` means when the permission ends, and the recovered-struct\n * check compares it against the permission's own `end` to know the relay\n * answered about this session: overwriting it made that comparison fail\n * forever, so a partly revoked session could never recover its struct and\n * went back to reporting \"cannot tell\", which is the state recovery exists to\n * end. A field that says one thing cannot be borrowed to say another.\n */\n permissionRevoked?: boolean;\n}\n\n/**\n * The orphans still worth carrying, newest first.\n *\n * An expired permission authorises nothing, so it is dropped rather than\n * accumulating in the file for the life of the machine. Dropping it is the same\n * decision `session revoke` makes when it skips the browser for an expired\n * session.\n */\nexport function liveOrphans(\n orphans: OrphanedPermission[] | undefined,\n now: number = Date.now() / 1000\n): OrphanedPermission[] {\n return (orphans ?? []).filter((orphan) => orphan.expiry > now);\n}\n\n/**\n * `mode` is required and pinned here, unlike on the read side where it stays\n * optional to describe files earlier versions wrote. Nothing but `SessionSetup`\n * writes a session, and a session written without the mode would be refused by\n * `SessionBridge` as if an old CLI had made it, so the compiler holds the\n * invariant rather than a test having to.\n */\nexport function saveSessionConfig(\n input: Omit<SessionConfig, 'createdAt' | 'mode'> & {\n mode: 'eip7702';\n /**\n * Carried over when a session is being replaced in place rather than\n * started, which `session add` does. `createdAt` is what the session total\n * is counted from (`sumSpentSince(payer, session.createdAt)`), so stamping a\n * fresh one there would hand the session cap a clean slate as a side effect\n * of adding a capability.\n */\n createdAt?: string;\n }\n): void {\n writeSessionConfig({ ...input, createdAt: input.createdAt ?? new Date().toISOString() });\n}\n\n/**\n * Written to a temporary file and renamed over the real one, which is atomic on\n * the same filesystem, so a reader never sees a half-written config.\n *\n * It matters more than it did: recovering the permission struct made two\n * commands that only ever read (`x402 status`, `session status`) into writers,\n * and the MCP server runs alongside a terminal, so two processes writing at\n * once is ordinary rather than exotic. A torn file loses the permission id,\n * which is exactly the stranding the orphan list exists to prevent.\n */\nfunction writeSessionConfig(config: SessionConfig): void {\n ensureDir(PATHS.root);\n const temp = `${PATHS.sessionConfig}.${process.pid}.tmp`;\n fs.writeFileSync(temp, JSON.stringify(config, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n fs.chmodSync(temp, 0o600);\n fs.renameSync(temp, PATHS.sessionConfig);\n}\n\n/**\n * Store a permission struct recovered for a session that was written without\n * one, leaving the rest of the file alone. Same reason as below for not going\n * through `saveSessionConfig`: it stamps a fresh `createdAt`.\n */\nexport function saveRecoveredPermission(config: SessionConfig, permission: GrantedPermission): boolean {\n // Merged into the file as it is now, not into the snapshot the caller loaded.\n // Recovery holds its copy across a relay round trip, and in that time a\n // `session revoke` running beside it writes progress between browser\n // approvals: renaming the old copy back over it would restore the expiry and\n // the orphan list that revoke had just cleared, and the next revoke would\n // re-attempt ids that are already gone. Reading immediately before the write\n // does not make this a transaction, it makes the window microseconds instead\n // of the length of a network call.\n //\n // A session that disappeared in the meantime stays gone: the only thing being\n // added here is a cache of something the relay can produce again.\n const current = tryLoadSessionConfig();\n if (!current || current.permissionId !== config.permissionId) return false;\n writeSessionConfig({ ...current, permission });\n return true;\n}\n\n/**\n * Record what a revoke has already done, so the rest of it can be retried.\n *\n * Revoking is not idempotent: core reads the permission from the relay before\n * sending and deletes it from there afterwards, so a second attempt at an id\n * already revoked fails before it sends anything. A session left naming an id\n * that is gone therefore costs a browser round trip that can only fail, which\n * is why what succeeded has to come out of the file as it succeeds.\n *\n * `ownPermissionRevoked` is recorded on its own field. Borrowing `expiry` for\n * it, which is what this did first, made that field mean two things at once and\n * broke the recovered-struct check that compares it against the permission's\n * own `end`. What it is for is unchanged: stopping the next revoke from\n * attempting an id the relay no longer has.\n *\n * Separate from `saveSessionConfig` because that one stamps a fresh\n * `createdAt`, and `createdAt` is what the session total is counted from\n * (`sumSpentSince(payer, session.createdAt)`). Editing a session through it\n * would hand the session cap a clean slate as a side effect of revoking one\n * permission.\n */\nexport function saveRevokeProgress(\n config: SessionConfig,\n progress: { orphans: OrphanedPermission[]; ownPermissionRevoked: boolean }\n): void {\n // Merged into the file as it stands, for the same reason\n // `saveRecoveredPermission` does: this runs between browser round trips, and\n // a recovery finishing beside it would otherwise be dropped by the next\n // progress write.\n const next: SessionConfig = { ...(tryLoadSessionConfig() ?? config) };\n if (progress.orphans.length > 0) next.orphanedPermissions = progress.orphans;\n else delete next.orphanedPermissions;\n if (progress.ownPermissionRevoked) next.permissionRevoked = true;\n writeSessionConfig(next);\n}\n\nexport function sessionConfigExists(): boolean {\n return fs.existsSync(PATHS.sessionConfig);\n}\n\nexport function loadSessionConfig(): SessionConfig {\n if (!fs.existsSync(PATHS.sessionConfig)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n const raw = fs.readFileSync(PATHS.sessionConfig, 'utf-8');\n try {\n return JSON.parse(raw) as SessionConfig;\n } catch {\n throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n}\n\n/**\n * Like `loadSessionConfig`, but returns null instead of throwing when the file\n * is missing or unreadable. For callers that can recover from a keystore whose\n * session-config is gone (interrupted setup, manual deletion, partial restore)\n * rather than callers that need an existing session to do their job.\n */\nexport function tryLoadSessionConfig(): SessionConfig | null {\n try {\n return loadSessionConfig();\n } catch {\n return null;\n }\n}\n\nexport function deleteSessionConfig(): void {\n if (fs.existsSync(PATHS.sessionConfig)) {\n fs.unlinkSync(PATHS.sessionConfig);\n }\n}\n","// USDC asset registry, mirrored from the backend's\n// `apps/ens/src/external/payment/asset-registry.ts`. Keep this in sync when the\n// server adds a chain. `wireNetwork` is the CAIP-2 id used on the x402 v2 wire.\n\nexport interface UsdcAsset {\n address: `0x${string}`;\n chainId: number;\n wireNetwork: string;\n /** EIP-712 domain `name` for this deployment's USDC. */\n usdcName: string;\n /** EIP-712 domain `version`. */\n usdcVersion: string;\n /**\n * Token decimals. Every USDC deployment here is 6, but carrying it on the\n * registry entry (rather than a literal `6` at the format site) keeps the\n * source of truth in one place and is ready for a non-6-decimal asset when\n * the registry grows past USDC. Reading it off-chain from the contract is\n * deliberately avoided: the registry is a controlled allowlist, so an extra\n * RPC round-trip and trusting a token's self-reported decimals buy nothing.\n */\n decimals: number;\n}\n\nexport const USDC_BY_NETWORK: Record<string, UsdcAsset> = {\n 'eip155:8453': {\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n chainId: 8453,\n wireNetwork: 'eip155:8453',\n usdcName: 'USD Coin',\n usdcVersion: '2',\n decimals: 6,\n },\n 'eip155:84532': {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n chainId: 84532,\n wireNetwork: 'eip155:84532',\n usdcName: 'USDC',\n usdcVersion: '2',\n decimals: 6,\n },\n 'eip155:137': {\n address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n chainId: 137,\n wireNetwork: 'eip155:137',\n usdcName: 'USD Coin',\n usdcVersion: '2',\n decimals: 6,\n },\n 'eip155:80002': {\n address: '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582',\n chainId: 80002,\n wireNetwork: 'eip155:80002',\n usdcName: 'USDC',\n usdcVersion: '2',\n decimals: 6,\n },\n};\n\n/**\n * Look up USDC metadata by CAIP-2 network id, or `undefined` if unsupported.\n *\n * Own keys only. The network reaches here from a 402 challenge and from the\n * Bazaar catalogue, both untrusted, and a plain index answers `constructor` or\n * `toString` with something off the prototype: callers then read `.address` off\n * a function and throw where they expected an unsupported network.\n */\nexport function usdcForNetwork(network: string): UsdcAsset | undefined {\n return Object.hasOwn(USDC_BY_NETWORK, network) ? USDC_BY_NETWORK[network] : undefined;\n}\n","import { createPublicClient, http, erc20Abi, formatUnits, type Chain, type PublicClient } from 'viem';\nimport { base, baseSepolia, polygon, polygonAmoy } from 'viem/chains';\nimport { usdcForNetwork, USDC_BY_NETWORK, type UsdcAsset } from './asset-registry.js';\nimport { loadConfig } from '../lib/config.js';\n\n// The JAW proxy RPC endpoint, mirrored from core's JAW_RPC_URL. Kept as a local\n// literal rather than an import because `@jaw.id/core` is lazy-loaded in the CLI\n// (a static import would pull it into startup); keep in sync if core's URL moves.\nconst JAW_RPC_URL = 'https://api.justaname.id/proxy/v1/rpc';\n\nconst CHAINS: Record<number, Chain> = {\n [base.id]: base,\n [baseSepolia.id]: baseSepolia,\n [polygon.id]: polygon,\n [polygonAmoy.id]: polygonAmoy,\n};\n\n// The viem chains here and USDC_BY_NETWORK in asset-registry are two lists that\n// must cover the same chain ids. Assert it at load so adding a USDC entry\n// without its viem chain fails loudly here instead of silently building a\n// client with `chain: undefined` (wrong gas/explorer defaults) at read time.\nfor (const chainId of Object.values(USDC_BY_NETWORK).map((a) => a.chainId)) {\n if (!CHAINS[chainId]) {\n throw new Error(\n `x402 balance: USDC registry has chain ${chainId} but no viem chain is mapped for it in balance.ts`\n );\n }\n}\n\n// One client per (chain, apiKey) across the process — a fresh transport per read\n// is wasted setup once balance checks run more than once per payment. Keying on\n// the apiKey too matters for the long-lived `jaw mcp` server: a first read before\n// a key is configured would otherwise cache the public-RPC client forever, so a\n// later `jaw config set apiKey` (or a key change) never takes effect. A new key\n// yields a new cache entry and a keyed transport; the stale entry just goes cold.\nconst clients = new Map<string, PublicClient>();\n\n/**\n * Route reads through the same JAW proxy core uses, so the x402 modules see the\n * same node/state core does and don't depend on viem's public RPC fallback\n * (rate-limited and flaky). This matters for the delegation probe in payer.ts:\n * a dropped getCode there would sign a raw signature where a wrapped one was\n * needed. Falls back to the public RPC when no apiKey is configured (e.g. tests).\n */\nfunction rpcTransport(chainId: number, apiKey?: string) {\n if (!apiKey) return http();\n return http(`${JAW_RPC_URL}?chainId=${chainId}&api-key=${apiKey}`);\n}\n\n/** Shared per-(chain, apiKey) public client for the x402 modules (reads only). */\nexport function publicClientFor(chainId: number): PublicClient {\n const chain = CHAINS[chainId];\n if (!chain) throw new Error(`x402: no viem chain configured for chainId ${chainId}`);\n const apiKey = loadConfig().apiKey;\n const key = `${chainId}:${apiKey ?? ''}`;\n let client = clients.get(key);\n if (!client) {\n client = createPublicClient({ chain, transport: rpcTransport(chainId, apiKey) });\n clients.set(key, client);\n }\n return client;\n}\n\n/** Reads the raw USDC balance (base units) of `owner`. Injectable for tests. */\nexport type BalanceReader = (asset: UsdcAsset, owner: `0x${string}`) => Promise<bigint>;\n\nconst readOnChain: BalanceReader = (asset, owner) =>\n publicClientFor(asset.chainId).readContract({\n address: asset.address,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [owner],\n });\n\nexport interface UsdcBalance {\n network: string;\n asset: `0x${string}`;\n /** Base units, decimal string. */\n raw: string;\n /** Human-readable amount, formatted with the asset's decimals from the registry. */\n formatted: string;\n}\n\n/**\n * Read an address's USDC balance on a CAIP-2 network so an agent can tell\n * whether it can afford a payment (or confirm one landed).\n */\nexport async function usdcBalance(\n network: string,\n owner: `0x${string}`,\n read: BalanceReader = readOnChain\n): Promise<UsdcBalance> {\n const asset = usdcForNetwork(network);\n if (!asset) throw new Error(`Unsupported x402 network: ${network}`);\n const raw = await read(asset, owner);\n return { network, asset: asset.address, raw: raw.toString(), formatted: formatUnits(raw, asset.decimals) };\n}\n","import { parseAbi, zeroAddress, ContractFunctionRevertedError, BaseError } from 'viem';\nimport { publicClientFor } from './balance.js';\nimport type { GrantedPermission } from '../lib/session-config.js';\n\n/**\n * What the permission manager will say about a session's own permission.\n *\n * Everything the CLI knew about its permission came out of\n * `~/.jaw/session-config.json`, which is written once at setup and never\n * updated. A permission revoked from keys.jaw.id or from another machine, or an\n * allowance drawn down by something other than this CLI, leaves that file\n * saying what was true the day it was written. The chain knows, and the reads\n * are views.\n *\n * None of them take the permission id. `isApproved`, `isRevoked` and\n * `getCurrentPeriod` all take `Permission calldata` and hash it internally, so\n * the struct has to be rebuilt exactly as it was approved. That is what\n * `GrantedPermission` is for, and why `permissionHash` is checked against the\n * id the grant returned before any answer here is trusted.\n */\n\nexport const PERMISSION_MANAGER_ABI = parseAbi([\n 'struct CallPermission { address target; bytes4 selector; address checker; }',\n 'struct SpendLimit { address token; uint160 allowance; uint8 unit; uint16 multiplier; }',\n 'struct Permission { address account; address spender; uint48 start; uint48 end; uint256 salt; CallPermission[] calls; SpendLimit[] spends; }',\n 'struct PeriodSpend { uint48 start; uint48 end; uint160 spend; }',\n 'function getHash(Permission permission) view returns (bytes32)',\n 'function isApproved(Permission permission) view returns (bool)',\n 'function isRevoked(Permission permission) view returns (bool)',\n 'function getCurrentPeriod(Permission permission, SpendLimit spendLimit) view returns (PeriodSpend)',\n // Carried so the two time-bound reverts can be told apart from a node that\n // did not answer. Everything else the manager can revert with decodes to an\n // unnamed error, which is treated as unavailable rather than guessed at.\n 'error JustaPermissionManager_BeforePermissionStart(uint48 currentTimestamp, uint48 start)',\n 'error JustaPermissionManager_AfterPermissionEnd(uint48 currentTimestamp, uint48 end)',\n]);\n\nconst TIME_BOUND_ERRORS = new Set([\n 'JustaPermissionManager_BeforePermissionStart',\n 'JustaPermissionManager_AfterPermissionEnd',\n]);\n\n/**\n * `PeriodUnit` in the contract, by declaration order. `year` is absent on\n * purpose: it has no on-chain unit, and the SDK rewrites it to `month` with the\n * multiplier times twelve before encoding. The struct has to be rebuilt with\n * the same rewrite or it hashes to a different permission.\n */\nconst PERIOD_UNIT_ENUM: Record<string, number> = {\n minute: 0,\n hour: 1,\n day: 2,\n week: 3,\n month: 4,\n forever: 5,\n};\n\ntype ContractSpendLimit = {\n token: `0x${string}`;\n allowance: bigint;\n unit: number;\n multiplier: number;\n};\n\ntype ContractPermission = {\n account: `0x${string}`;\n spender: `0x${string}`;\n start: number;\n end: number;\n salt: bigint;\n calls: ReadonlyArray<{ target: `0x${string}`; selector: `0x${string}`; checker: `0x${string}` }>;\n spends: ReadonlyArray<ContractSpendLimit>;\n};\n\nfunction toContractSpendLimit(spend: GrantedPermission['spends'][number]): ContractSpendLimit | null {\n const unit = spend.unit === 'year' ? 'month' : spend.unit;\n const multiplier = spend.unit === 'year' ? spend.multiplier * 12 : spend.multiplier;\n if (!Object.hasOwn(PERIOD_UNIT_ENUM, unit)) return null;\n return {\n token: spend.token as `0x${string}`,\n allowance: BigInt(spend.allowance),\n unit: PERIOD_UNIT_ENUM[unit],\n multiplier,\n };\n}\n\n/**\n * The stored permission in the shape the contract hashes, or null when it\n * cannot be rebuilt.\n *\n * `checker` is filled with the zero address because the grant response does not\n * carry one and `apiPermissionsToPermission` in the SDK always sets it that\n * way. The day a call checker is actually used that stops being true, and the\n * struct built here hashes to a permission that does not exist. Nothing here\n * can detect that on its own, which is why every caller goes through\n * `readPermissionState` and its hash check first.\n */\nexport function toContractPermission(permission: GrantedPermission): ContractPermission | null {\n const spends: ContractSpendLimit[] = [];\n for (const spend of permission.spends) {\n const converted = toContractSpendLimit(spend);\n if (!converted) return null;\n spends.push(converted);\n }\n let salt: bigint;\n try {\n salt = BigInt(permission.salt);\n } catch {\n return null;\n }\n return {\n account: permission.account as `0x${string}`,\n spender: permission.spender as `0x${string}`,\n start: permission.start,\n end: permission.end,\n salt,\n calls: permission.calls.map((call) => ({\n target: call.target as `0x${string}`,\n selector: call.selector as `0x${string}`,\n checker: zeroAddress,\n })),\n spends,\n };\n}\n\n/**\n * What the chain says about the permission, or why it could not say.\n *\n * `mismatch` is its own answer rather than an error: it means the struct on\n * disk does not hash to the id the grant returned, so every read made from it\n * would be about some other permission. Reporting that is more useful than\n * either a false \"revoked\" or silence.\n */\nexport type PermissionState =\n | { status: 'ok'; approved: boolean; revoked: boolean }\n | { status: 'mismatch' }\n | { status: 'unavailable' };\n\nexport interface PermissionReadTarget {\n chainId: number;\n permissionId: string;\n permission?: GrantedPermission;\n}\n\n/** Injectable for tests, and the seam that keeps the network out of the unit tests. */\nexport interface ReadDeps {\n readContract?: (args: {\n address: `0x${string}`;\n abi: typeof PERMISSION_MANAGER_ABI;\n functionName: 'getHash' | 'isApproved' | 'isRevoked' | 'getCurrentPeriod';\n args: readonly unknown[];\n }) => Promise<unknown>;\n /** The permission manager, injectable so a test does not have to import core. */\n manager?: `0x${string}`;\n /**\n * How long to wait on the node.\n *\n * These reads sit in `session status` and `x402 status`, which were local\n * commands before them, and in the payment path. viem retries three times at\n * ten seconds, so an unreachable node would hold a status report open for\n * most of a minute to add a fact that is optional in every caller. Timing out\n * is the same answer as any other failed read: not knowing.\n */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\n/** Rejects the read once the node has had long enough. */\nasync function within<T>(work: Promise<T>, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const expired = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('timed out')), timeoutMs);\n });\n return await Promise.race([work, expired]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function managerAddress(override?: `0x${string}`): Promise<`0x${string}`> {\n if (override) return override;\n // Lazy, like every other core import in the CLI: a static one pulls the whole\n // SDK into startup for a command that may never read the chain.\n const { PERMISSIONS_MANAGER_ADDRESS } = await import('@jaw.id/core');\n return PERMISSIONS_MANAGER_ADDRESS as `0x${string}`;\n}\n\n/**\n * The read function for this chain, or null when there is none.\n *\n * `publicClientFor` throws for a chain it has no viem entry for, and a session\n * can live on one: `session status` runs for any chain the account supports,\n * while the client here covers the four the USDC registry names. Null rather\n * than a throw, so a chain without a client reads as not knowing instead of\n * taking the command down.\n */\nfunction reader(chainId: number, deps: ReadDeps) {\n if (deps.readContract) return deps.readContract;\n try {\n const client = publicClientFor(chainId);\n return (args: Parameters<NonNullable<ReadDeps['readContract']>>[0]) =>\n client.readContract(args as never) as Promise<unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the chain whether the permission is approved and whether it has been\n * revoked.\n *\n * Expiry is deliberately not asked for. The contract's `end` is the same number\n * already in the session config, so a read would only confirm what is on disk.\n * What no local file can know is that someone revoked from another device, and\n * that is the whole reason to make the call.\n */\nexport async function readPermissionState(target: PermissionReadTarget, deps: ReadDeps = {}): Promise<PermissionState> {\n if (!target.permission) return { status: 'unavailable' };\n const permission = toContractPermission(target.permission);\n if (!permission) return { status: 'unavailable' };\n\n const read = reader(target.chainId, deps);\n if (!read) return { status: 'unavailable' };\n\n try {\n const address = await managerAddress(deps.manager);\n const [hash, approved, revoked] = await within(\n Promise.all([\n read({ address, abi: PERMISSION_MANAGER_ABI, functionName: 'getHash', args: [permission] }),\n read({ address, abi: PERMISSION_MANAGER_ABI, functionName: 'isApproved', args: [permission] }),\n read({ address, abi: PERMISSION_MANAGER_ABI, functionName: 'isRevoked', args: [permission] }),\n ]),\n deps.timeoutMs\n );\n if (typeof hash !== 'string' || hash.toLowerCase() !== target.permissionId.toLowerCase()) {\n return { status: 'mismatch' };\n }\n return { status: 'ok', approved: approved === true, revoked: revoked === true };\n } catch {\n // An unreachable node is not evidence about a permission. Callers report\n // that they could not tell, never that the session is dead.\n return { status: 'unavailable' };\n }\n}\n\n/**\n * What the on-chain allowance has actually lost this period.\n *\n * `outside-window` is the permission being expired or not yet started:\n * `getCurrentPeriod` runs the same time-bound check the spend path does and\n * reverts rather than answering zero, so the revert is the answer and not a\n * failure.\n */\nexport type OnChainPeriod =\n | { status: 'ok'; start: number; end: number; spend: bigint }\n | { status: 'outside-window' }\n | { status: 'unavailable' };\n\n/** One limit's counter, tagged with the limit it belongs to. */\nexport interface OnChainLimit {\n token: string;\n unit: string;\n multiplier: number;\n allowance: string;\n period: OnChainPeriod;\n}\n\n/**\n * The current period for every spend limit the permission puts on `token`.\n *\n * All of them, because the contract charges all of them: the counter lives at\n * `_lastUpdatedPeriod[permissionHash][spendLimitHash]`, so a permission with\n * several limits on one token has several counters and reading one answers\n * about one budget. Issued together and awaited as one, so N limits cost the\n * latency of one rather than N; the client does not batch them into a single\n * request, so it is still N+1 calls on the wire.\n *\n * The hash is checked alongside them. `getCurrentPeriod` does not require the\n * permission to exist: handed a struct that hashes to something else it answers\n * about that other hash, as a zeroed counter over a window built from the\n * on-disk start.\n */\nexport async function readCurrentPeriods(\n target: PermissionReadTarget & { token: string },\n deps: ReadDeps = {}\n): Promise<OnChainLimit[]> {\n if (!target.permission) return [];\n const permission = toContractPermission(target.permission);\n if (!permission) return [];\n\n const granted = target.permission.spends;\n const indexes = granted\n .map((spend, index) => ({ spend, index }))\n .filter(({ spend }) => spend.token.toLowerCase() === target.token.toLowerCase());\n if (indexes.length === 0) return [];\n\n const unreadable = indexes.map(({ spend }) => ({ ...spend, period: { status: 'unavailable' as const } }));\n\n const read = reader(target.chainId, deps);\n if (!read) return unreadable;\n\n try {\n const address = await managerAddress(deps.manager);\n const settled = await within(\n Promise.allSettled([\n read({ address, abi: PERMISSION_MANAGER_ABI, functionName: 'getHash', args: [permission] }),\n ...indexes.map(({ index }) =>\n read({\n address,\n abi: PERMISSION_MANAGER_ABI,\n functionName: 'getCurrentPeriod',\n args: [permission, permission.spends[index]],\n })\n ),\n ]),\n deps.timeoutMs\n );\n\n const [hashed, ...counters] = settled;\n const hash = hashed.status === 'fulfilled' ? hashed.value : null;\n if (typeof hash !== 'string' || hash.toLowerCase() !== target.permissionId.toLowerCase()) {\n return unreadable;\n }\n\n return indexes.map(({ spend }, i) => {\n const result = counters[i];\n if (result.status === 'rejected') {\n return {\n ...spend,\n period: isTimeBoundRevert(result.reason)\n ? ({ status: 'outside-window' } as const)\n : ({ status: 'unavailable' } as const),\n };\n }\n const period = result.value as { start: number; end: number; spend: bigint } | undefined;\n return {\n ...spend,\n period: period\n ? ({\n status: 'ok',\n start: Number(period.start),\n end: Number(period.end),\n spend: BigInt(period.spend),\n } as const)\n : ({ status: 'unavailable' } as const),\n };\n });\n } catch {\n return unreadable;\n }\n}\n\n/** Whether a failed read was the contract refusing on time bounds rather than a node not answering. */\nfunction isTimeBoundRevert(err: unknown): boolean {\n if (!(err instanceof BaseError)) return false;\n const revert = err.walk((e) => e instanceof ContractFunctionRevertedError);\n return revert instanceof ContractFunctionRevertedError && TIME_BOUND_ERRORS.has(revert.data?.errorName ?? '');\n}\n\n/**\n * What the chain says about a session's permission, flattened to one word.\n *\n * `unknown` covers every way of not knowing, which callers treat alike: a node\n * that did not answer, a chain with no client, a session written before the\n * struct was stored, and a struct that cannot be rebuilt. None of them are\n * evidence about a permission, so none of them may read as dead.\n */\nexport type PermissionLiveness = 'active' | 'revoked' | 'unapproved' | 'mismatch' | 'unknown';\n\n/** `readPermissionState` for the three status surfaces, which all want the one word. */\nexport async function readLiveness(session: PermissionReadTarget, deps: ReadDeps = {}): Promise<PermissionLiveness> {\n const state = await readPermissionState(session, deps);\n if (state.status === 'unavailable') return 'unknown';\n if (state.status === 'mismatch') return 'mismatch';\n if (state.revoked) return 'revoked';\n return state.approved ? 'active' : 'unapproved';\n}\n","import { saveRecoveredPermission, parseGrantedPermission, type SessionConfig } from '../lib/session-config.js';\nimport type { GrantedPermission } from '../lib/session-config.js';\n\n/**\n * The permission struct for a session that was created before the CLI kept one.\n *\n * Those sessions hold only the id, and no view on the permission manager takes\n * an id, so every on-chain read reports \"cannot tell\" and `session add` refuses\n * for want of a scope to merge against. Telling people to run setup again is a\n * poor answer when the grant is recoverable: the relay stores what was granted,\n * keyed by the same hash, and `getPermissionFromRelay` returns it.\n *\n * Recovered once and written back, so this costs one GET on the first command\n * that needs it and nothing afterwards. Anything that goes wrong leaves the\n * session exactly as it was, which is the behaviour every such session had\n * before this existed: a failed recovery must not turn a working command into\n * an error.\n */\n\nconst RECOVERY_TIMEOUT_MS = 5_000;\n\nexport interface RecoveryDeps {\n /** Injected for tests, and so the unit tests never import core. */\n fetchPermission?: (permissionId: string, apiKey: string) => Promise<unknown>;\n timeoutMs?: number;\n}\n\nexport async function recoverPermission(\n session: SessionConfig,\n apiKey: string | undefined,\n deps: RecoveryDeps = {}\n): Promise<GrantedPermission | undefined> {\n if (session.permission) return session.permission;\n if (!apiKey) return undefined;\n\n const fetchPermission =\n deps.fetchPermission ??\n (async (id: string, key: string) => {\n // Lazy, like every other core import in the CLI: a static one pulls the\n // whole SDK into startup for a command that may never need it.\n const { getPermissionFromRelay } = await import('@jaw.id/core');\n return getPermissionFromRelay(id as `0x${string}`, key);\n });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const expired = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('timed out')), deps.timeoutMs ?? RECOVERY_TIMEOUT_MS);\n });\n const relayed = await Promise.race([fetchPermission(session.permissionId, apiKey), expired]);\n const permission = parseGrantedPermission(relayed);\n if (!permission) return undefined;\n // Checked against the session before it is trusted. The on-chain `getHash`\n // gate covers the case where the chain can be reached, and `session add`\n // goes ahead on `unknown`, which is exactly what a chain outside the\n // client's registry returns: there the union would be built from an\n // unverified relay response with nothing local to corroborate it. These\n // three cost nothing and make it a response about this session or none.\n if (\n permission.account.toLowerCase() !== session.ownerAddress.toLowerCase() ||\n permission.spender.toLowerCase() !== session.sessionAddress.toLowerCase() ||\n permission.end !== session.expiry // safe now that nothing else rewrites expiry\n ) {\n return undefined;\n }\n // Whether it describes the permission that was granted is settled on chain,\n // by the `getHash` check every read here goes through. Storing one that does\n // not match costs a `mismatch` report, which is what the session already\n // showed as `unknown`, and never a wrong answer.\n // Undefined when it did not land, which means the session went away while\n // this was waiting on the relay. Handing the struct back anyway would let a\n // caller go on to merge against, or report on, a session that no longer\n // exists.\n return saveRecoveredPermission(session, permission) ? permission : undefined;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { BaseCommand } from '../../base-command.js';\nimport { keystoreExists } from '../../lib/keystore.js';\nimport { isLegacySession, liveOrphans, loadSessionConfig } from '../../lib/session-config.js';\nimport { loadConfig } from '../../lib/config.js';\nimport { readLiveness, type PermissionLiveness } from '../../x402/permission-onchain.js';\nimport { recoverPermission } from '../../x402/permission-recovery.js';\nimport type { OutputFormat } from '../../lib/types.js';\n\nexport default class SessionStatus extends BaseCommand {\n static override description = 'Show current session status (address, permissions, expiry).';\n\n static override flags = {\n ...BaseCommand.baseFlags,\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(SessionStatus);\n const format = flags.output as OutputFormat;\n\n if (!keystoreExists()) {\n this.log('No session configured. Run `jaw session setup`.');\n return;\n }\n\n const config = loadSessionConfig();\n const now = Date.now() / 1000;\n const isExpired = config.expiry <= now;\n // The one fact no local file can hold. Expiry is already on disk, so it\n // needs no read; a revoke made from keys.jaw.id or from another machine\n // leaves this file saying the session is fine. Fails soft to 'unknown',\n // which is what a session written before the struct was stored reports.\n // Best-effort, and quiet without an API key: this command has never needed\n // one, and a session written before the struct existed should not start\n // demanding a key to report what it always reported.\n const permission = await recoverPermission(config, loadConfig().apiKey);\n // Carried into everything below, including the json, so the run that\n // recovers the struct reports it rather than the next one.\n const current = permission ? { ...config, permission } : config;\n const liveness = await readLiveness(current);\n // Permissions from earlier sessions that are still live on chain. Said in\n // terms of the permission rather than of this key: setup generates a fresh\n // key whenever it is not reusing one, and `--yes` always does, so the key\n // that could exercise these is usually gone. The grant on the account is\n // what outlives the session, and before setup recorded the id it could not\n // be revoked at all.\n const stillLive = liveOrphans(config.orphanedPermissions, now);\n\n if (format === 'json') {\n this.outputResult(\n {\n ...current,\n expired: isExpired,\n permissionOnChain: liveness,\n ...(stillLive.length > 0 ? { stillLiveOnChain: stillLive } : {}),\n },\n format\n );\n return;\n }\n\n if (isExpired) {\n const ago = Math.floor((now - config.expiry) / 86400);\n this.log('Session expired.\\n');\n this.log(` Session address: ${config.sessionAddress}`);\n this.log(` Owner address: ${config.ownerAddress}`);\n this.log(` Permission ID: ${config.permissionId}${onChainNote(liveness)}`);\n this.log(` Chain: ${config.chainId}`);\n this.log(` Expired: ${new Date(config.expiry * 1000).toISOString()} (${ago} days ago)`);\n if (stillLive.length > 0) this.log(stillLiveLine(stillLive.length));\n this.log('\\nRun `jaw session setup` to create a new session.');\n } else {\n const remaining = Math.floor((config.expiry - now) / 86400);\n this.log('Session active.\\n');\n this.log(` Session address: ${config.sessionAddress}`);\n if (isLegacySession(config)) {\n // Auto mode refuses these, so say it here rather than letting the next\n // command be the one that explains it.\n this.log(' (separate from the session key: created by an older CLI)');\n } else {\n this.log(' (the session key EOA, and the x402 payer)');\n }\n this.log(` Owner address: ${config.ownerAddress}`);\n this.log(` Permission ID: ${config.permissionId}${onChainNote(liveness)}`);\n this.log(` Chain: ${config.chainId}`);\n this.log(` Expires: ${new Date(config.expiry * 1000).toISOString()}`);\n // Revoked outranks the local expiry: the session has time left on paper\n // and can no longer pull anything through the permission.\n this.log(\n liveness === 'revoked'\n ? ' Status: Revoked on chain. Run `jaw session setup` to create a new session.'\n : ` Status: Valid (${remaining} days remaining)`\n );\n if (stillLive.length > 0) this.log(stillLiveLine(stillLive.length));\n }\n }\n}\n\n/**\n * What to add after the permission id, or nothing when the chain did not\n * answer. Silence rather than \"unknown\" on every run of an older session: it\n * is a question the user did not ask and the CLI cannot answer.\n */\nfunction onChainNote(liveness: PermissionLiveness): string {\n if (liveness === 'revoked') return ' (REVOKED on chain)';\n if (liveness === 'unapproved') return ' (not approved on chain)';\n if (liveness === 'mismatch') return ' (does not match the stored permission)';\n return '';\n}\n\n/**\n * Said whenever earlier sessions left grants behind, because that is authority\n * on the account which this session does not describe and nothing else reports.\n */\nfunction stillLiveLine(count: number): string {\n return (\n ` Still live: ${count} permission${count === 1 ? '' : 's'} from earlier sessions ` +\n '(`jaw session revoke` removes them too)'\n );\n}\n"]}
|
package/dist/commands/version.js
CHANGED
|
@@ -12,7 +12,9 @@ var PATHS = {
|
|
|
12
12
|
session: path.join(JAW_DIR, "session.json"),
|
|
13
13
|
relay: path.join(JAW_DIR, "relay.json"),
|
|
14
14
|
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
15
|
-
sessionConfig: path.join(JAW_DIR, "session-config.json")
|
|
15
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json"),
|
|
16
|
+
x402Log: path.join(JAW_DIR, "x402-log.jsonl"),
|
|
17
|
+
paymentLock: path.join(JAW_DIR, "x402-payment.lock")
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
// src/lib/config.ts
|