@1sat/cli 0.0.120 → 0.0.122
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/cli.js +712 -91
- package/package.json +5 -4
- package/src/commands/permissions.ts +269 -0
- package/src/commands/serve.ts +25 -62
- package/src/config.ts +5 -4
- package/src/context.ts +21 -1
- package/src/help.ts +82 -1
- package/src/main.ts +5 -0
- package/src/wallet-api/admin.ts +107 -0
- package/src/wallet-api/deny.ts +194 -0
- package/src/wallet-api/endpoint.ts +108 -0
- package/src/wallet-api/grants.ts +238 -0
- package/src/wallet-api/permission-store.ts +106 -0
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
res = fn(fn = 0);
|
|
6
|
-
} catch (e) {
|
|
7
|
-
err = [e];
|
|
8
|
-
}
|
|
9
|
-
if (err)
|
|
10
|
-
throw err[0];
|
|
11
|
-
return res;
|
|
12
|
-
};
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
13
5
|
|
|
14
6
|
// src/args.ts
|
|
15
7
|
function parseGlobalFlags(args) {
|
|
@@ -386,6 +378,120 @@ var init_monitor_once = __esm(() => {
|
|
|
386
378
|
init_monitor_lock();
|
|
387
379
|
});
|
|
388
380
|
|
|
381
|
+
// src/wallet-api/admin.ts
|
|
382
|
+
import { PERMISSION_SCHEME_IDS } from "@1sat/types";
|
|
383
|
+
import {
|
|
384
|
+
LocalWalletPermissionsManager
|
|
385
|
+
} from "@1sat/wallet";
|
|
386
|
+
function adminWallet(wallet, store) {
|
|
387
|
+
const manager = new LocalWalletPermissionsManager(wallet, CLI_ADMIN_ORIGINATOR, { permissionModules: adminPermissionModules }, { store });
|
|
388
|
+
return withAdminOriginator(manager);
|
|
389
|
+
}
|
|
390
|
+
function withAdminOriginator(manager) {
|
|
391
|
+
return new Proxy(manager, {
|
|
392
|
+
get(target, property, receiver) {
|
|
393
|
+
const value = Reflect.get(target, property, receiver);
|
|
394
|
+
if (typeof value !== "function")
|
|
395
|
+
return value;
|
|
396
|
+
return (args) => value.call(target, args, CLI_ADMIN_ORIGINATOR);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
var CLI_ADMIN_ORIGINATOR = "1sat-cli.internal", adminPermissionModules;
|
|
401
|
+
var init_admin = __esm(() => {
|
|
402
|
+
adminPermissionModules = Object.fromEntries(PERMISSION_SCHEME_IDS.map((scheme) => [
|
|
403
|
+
scheme,
|
|
404
|
+
{
|
|
405
|
+
onRequest: async (req) => ({ args: req.args }),
|
|
406
|
+
onResponse: async (res) => res
|
|
407
|
+
}
|
|
408
|
+
]));
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// src/wallet-api/permission-store.ts
|
|
412
|
+
import {
|
|
413
|
+
existsSync as existsSync3,
|
|
414
|
+
mkdirSync as mkdirSync2,
|
|
415
|
+
readFileSync as readFileSync4,
|
|
416
|
+
renameSync,
|
|
417
|
+
writeFileSync as writeFileSync4
|
|
418
|
+
} from "node:fs";
|
|
419
|
+
import { dirname, join as join5 } from "node:path";
|
|
420
|
+
import {
|
|
421
|
+
permissionKeyToString
|
|
422
|
+
} from "@1sat/wallet";
|
|
423
|
+
function permissionStorePath(dataDir, chain) {
|
|
424
|
+
return join5(dataDir, `permissions-${chain}.json`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
class FilePermissionStore {
|
|
428
|
+
path;
|
|
429
|
+
constructor(path) {
|
|
430
|
+
this.path = path;
|
|
431
|
+
}
|
|
432
|
+
async findGrant(key) {
|
|
433
|
+
return this.load().grants[permissionKeyToString(key)] ?? null;
|
|
434
|
+
}
|
|
435
|
+
async putGrant(grant) {
|
|
436
|
+
const file = this.load();
|
|
437
|
+
file.grants[permissionKeyToString(grant.key)] = grant;
|
|
438
|
+
this.save(file);
|
|
439
|
+
}
|
|
440
|
+
async deleteGrant(key) {
|
|
441
|
+
const file = this.load();
|
|
442
|
+
const id = permissionKeyToString(key);
|
|
443
|
+
if (!(id in file.grants))
|
|
444
|
+
return;
|
|
445
|
+
delete file.grants[id];
|
|
446
|
+
this.save(file);
|
|
447
|
+
}
|
|
448
|
+
async deleteAllForOriginator(originator) {
|
|
449
|
+
const file = this.load();
|
|
450
|
+
let removed = 0;
|
|
451
|
+
for (const [id, grant] of Object.entries(file.grants)) {
|
|
452
|
+
if (grant.key.originator === originator) {
|
|
453
|
+
delete file.grants[id];
|
|
454
|
+
removed++;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (removed > 0)
|
|
458
|
+
this.save(file);
|
|
459
|
+
return removed;
|
|
460
|
+
}
|
|
461
|
+
async listGrants(filter) {
|
|
462
|
+
const out = [];
|
|
463
|
+
for (const grant of Object.values(this.load().grants)) {
|
|
464
|
+
if (filter?.originator && grant.key.originator !== filter.originator)
|
|
465
|
+
continue;
|
|
466
|
+
if (filter?.type && grant.key.type !== filter.type)
|
|
467
|
+
continue;
|
|
468
|
+
out.push(grant);
|
|
469
|
+
}
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
load() {
|
|
473
|
+
if (!existsSync3(this.path))
|
|
474
|
+
return { version: FILE_VERSION, grants: {} };
|
|
475
|
+
const raw = readFileSync4(this.path, "utf8");
|
|
476
|
+
const parsed = JSON.parse(raw);
|
|
477
|
+
if (parsed.version !== FILE_VERSION) {
|
|
478
|
+
throw new Error(`Unsupported permission store version ${String(parsed.version)} in ${this.path}`);
|
|
479
|
+
}
|
|
480
|
+
return { version: FILE_VERSION, grants: parsed.grants ?? {} };
|
|
481
|
+
}
|
|
482
|
+
save(file) {
|
|
483
|
+
const dir = dirname(this.path);
|
|
484
|
+
if (!existsSync3(dir))
|
|
485
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
486
|
+
const tmp = `${this.path}.${process.pid}.tmp`;
|
|
487
|
+
writeFileSync4(tmp, `${JSON.stringify(file, null, 2)}
|
|
488
|
+
`, { mode: 384 });
|
|
489
|
+
renameSync(tmp, this.path);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
var FILE_VERSION = 1;
|
|
493
|
+
var init_permission_store = () => {};
|
|
494
|
+
|
|
389
495
|
// src/context.ts
|
|
390
496
|
import { createContext } from "@1sat/actions";
|
|
391
497
|
import { createNodeWallet as createNodeWallet2 } from "@1sat/wallet-node";
|
|
@@ -405,7 +511,8 @@ async function loadContext(privateKey, opts) {
|
|
|
405
511
|
backups: config.backups,
|
|
406
512
|
skipInitialMonitor: true
|
|
407
513
|
});
|
|
408
|
-
const
|
|
514
|
+
const wallet = adminWallet(walletResult.wallet, new FilePermissionStore(permissionStorePath(dataDir, opts.chain)));
|
|
515
|
+
const ctx = createContext(wallet, {
|
|
409
516
|
services: walletResult.services,
|
|
410
517
|
chain: opts.chain,
|
|
411
518
|
dataDir,
|
|
@@ -428,6 +535,8 @@ async function loadContext(privateKey, opts) {
|
|
|
428
535
|
var init_context = __esm(() => {
|
|
429
536
|
init_config();
|
|
430
537
|
init_monitor_once();
|
|
538
|
+
init_admin();
|
|
539
|
+
init_permission_store();
|
|
431
540
|
});
|
|
432
541
|
|
|
433
542
|
// src/output.ts
|
|
@@ -513,11 +622,11 @@ async function handleActionCommand(args, opts) {
|
|
|
513
622
|
Registered actions:
|
|
514
623
|
`);
|
|
515
624
|
const grouped = new Map;
|
|
516
|
-
for (const
|
|
517
|
-
const cat =
|
|
625
|
+
for (const action2 of actions) {
|
|
626
|
+
const cat = action2.meta.category;
|
|
518
627
|
if (!grouped.has(cat))
|
|
519
628
|
grouped.set(cat, []);
|
|
520
|
-
grouped.get(cat).push(
|
|
629
|
+
grouped.get(cat).push(action2);
|
|
521
630
|
}
|
|
522
631
|
for (const [category, categoryActions] of grouped) {
|
|
523
632
|
console.log(` ${category}:`);
|
|
@@ -563,7 +672,7 @@ var init_action = __esm(() => {
|
|
|
563
672
|
});
|
|
564
673
|
|
|
565
674
|
// src/help.ts
|
|
566
|
-
import { readFileSync as
|
|
675
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
567
676
|
import chalk2 from "chalk";
|
|
568
677
|
function getVersion() {
|
|
569
678
|
return VERSION;
|
|
@@ -717,7 +826,7 @@ var VERSION, GLOBAL_OPTIONS, ENV_VARS, COMMANDS;
|
|
|
717
826
|
var init_help = __esm(() => {
|
|
718
827
|
VERSION = (() => {
|
|
719
828
|
try {
|
|
720
|
-
const pkg = JSON.parse(
|
|
829
|
+
const pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
|
|
721
830
|
return pkg.version || "0.0.11";
|
|
722
831
|
} catch {
|
|
723
832
|
return "0.0.11";
|
|
@@ -1390,7 +1499,82 @@ var init_help = __esm(() => {
|
|
|
1390
1499
|
{ name: "monitor", description: "Monitor daemon only" },
|
|
1391
1500
|
{
|
|
1392
1501
|
name: "wallet-api",
|
|
1393
|
-
description: "App-facing BRC-100 wallet endpoint only (headless, default port 3321)"
|
|
1502
|
+
description: "App-facing BRC-100 wallet endpoint only (headless, default port 3321). Ungranted requests are denied with the `1sat permissions grant` command that would allow them"
|
|
1503
|
+
}
|
|
1504
|
+
]
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
group: "Server",
|
|
1508
|
+
name: "permissions",
|
|
1509
|
+
description: "What apps on `1sat serve wallet-api` may do. Grants live in <dataDir>/permissions-<chain>.json; the endpoint reads the file on every check, so a grant applies to the app's next call without a restart. Works with the server stopped or running and needs no wallet key.",
|
|
1510
|
+
subcommands: [
|
|
1511
|
+
{
|
|
1512
|
+
name: "list",
|
|
1513
|
+
description: "Show grants, grouped by app origin",
|
|
1514
|
+
positional: "[<origin>]"
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
name: "grant",
|
|
1518
|
+
description: "Allow an app one or more permissions. Selectors may be combined in one call.",
|
|
1519
|
+
positional: "<origin>",
|
|
1520
|
+
args: [
|
|
1521
|
+
{
|
|
1522
|
+
flag: "--protocol",
|
|
1523
|
+
values: "<name>",
|
|
1524
|
+
description: "Protocol name; requires --level"
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
flag: "--level",
|
|
1528
|
+
values: "<0|1|2>",
|
|
1529
|
+
description: "Protocol security level"
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
flag: "--counterparty",
|
|
1533
|
+
values: "<hex|self|anyone>",
|
|
1534
|
+
description: "Counterparty for a level 0/2 protocol (default self; ignored at level 1), or a certificate's verifier (also --verifier)"
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
flag: "--privileged",
|
|
1538
|
+
description: "Grant the privileged variant of a protocol/certificate"
|
|
1539
|
+
},
|
|
1540
|
+
{ flag: "--basket", values: "<name>", description: "Basket access" },
|
|
1541
|
+
{
|
|
1542
|
+
flag: "--label",
|
|
1543
|
+
values: "<name>",
|
|
1544
|
+
description: "Action label (a level-1 `action label <name>` protocol)"
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
flag: "--certificate",
|
|
1548
|
+
values: "<type>",
|
|
1549
|
+
description: "Certificate type; requires --fields"
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
flag: "--fields",
|
|
1553
|
+
values: "<a,b>",
|
|
1554
|
+
description: "Certificate fields that may be disclosed"
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
flag: "--spending",
|
|
1558
|
+
values: "<satoshis>",
|
|
1559
|
+
description: "Monthly spending cap; month-to-date spend is read from action history on every check"
|
|
1560
|
+
}
|
|
1561
|
+
]
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
name: "revoke",
|
|
1565
|
+
description: "Remove matching grants, or every grant for an origin",
|
|
1566
|
+
positional: "<origin>",
|
|
1567
|
+
args: [
|
|
1568
|
+
{
|
|
1569
|
+
flag: "--all",
|
|
1570
|
+
description: "Remove every grant for the origin"
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
flag: "--protocol",
|
|
1574
|
+
values: "<name>",
|
|
1575
|
+
description: "Same selectors as `grant`"
|
|
1576
|
+
}
|
|
1577
|
+
]
|
|
1394
1578
|
}
|
|
1395
1579
|
]
|
|
1396
1580
|
},
|
|
@@ -1476,7 +1660,7 @@ function createApprovalAuth(wallet) {
|
|
|
1476
1660
|
get(target, property) {
|
|
1477
1661
|
if (property === "createAction")
|
|
1478
1662
|
return async (...args) => {
|
|
1479
|
-
const satoshis = args[0].outputs?.reduce((sum,
|
|
1663
|
+
const satoshis = args[0].outputs?.reduce((sum, output2) => sum + output2.satoshis, 0) ?? 0;
|
|
1480
1664
|
if (!Number.isSafeInteger(satoshis) || satoshis <= 0)
|
|
1481
1665
|
throw new Error("Invalid HTTP payment amount");
|
|
1482
1666
|
if (approved === false)
|
|
@@ -1573,7 +1757,7 @@ var init_authfetch_request = __esm(() => {
|
|
|
1573
1757
|
});
|
|
1574
1758
|
|
|
1575
1759
|
// src/commands/authfetch.ts
|
|
1576
|
-
import { readFileSync as
|
|
1760
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1577
1761
|
import { confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
1578
1762
|
async function handleAuthfetchCommand(args, opts) {
|
|
1579
1763
|
if (!args[0] || args[0] === "help" || args.includes("--help") || args.includes("-h")) {
|
|
@@ -1632,7 +1816,7 @@ Unknown or missing method: ${method ?? ""}`);
|
|
|
1632
1816
|
}
|
|
1633
1817
|
let body;
|
|
1634
1818
|
if (bodyRaw !== undefined) {
|
|
1635
|
-
body = bodyRaw.startsWith("@") ?
|
|
1819
|
+
body = bodyRaw.startsWith("@") ? readFileSync6(bodyRaw.slice(1), "utf8") : bodyRaw;
|
|
1636
1820
|
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
1637
1821
|
headers["Content-Type"] = "application/json";
|
|
1638
1822
|
}
|
|
@@ -2000,15 +2184,15 @@ async function handleInitCommand(_args, opts) {
|
|
|
2000
2184
|
}
|
|
2001
2185
|
let wif;
|
|
2002
2186
|
if (keySource === "generate") {
|
|
2003
|
-
const
|
|
2004
|
-
wif =
|
|
2005
|
-
const
|
|
2187
|
+
const pk2 = PrivateKey2.fromRandom();
|
|
2188
|
+
wif = pk2.toWif();
|
|
2189
|
+
const address2 = pk2.toPublicKey().toAddress();
|
|
2006
2190
|
console.log();
|
|
2007
2191
|
console.log(formatWarning(" IMPORTANT: Back up your private key!"));
|
|
2008
2192
|
console.log(formatWarning(" If you lose it, your funds are gone forever."));
|
|
2009
2193
|
console.log();
|
|
2010
2194
|
console.log(` ${formatValue("WIF:")} ${wif}`);
|
|
2011
|
-
console.log(` ${formatValue("Address:")} ${
|
|
2195
|
+
console.log(` ${formatValue("Address:")} ${address2}`);
|
|
2012
2196
|
console.log();
|
|
2013
2197
|
const confirmed = await confirm2({
|
|
2014
2198
|
message: "Have you saved your private key somewhere safe?"
|
|
@@ -2077,7 +2261,7 @@ var init_init = __esm(() => {
|
|
|
2077
2261
|
});
|
|
2078
2262
|
|
|
2079
2263
|
// src/beef.ts
|
|
2080
|
-
import { readFileSync as
|
|
2264
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
2081
2265
|
import { Utils } from "@bsv/sdk";
|
|
2082
2266
|
function parseBeefFlag(value) {
|
|
2083
2267
|
if (!value)
|
|
@@ -2086,7 +2270,7 @@ function parseBeefFlag(value) {
|
|
|
2086
2270
|
if (!value.includes(`
|
|
2087
2271
|
`) && !value.includes(" ") && value.length < 4096) {
|
|
2088
2272
|
try {
|
|
2089
|
-
const buf =
|
|
2273
|
+
const buf = readFileSync7(value);
|
|
2090
2274
|
return Array.from(buf);
|
|
2091
2275
|
} catch {}
|
|
2092
2276
|
}
|
|
@@ -2227,7 +2411,7 @@ var init_locks = __esm(() => {
|
|
|
2227
2411
|
});
|
|
2228
2412
|
|
|
2229
2413
|
// src/commands/mcp-proxy.ts
|
|
2230
|
-
import { existsSync as
|
|
2414
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2231
2415
|
import { Hash, KeyDeriver, PrivateKey as PrivateKey3, Signature, Utils as Utils2 } from "@bsv/sdk";
|
|
2232
2416
|
import { createLogger, initLogger } from "evlog";
|
|
2233
2417
|
function generateNonce() {
|
|
@@ -2236,12 +2420,12 @@ function generateNonce() {
|
|
|
2236
2420
|
return toBase64(Array.from(bytes));
|
|
2237
2421
|
}
|
|
2238
2422
|
function getClientKey() {
|
|
2239
|
-
|
|
2240
|
-
if (
|
|
2241
|
-
return PrivateKey3.fromWif(
|
|
2423
|
+
mkdirSync3(KEY_DIR, { recursive: true });
|
|
2424
|
+
if (existsSync4(CLIENT_KEY_PATH)) {
|
|
2425
|
+
return PrivateKey3.fromWif(readFileSync8(CLIENT_KEY_PATH, "utf-8").trim());
|
|
2242
2426
|
}
|
|
2243
2427
|
const key = PrivateKey3.fromRandom();
|
|
2244
|
-
|
|
2428
|
+
writeFileSync5(CLIENT_KEY_PATH, key.toWif(), { mode: 384 });
|
|
2245
2429
|
const log = createLogger({ context: "startup" });
|
|
2246
2430
|
log.set({ event: "key_generated", path: CLIENT_KEY_PATH });
|
|
2247
2431
|
log.emit();
|
|
@@ -2739,7 +2923,7 @@ var init_opns = __esm(() => {
|
|
|
2739
2923
|
});
|
|
2740
2924
|
|
|
2741
2925
|
// src/commands/ordinals.ts
|
|
2742
|
-
import { readFileSync as
|
|
2926
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2743
2927
|
import { basename, extname } from "node:path";
|
|
2744
2928
|
import {
|
|
2745
2929
|
burnOrdinals,
|
|
@@ -2857,7 +3041,7 @@ async function ordinalsInscribe(args, opts) {
|
|
|
2857
3041
|
}
|
|
2858
3042
|
let fileBytes;
|
|
2859
3043
|
try {
|
|
2860
|
-
fileBytes =
|
|
3044
|
+
fileBytes = readFileSync9(file);
|
|
2861
3045
|
} catch (err) {
|
|
2862
3046
|
fatal(`Failed to read file: ${err.message}`);
|
|
2863
3047
|
}
|
|
@@ -3045,6 +3229,335 @@ var init_ordinals = __esm(() => {
|
|
|
3045
3229
|
};
|
|
3046
3230
|
});
|
|
3047
3231
|
|
|
3232
|
+
// src/wallet-api/grants.ts
|
|
3233
|
+
import {
|
|
3234
|
+
normalizeOriginator,
|
|
3235
|
+
permissionKeyToString as permissionKeyToString2
|
|
3236
|
+
} from "@1sat/wallet";
|
|
3237
|
+
function shellQuote(value) {
|
|
3238
|
+
if (value !== "" && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value))
|
|
3239
|
+
return value;
|
|
3240
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
3241
|
+
}
|
|
3242
|
+
function grantCommand(spec, prefix = GRANT_COMMAND) {
|
|
3243
|
+
const key = spec.key;
|
|
3244
|
+
const parts = [prefix, shellQuote(key.originator)];
|
|
3245
|
+
switch (key.type) {
|
|
3246
|
+
case "protocol": {
|
|
3247
|
+
if (key.protocolLevel === 1 && key.protocolName.startsWith(LABEL_PROTOCOL_PREFIX)) {
|
|
3248
|
+
parts.push("--label", shellQuote(key.protocolName.slice(LABEL_PROTOCOL_PREFIX.length)));
|
|
3249
|
+
break;
|
|
3250
|
+
}
|
|
3251
|
+
parts.push("--protocol", shellQuote(key.protocolName));
|
|
3252
|
+
parts.push("--level", String(key.protocolLevel));
|
|
3253
|
+
if (key.protocolLevel !== 1 && key.counterparty) {
|
|
3254
|
+
parts.push("--counterparty", shellQuote(key.counterparty));
|
|
3255
|
+
}
|
|
3256
|
+
if (key.privileged)
|
|
3257
|
+
parts.push("--privileged");
|
|
3258
|
+
break;
|
|
3259
|
+
}
|
|
3260
|
+
case "basket":
|
|
3261
|
+
parts.push("--basket", shellQuote(key.basket));
|
|
3262
|
+
break;
|
|
3263
|
+
case "certificate":
|
|
3264
|
+
parts.push("--certificate", shellQuote(key.certType));
|
|
3265
|
+
parts.push("--fields", shellQuote(key.fields.join(",")));
|
|
3266
|
+
if (key.verifier) {
|
|
3267
|
+
parts.push("--counterparty", shellQuote(key.verifier));
|
|
3268
|
+
}
|
|
3269
|
+
if (key.privileged)
|
|
3270
|
+
parts.push("--privileged");
|
|
3271
|
+
break;
|
|
3272
|
+
case "spending":
|
|
3273
|
+
parts.push("--spending", String(spec.authorizedAmount ?? 0));
|
|
3274
|
+
break;
|
|
3275
|
+
}
|
|
3276
|
+
return parts.join(" ");
|
|
3277
|
+
}
|
|
3278
|
+
function describeKey(spec) {
|
|
3279
|
+
const key = spec.key;
|
|
3280
|
+
switch (key.type) {
|
|
3281
|
+
case "protocol": {
|
|
3282
|
+
if (key.protocolLevel === 1 && key.protocolName.startsWith(LABEL_PROTOCOL_PREFIX)) {
|
|
3283
|
+
return `label ${key.protocolName.slice(LABEL_PROTOCOL_PREFIX.length)}`;
|
|
3284
|
+
}
|
|
3285
|
+
const bits = [`"${key.protocolName}" (level ${key.protocolLevel}`];
|
|
3286
|
+
if (key.protocolLevel !== 1 && key.counterparty) {
|
|
3287
|
+
bits.push(`, counterparty ${key.counterparty}`);
|
|
3288
|
+
}
|
|
3289
|
+
if (key.privileged)
|
|
3290
|
+
bits.push(", privileged");
|
|
3291
|
+
return `protocol ${bits.join("")})`;
|
|
3292
|
+
}
|
|
3293
|
+
case "basket":
|
|
3294
|
+
return `basket "${key.basket}"`;
|
|
3295
|
+
case "certificate":
|
|
3296
|
+
return `cert "${key.certType}" fields [${key.fields.join(", ")}]${key.verifier ? ` to ${key.verifier}` : ""}${key.privileged ? " (privileged)" : ""}`;
|
|
3297
|
+
case "spending":
|
|
3298
|
+
return `spending up to ${spec.authorizedAmount ?? 0} sat per month`;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
function specsFromFlags(origin, flags) {
|
|
3302
|
+
const originator = normalizeOriginator(origin);
|
|
3303
|
+
if (!originator)
|
|
3304
|
+
throw new Error("An app origin is required");
|
|
3305
|
+
const specs = [];
|
|
3306
|
+
if (flags.protocol !== undefined || flags.label !== undefined) {
|
|
3307
|
+
if (flags.protocol !== undefined && flags.label !== undefined) {
|
|
3308
|
+
throw new Error("Use either --protocol or --label, not both");
|
|
3309
|
+
}
|
|
3310
|
+
const isLabel = flags.label !== undefined;
|
|
3311
|
+
const name = isLabel ? `${LABEL_PROTOCOL_PREFIX}${flags.label}` : flags.protocol;
|
|
3312
|
+
if (!name.trim() || name === LABEL_PROTOCOL_PREFIX) {
|
|
3313
|
+
throw new Error(isLabel ? "--label requires a name" : "--protocol requires a name");
|
|
3314
|
+
}
|
|
3315
|
+
const level = isLabel ? 1 : parseLevel(flags.level);
|
|
3316
|
+
specs.push({
|
|
3317
|
+
key: {
|
|
3318
|
+
type: "protocol",
|
|
3319
|
+
originator,
|
|
3320
|
+
privileged: flags.privileged,
|
|
3321
|
+
protocolLevel: level,
|
|
3322
|
+
protocolName: name,
|
|
3323
|
+
counterparty: level === 1 ? "" : flags.counterparty ?? "self"
|
|
3324
|
+
}
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
if (flags.basket !== undefined) {
|
|
3328
|
+
if (!flags.basket.trim())
|
|
3329
|
+
throw new Error("--basket requires a name");
|
|
3330
|
+
specs.push({
|
|
3331
|
+
key: { type: "basket", originator, basket: flags.basket }
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
3334
|
+
if (flags.certificate !== undefined) {
|
|
3335
|
+
if (!flags.certificate.trim()) {
|
|
3336
|
+
throw new Error("--certificate requires a certificate type");
|
|
3337
|
+
}
|
|
3338
|
+
const fields = (flags.fields ?? "").split(",").map((f) => f.trim()).filter((f) => f.length > 0);
|
|
3339
|
+
if (fields.length === 0) {
|
|
3340
|
+
throw new Error("--certificate requires --fields <a,b>");
|
|
3341
|
+
}
|
|
3342
|
+
specs.push({
|
|
3343
|
+
key: {
|
|
3344
|
+
type: "certificate",
|
|
3345
|
+
originator,
|
|
3346
|
+
privileged: flags.privileged,
|
|
3347
|
+
verifier: flags.counterparty ?? "",
|
|
3348
|
+
certType: flags.certificate,
|
|
3349
|
+
fields: fields.sort()
|
|
3350
|
+
}
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
if (flags.spending !== undefined) {
|
|
3354
|
+
const satoshis = Number(flags.spending);
|
|
3355
|
+
if (!Number.isInteger(satoshis) || satoshis <= 0) {
|
|
3356
|
+
throw new Error("--spending requires a positive whole number of satoshis");
|
|
3357
|
+
}
|
|
3358
|
+
specs.push({
|
|
3359
|
+
key: { type: "spending", originator },
|
|
3360
|
+
authorizedAmount: satoshis
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
if (specs.length === 0) {
|
|
3364
|
+
throw new Error("Nothing selected. Pass --protocol/--label, --basket, --certificate or --spending");
|
|
3365
|
+
}
|
|
3366
|
+
return specs;
|
|
3367
|
+
}
|
|
3368
|
+
function parseLevel(value) {
|
|
3369
|
+
if (value === undefined) {
|
|
3370
|
+
throw new Error("--protocol requires --level <0|1|2>");
|
|
3371
|
+
}
|
|
3372
|
+
if (value !== "0" && value !== "1" && value !== "2") {
|
|
3373
|
+
throw new Error(`--level must be 0, 1 or 2 (got ${value})`);
|
|
3374
|
+
}
|
|
3375
|
+
return Number(value);
|
|
3376
|
+
}
|
|
3377
|
+
function specId(spec) {
|
|
3378
|
+
return permissionKeyToString2(spec.key);
|
|
3379
|
+
}
|
|
3380
|
+
var LABEL_PROTOCOL_PREFIX = "action label ", GRANT_COMMAND = "1sat permissions grant";
|
|
3381
|
+
var init_grants = () => {};
|
|
3382
|
+
|
|
3383
|
+
// src/commands/permissions.ts
|
|
3384
|
+
import { normalizeOriginator as normalizeOriginator2 } from "@1sat/wallet";
|
|
3385
|
+
async function handlePermissionsCommand(args, opts) {
|
|
3386
|
+
const [subcommand, ...rest] = args;
|
|
3387
|
+
switch (subcommand) {
|
|
3388
|
+
case "list":
|
|
3389
|
+
case "ls":
|
|
3390
|
+
return permissionsList(rest, opts);
|
|
3391
|
+
case "grant":
|
|
3392
|
+
return permissionsGrant(rest, opts);
|
|
3393
|
+
case "revoke":
|
|
3394
|
+
return permissionsRevoke(rest, opts);
|
|
3395
|
+
default:
|
|
3396
|
+
printCommandHelp("permissions", opts.json);
|
|
3397
|
+
if (subcommand && subcommand !== "help") {
|
|
3398
|
+
process.exit(1);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
function openStore(opts) {
|
|
3403
|
+
const path = permissionStorePath(ensureDataDir(), opts.chain);
|
|
3404
|
+
return { store: new FilePermissionStore(path), path };
|
|
3405
|
+
}
|
|
3406
|
+
function commandPrefix(opts) {
|
|
3407
|
+
return opts.chain === "test" ? "1sat --chain test permissions grant" : "1sat permissions grant";
|
|
3408
|
+
}
|
|
3409
|
+
function grantFlags(args) {
|
|
3410
|
+
return {
|
|
3411
|
+
protocol: extractFlag(args, "--protocol"),
|
|
3412
|
+
level: extractFlag(args, "--level"),
|
|
3413
|
+
counterparty: extractFlag(args, "--counterparty") ?? extractFlag(args, "--verifier"),
|
|
3414
|
+
privileged: hasFlag(args, "--privileged"),
|
|
3415
|
+
basket: extractFlag(args, "--basket"),
|
|
3416
|
+
label: extractFlag(args, "--label"),
|
|
3417
|
+
certificate: extractFlag(args, "--certificate"),
|
|
3418
|
+
fields: extractFlag(args, "--fields"),
|
|
3419
|
+
spending: extractFlag(args, "--spending")
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
function firstPositional(args) {
|
|
3423
|
+
for (let i = 0;i < args.length; i++) {
|
|
3424
|
+
const arg = args[i];
|
|
3425
|
+
if (arg.startsWith("--")) {
|
|
3426
|
+
if (!BOOLEAN_FLAGS.has(arg))
|
|
3427
|
+
i++;
|
|
3428
|
+
continue;
|
|
3429
|
+
}
|
|
3430
|
+
return arg;
|
|
3431
|
+
}
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
function toSpec(grant) {
|
|
3435
|
+
return grant.authorizedAmount != null ? { key: grant.key, authorizedAmount: grant.authorizedAmount } : { key: grant.key };
|
|
3436
|
+
}
|
|
3437
|
+
async function permissionsList(args, opts) {
|
|
3438
|
+
const { store, path } = openStore(opts);
|
|
3439
|
+
const origin = firstPositional(args);
|
|
3440
|
+
const filter = origin ? { originator: normalizeOriginator2(origin) } : {};
|
|
3441
|
+
const grants = await store.listGrants(filter);
|
|
3442
|
+
if (opts.json) {
|
|
3443
|
+
output(grants, opts);
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
if (grants.length === 0) {
|
|
3447
|
+
output(origin ? `No grants for ${normalizeOriginator2(origin)} in ${path}` : `No grants in ${path}`, opts);
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
const byOrigin = new Map;
|
|
3451
|
+
for (const grant of grants) {
|
|
3452
|
+
const list = byOrigin.get(grant.key.originator) ?? [];
|
|
3453
|
+
list.push(grant);
|
|
3454
|
+
byOrigin.set(grant.key.originator, list);
|
|
3455
|
+
}
|
|
3456
|
+
for (const name of [...byOrigin.keys()].sort()) {
|
|
3457
|
+
console.log(`
|
|
3458
|
+
${formatValue(name)}`);
|
|
3459
|
+
for (const grant of byOrigin.get(name) ?? []) {
|
|
3460
|
+
const expiry = grant.expiry > 0 ? ` expires ${new Date(grant.expiry * 1000).toISOString()}` : "";
|
|
3461
|
+
console.log(` ${describeKey(toSpec(grant))}${formatLabel(expiry)}${formatLabel(grant.reason ? ` — ${grant.reason}` : "")}`);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
console.log(`
|
|
3465
|
+
${grants.length} grant(s) in ${path}`);
|
|
3466
|
+
}
|
|
3467
|
+
async function permissionsGrant(args, opts) {
|
|
3468
|
+
const origin = firstPositional(args);
|
|
3469
|
+
if (!origin)
|
|
3470
|
+
fatal("Missing <origin>. See `1sat permissions --help`");
|
|
3471
|
+
let specs;
|
|
3472
|
+
try {
|
|
3473
|
+
specs = specsFromFlags(origin, grantFlags(args));
|
|
3474
|
+
} catch (err) {
|
|
3475
|
+
return fatal(err.message);
|
|
3476
|
+
}
|
|
3477
|
+
const { store, path } = openStore(opts);
|
|
3478
|
+
const now = Date.now();
|
|
3479
|
+
for (const spec of specs) {
|
|
3480
|
+
await store.putGrant({
|
|
3481
|
+
key: spec.key,
|
|
3482
|
+
expiry: 0,
|
|
3483
|
+
grantedAt: now,
|
|
3484
|
+
...spec.authorizedAmount != null ? { authorizedAmount: spec.authorizedAmount } : {}
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
if (opts.json) {
|
|
3488
|
+
output({
|
|
3489
|
+
granted: specs.map((spec) => ({
|
|
3490
|
+
id: specId(spec),
|
|
3491
|
+
key: spec.key,
|
|
3492
|
+
authorizedAmount: spec.authorizedAmount
|
|
3493
|
+
})),
|
|
3494
|
+
store: path
|
|
3495
|
+
}, opts);
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
for (const spec of specs) {
|
|
3499
|
+
console.log(`granted ${describeKey(spec)}`);
|
|
3500
|
+
}
|
|
3501
|
+
console.log(`
|
|
3502
|
+
${specs.length} grant(s) written to ${path}. A running \`1sat serve wallet-api\` picks them up on the app's next call.`);
|
|
3503
|
+
}
|
|
3504
|
+
async function permissionsRevoke(args, opts) {
|
|
3505
|
+
const origin = firstPositional(args);
|
|
3506
|
+
if (!origin)
|
|
3507
|
+
fatal("Missing <origin>. See `1sat permissions --help`");
|
|
3508
|
+
const originator = normalizeOriginator2(origin);
|
|
3509
|
+
const { store, path } = openStore(opts);
|
|
3510
|
+
if (hasFlag(args, "--all")) {
|
|
3511
|
+
const removed2 = await store.deleteAllForOriginator(originator);
|
|
3512
|
+
if (opts.json) {
|
|
3513
|
+
output({ originator, removed: removed2, store: path }, opts);
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
output(`revoked ${removed2} grant(s) for ${originator}`, opts);
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
let specs;
|
|
3520
|
+
try {
|
|
3521
|
+
specs = specsFromFlags(origin, grantFlags(args));
|
|
3522
|
+
} catch (err) {
|
|
3523
|
+
return fatal(`${err.message}. Pass --all to revoke everything for an origin.`);
|
|
3524
|
+
}
|
|
3525
|
+
const removed = [];
|
|
3526
|
+
const missing = [];
|
|
3527
|
+
for (const spec of specs) {
|
|
3528
|
+
if (await store.findGrant(spec.key)) {
|
|
3529
|
+
await store.deleteGrant(spec.key);
|
|
3530
|
+
removed.push(spec);
|
|
3531
|
+
} else {
|
|
3532
|
+
missing.push(spec);
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
if (opts.json) {
|
|
3536
|
+
output({
|
|
3537
|
+
revoked: removed.map((spec) => ({ id: specId(spec), key: spec.key })),
|
|
3538
|
+
notFound: missing.map((spec) => ({ id: specId(spec), key: spec.key })),
|
|
3539
|
+
store: path
|
|
3540
|
+
}, opts);
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
for (const spec of removed)
|
|
3544
|
+
console.log(`revoked ${describeKey(spec)}`);
|
|
3545
|
+
for (const spec of missing) {
|
|
3546
|
+
console.log(`no such grant: ${describeKey(spec)} (grant it with \`${grantCommand(spec, commandPrefix(opts))}\`)`);
|
|
3547
|
+
}
|
|
3548
|
+
console.log(`
|
|
3549
|
+
${removed.length} grant(s) removed from ${path}`);
|
|
3550
|
+
}
|
|
3551
|
+
var BOOLEAN_FLAGS;
|
|
3552
|
+
var init_permissions = __esm(() => {
|
|
3553
|
+
init_config();
|
|
3554
|
+
init_help();
|
|
3555
|
+
init_output();
|
|
3556
|
+
init_grants();
|
|
3557
|
+
init_permission_store();
|
|
3558
|
+
BOOLEAN_FLAGS = new Set(["--privileged", "--all"]);
|
|
3559
|
+
});
|
|
3560
|
+
|
|
3048
3561
|
// src/commands/remote.ts
|
|
3049
3562
|
import { StorageClient } from "@1sat/wallet-node";
|
|
3050
3563
|
import { WalletServerClient, topUpStorage } from "@1sat/wallet-server";
|
|
@@ -3533,8 +4046,135 @@ var init_repricer = __esm(() => {
|
|
|
3533
4046
|
init_providers();
|
|
3534
4047
|
});
|
|
3535
4048
|
|
|
4049
|
+
// src/wallet-api/deny.ts
|
|
4050
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4051
|
+
import {
|
|
4052
|
+
normalizeOriginator as normalizeOriginator3,
|
|
4053
|
+
permissionKeyFromRequest,
|
|
4054
|
+
permissionKeysFromGroup
|
|
4055
|
+
} from "@1sat/wallet";
|
|
4056
|
+
function bindPermissionDenials(manager, options = {}) {
|
|
4057
|
+
const prefix = options.grantCommandPrefix ?? GRANT_COMMAND;
|
|
4058
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
4059
|
+
const record = (origin, commands) => {
|
|
4060
|
+
const message = denialMessage(origin, commands);
|
|
4061
|
+
inFlight.getStore()?.push(message);
|
|
4062
|
+
log(`[wallet-api] ${message}`);
|
|
4063
|
+
};
|
|
4064
|
+
const single = async (request) => {
|
|
4065
|
+
const origin = displayOrigin(request);
|
|
4066
|
+
try {
|
|
4067
|
+
record(origin, [grantCommand(specFromRequest(request), prefix)]);
|
|
4068
|
+
} catch (err) {
|
|
4069
|
+
log(`[wallet-api] could not describe request: ${err.message}`);
|
|
4070
|
+
}
|
|
4071
|
+
await safely(log, "deny", () => manager.denyPermission(request.requestID));
|
|
4072
|
+
};
|
|
4073
|
+
const grouped = async (request) => {
|
|
4074
|
+
const origin = normalizeOriginator3(request.originator);
|
|
4075
|
+
try {
|
|
4076
|
+
record(origin, specsFromGroupedRequest(request).map((spec) => grantCommand(spec, prefix)));
|
|
4077
|
+
} catch (err) {
|
|
4078
|
+
log(`[wallet-api] could not describe request: ${err.message}`);
|
|
4079
|
+
}
|
|
4080
|
+
await safely(log, "deny", () => manager.denyGroupedPermission(request.requestID));
|
|
4081
|
+
};
|
|
4082
|
+
manager.bindCallback("onProtocolPermissionRequested", single);
|
|
4083
|
+
manager.bindCallback("onBasketAccessRequested", single);
|
|
4084
|
+
manager.bindCallback("onCertificateAccessRequested", single);
|
|
4085
|
+
manager.bindCallback("onSpendingAuthorizationRequested", single);
|
|
4086
|
+
manager.bindCallback("onGroupedPermissionRequested", grouped);
|
|
4087
|
+
}
|
|
4088
|
+
async function withDenialMessage(fn) {
|
|
4089
|
+
const messages = [];
|
|
4090
|
+
try {
|
|
4091
|
+
return await inFlight.run(messages, fn);
|
|
4092
|
+
} catch (err) {
|
|
4093
|
+
const code = err?.code;
|
|
4094
|
+
if (code !== DENIED_CODE || messages.length === 0)
|
|
4095
|
+
throw err;
|
|
4096
|
+
const denied = new Error(messages.join(" "));
|
|
4097
|
+
denied.code = DENIED_CODE;
|
|
4098
|
+
throw denied;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
function denialMessage(origin, commands) {
|
|
4102
|
+
const quoted = commands.map((c) => `\`${c}\``);
|
|
4103
|
+
const list = quoted.length > 1 ? `${quoted.slice(0, -1).join(", ")} and ${quoted[quoted.length - 1]}` : quoted[0];
|
|
4104
|
+
return `permission denied for ${origin}: run ${list} and retry`;
|
|
4105
|
+
}
|
|
4106
|
+
function specFromRequest(request) {
|
|
4107
|
+
const key = permissionKeyFromRequest(request);
|
|
4108
|
+
return key.type === "spending" ? { key, authorizedAmount: request.spending?.satoshis ?? 0 } : { key };
|
|
4109
|
+
}
|
|
4110
|
+
function specsFromGroupedRequest(request) {
|
|
4111
|
+
const amount = request.permissions.spendingAuthorization?.amount ?? 0;
|
|
4112
|
+
return permissionKeysFromGroup(request.originator, request.permissions).map((key) => key.type === "spending" ? { key, authorizedAmount: amount } : { key });
|
|
4113
|
+
}
|
|
4114
|
+
function displayOrigin(request) {
|
|
4115
|
+
return normalizeOriginator3(request.originator);
|
|
4116
|
+
}
|
|
4117
|
+
async function safely(log, what, fn) {
|
|
4118
|
+
try {
|
|
4119
|
+
await fn();
|
|
4120
|
+
} catch (err) {
|
|
4121
|
+
log(`[wallet-api] ${what} failed: ${err.message}`);
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
var inFlight, DENIED_CODE = "ERR_PERMISSION_DENIED";
|
|
4125
|
+
var init_deny = __esm(() => {
|
|
4126
|
+
init_grants();
|
|
4127
|
+
inFlight = new AsyncLocalStorage;
|
|
4128
|
+
});
|
|
4129
|
+
|
|
4130
|
+
// src/wallet-api/endpoint.ts
|
|
4131
|
+
import { LocalWalletPermissionsManager as LocalWalletPermissionsManager2 } from "@1sat/wallet";
|
|
4132
|
+
import {
|
|
4133
|
+
startBRC100Server
|
|
4134
|
+
} from "@1sat/wallet-server";
|
|
4135
|
+
function walletApiHandle(wallet) {
|
|
4136
|
+
return {
|
|
4137
|
+
async call(method, args, origin) {
|
|
4138
|
+
const fn = wallet[method];
|
|
4139
|
+
if (typeof fn !== "function") {
|
|
4140
|
+
throw new Error(`Method not available: ${method}`);
|
|
4141
|
+
}
|
|
4142
|
+
return withDenialMessage(() => fn.call(wallet, args, origin));
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
async function startWalletApi(options) {
|
|
4147
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
4148
|
+
const manager = new LocalWalletPermissionsManager2(options.wallet, CLI_ADMIN_ORIGINATOR, { ...options.managerConfig }, { store: new FilePermissionStore(options.storePath) });
|
|
4149
|
+
bindPermissionDenials(manager, {
|
|
4150
|
+
grantCommandPrefix: options.grantCommandPrefix,
|
|
4151
|
+
log
|
|
4152
|
+
});
|
|
4153
|
+
const server = await startBRC100Server({
|
|
4154
|
+
host: options.host,
|
|
4155
|
+
port: options.port,
|
|
4156
|
+
adminOriginator: CLI_ADMIN_ORIGINATOR,
|
|
4157
|
+
wallet: walletApiHandle(manager),
|
|
4158
|
+
onEvent: (event) => {
|
|
4159
|
+
if (event.event !== "brc100_call")
|
|
4160
|
+
return;
|
|
4161
|
+
const line = `[wallet-api] ${event.method} from ${event.origin || "(no origin)"} -> ${event.status}`;
|
|
4162
|
+
log(event.error ? `${line}: ${event.error}` : line);
|
|
4163
|
+
}
|
|
4164
|
+
});
|
|
4165
|
+
return {
|
|
4166
|
+
server,
|
|
4167
|
+
close: () => server.close()
|
|
4168
|
+
};
|
|
4169
|
+
}
|
|
4170
|
+
var init_endpoint = __esm(() => {
|
|
4171
|
+
init_admin();
|
|
4172
|
+
init_deny();
|
|
4173
|
+
init_permission_store();
|
|
4174
|
+
});
|
|
4175
|
+
|
|
3536
4176
|
// src/commands/serve.ts
|
|
3537
|
-
import { join as
|
|
4177
|
+
import { join as join6 } from "node:path";
|
|
3538
4178
|
import {
|
|
3539
4179
|
createNodeWallet as createNodeWallet3
|
|
3540
4180
|
} from "@1sat/wallet-node";
|
|
@@ -3543,8 +4183,7 @@ import {
|
|
|
3543
4183
|
KnexHandleCertStore,
|
|
3544
4184
|
KnexPendingStore,
|
|
3545
4185
|
createHostServer,
|
|
3546
|
-
createWalletServer
|
|
3547
|
-
startBRC100Server
|
|
4186
|
+
createWalletServer
|
|
3548
4187
|
} from "@1sat/wallet-server";
|
|
3549
4188
|
import { initLogger as initLogger2 } from "evlog";
|
|
3550
4189
|
import knexLib from "knex";
|
|
@@ -3620,7 +4259,7 @@ async function resolveServe(opts) {
|
|
|
3620
4259
|
};
|
|
3621
4260
|
}
|
|
3622
4261
|
function deriveSqliteFilename(dataDir, chain) {
|
|
3623
|
-
return
|
|
4262
|
+
return join6(dataDir, `wallet-${chain}.db`);
|
|
3624
4263
|
}
|
|
3625
4264
|
function resolveMonitorEnabled(configured) {
|
|
3626
4265
|
const fromEnv = process.env.ONESAT_MONITOR;
|
|
@@ -3674,7 +4313,7 @@ async function runWithStorage(resolved, mode) {
|
|
|
3674
4313
|
skipInitialMonitor: true
|
|
3675
4314
|
});
|
|
3676
4315
|
if (mode === "wallet-api") {
|
|
3677
|
-
const dapp = await startDappEndpoint(walletResult);
|
|
4316
|
+
const dapp = await startDappEndpoint(walletResult, resolved);
|
|
3678
4317
|
return {
|
|
3679
4318
|
stop: async () => {
|
|
3680
4319
|
await dapp.close();
|
|
@@ -3745,7 +4384,7 @@ async function startWalletServer(resolved, walletResult, accounts, mode) {
|
|
|
3745
4384
|
ttlSeconds: sessionStoreCfg.ttlSeconds
|
|
3746
4385
|
} : undefined;
|
|
3747
4386
|
if (mode === "wallet") {
|
|
3748
|
-
const
|
|
4387
|
+
const handle2 = createWalletServer({
|
|
3749
4388
|
wallet: walletResult.wallet,
|
|
3750
4389
|
storage: walletResult.getActiveStorage(),
|
|
3751
4390
|
serverIdentityKey: walletResult.wallet.identityKey,
|
|
@@ -3755,10 +4394,10 @@ async function startWalletServer(resolved, walletResult, accounts, mode) {
|
|
|
3755
4394
|
accounts: accounts?.walletServerAccounts,
|
|
3756
4395
|
sessionStore
|
|
3757
4396
|
});
|
|
3758
|
-
const
|
|
3759
|
-
const
|
|
3760
|
-
console.log(`[wallet] listening on ${resolved.host}:${
|
|
3761
|
-
return { stop: () =>
|
|
4397
|
+
const port2 = await handle2.start();
|
|
4398
|
+
const notes2 = [resolved.accounts.enabled ? "accounts: on" : ""].filter(Boolean).join(", ");
|
|
4399
|
+
console.log(`[wallet] listening on ${resolved.host}:${port2}${notes2 ? ` (${notes2})` : ""}`);
|
|
4400
|
+
return { stop: () => handle2.stop() };
|
|
3762
4401
|
}
|
|
3763
4402
|
const serverCfg = config.server ?? {};
|
|
3764
4403
|
const paymailCfg = serverCfg.paymail;
|
|
@@ -3812,7 +4451,7 @@ async function startWalletServer(resolved, walletResult, accounts, mode) {
|
|
|
3812
4451
|
}
|
|
3813
4452
|
function createMessageboxKnex(storage, messagebox, resolved) {
|
|
3814
4453
|
if (storage.provider === "bun-sqlite") {
|
|
3815
|
-
const filename = messagebox?.dbPath ??
|
|
4454
|
+
const filename = messagebox?.dbPath ?? join6(resolved.dataDir, `messagebox-${resolved.chain}.db`);
|
|
3816
4455
|
return knexLib({
|
|
3817
4456
|
client: "sqlite3",
|
|
3818
4457
|
connection: { filename },
|
|
@@ -3870,50 +4509,24 @@ function waitForShutdown() {
|
|
|
3870
4509
|
process.once("SIGTERM", () => handler("SIGTERM"));
|
|
3871
4510
|
});
|
|
3872
4511
|
}
|
|
3873
|
-
function
|
|
3874
|
-
return {
|
|
3875
|
-
async call(method, args, origin) {
|
|
3876
|
-
const iface = wallet;
|
|
3877
|
-
const fn = iface[method];
|
|
3878
|
-
if (typeof fn !== "function") {
|
|
3879
|
-
throw new Error(`Method not available: ${method}`);
|
|
3880
|
-
}
|
|
3881
|
-
return await fn.call(iface, args, origin);
|
|
3882
|
-
}
|
|
3883
|
-
};
|
|
3884
|
-
}
|
|
3885
|
-
async function startDappEndpoint(walletResult) {
|
|
4512
|
+
async function startDappEndpoint(walletResult, resolved) {
|
|
3886
4513
|
const config = loadConfig();
|
|
3887
4514
|
const dapp = config.server?.dapp ?? {};
|
|
3888
4515
|
const host = dapp.host ?? DEFAULT_DAPP_HOST;
|
|
3889
4516
|
const port = dapp.port ?? (process.env.ONESAT_DAPP_PORT ? Number(process.env.ONESAT_DAPP_PORT) : DEFAULT_DAPP_PORT);
|
|
3890
|
-
const
|
|
3891
|
-
const
|
|
4517
|
+
const storePath = permissionStorePath(resolved.dataDir, resolved.chain);
|
|
4518
|
+
const grantCommandPrefix = resolved.chain === "test" ? "1sat --chain test permissions grant" : GRANT_COMMAND;
|
|
4519
|
+
const api = await startWalletApi({
|
|
4520
|
+
wallet: walletResult.wallet,
|
|
4521
|
+
storePath,
|
|
3892
4522
|
host,
|
|
3893
4523
|
port,
|
|
3894
|
-
|
|
3895
|
-
approvalPolicy: approve ? async ({ method, origin, args }) => {
|
|
3896
|
-
const { confirm, isCancel } = await import("@clack/prompts");
|
|
3897
|
-
console.log(`
|
|
3898
|
-
[brc-100] ${method} requested by ${origin}`);
|
|
3899
|
-
console.log(` ${JSON.stringify(args).slice(0, 400)}`);
|
|
3900
|
-
const ok = await confirm({
|
|
3901
|
-
message: `Approve ${method}?`
|
|
3902
|
-
});
|
|
3903
|
-
if (isCancel(ok) || !ok) {
|
|
3904
|
-
throw new Error("User denied permission");
|
|
3905
|
-
}
|
|
3906
|
-
} : undefined,
|
|
3907
|
-
onEvent: (event) => {
|
|
3908
|
-
if (event.event === "brc100_sensitive") {
|
|
3909
|
-
const trustNote = event.trusted ? " (trusted)" : approve ? "" : " (auto-approved)";
|
|
3910
|
-
console.log(`[brc-100] sensitive ${event.method} from ${event.origin}${trustNote}`);
|
|
3911
|
-
}
|
|
3912
|
-
}
|
|
4524
|
+
grantCommandPrefix
|
|
3913
4525
|
});
|
|
3914
4526
|
console.log(`[wallet-api] BRC-100 app endpoint on http://${host}:${port}`);
|
|
3915
|
-
console.log(
|
|
3916
|
-
|
|
4527
|
+
console.log(`[wallet-api] permission grants: ${storePath}`);
|
|
4528
|
+
console.log("[wallet-api] ungranted requests are denied; the error names the `1sat permissions grant` command that allows them");
|
|
4529
|
+
return { close: () => api.close() };
|
|
3917
4530
|
}
|
|
3918
4531
|
var DEFAULT_HOST = "127.0.0.1", DEFAULT_PORT = 8100, DEFAULT_STACK_URL = "https://api.1sat.app", DEFAULT_BASELINE_BYTES, DEFAULT_PURCHASE_UNIT_BYTES = 1073741824, DEFAULT_SATS_PER_UNIT = 1e6, DEFAULT_DURATION_BLOCKS = 4383, DEFAULT_STORAGE_IDENTITY_KEY = "1sat-cli-default", DEFAULT_PG_SCHEMA = "messagebox", DEFAULT_DAPP_HOST = "127.0.0.1", DEFAULT_DAPP_PORT = 3321;
|
|
3919
4532
|
var init_serve = __esm(() => {
|
|
@@ -3924,6 +4537,9 @@ var init_serve = __esm(() => {
|
|
|
3924
4537
|
init_monitor_lock();
|
|
3925
4538
|
init_output();
|
|
3926
4539
|
init_repricer();
|
|
4540
|
+
init_endpoint();
|
|
4541
|
+
init_grants();
|
|
4542
|
+
init_permission_store();
|
|
3927
4543
|
DEFAULT_BASELINE_BYTES = 1024 * 1024 * 1024;
|
|
3928
4544
|
});
|
|
3929
4545
|
|
|
@@ -3978,7 +4594,7 @@ var init_social = __esm(() => {
|
|
|
3978
4594
|
});
|
|
3979
4595
|
|
|
3980
4596
|
// src/commands/storage.ts
|
|
3981
|
-
import { join as
|
|
4597
|
+
import { join as join7 } from "node:path";
|
|
3982
4598
|
import { StorageBunSqlite, nominateInvalidReqs } from "@1sat/wallet-node";
|
|
3983
4599
|
import { StorageProvider } from "@bsv/wallet-toolbox";
|
|
3984
4600
|
async function handleStorageCommand(args, opts) {
|
|
@@ -4048,7 +4664,7 @@ async function openServeStorage(opts) {
|
|
|
4048
4664
|
} else {
|
|
4049
4665
|
storage = new StorageBunSqlite({
|
|
4050
4666
|
...baseOptions,
|
|
4051
|
-
filename:
|
|
4667
|
+
filename: join7(ensureDataDir(), `wallet-${chain}.db`)
|
|
4052
4668
|
});
|
|
4053
4669
|
}
|
|
4054
4670
|
await storage.makeAvailable();
|
|
@@ -4081,11 +4697,11 @@ function selectedSweepClasses(only, skip) {
|
|
|
4081
4697
|
throw new Error("--only and --skip are mutually exclusive");
|
|
4082
4698
|
}
|
|
4083
4699
|
if (only) {
|
|
4084
|
-
const
|
|
4085
|
-
if (
|
|
4700
|
+
const selected2 = parseSweepClasses(only, "--only");
|
|
4701
|
+
if (selected2.size === 0) {
|
|
4086
4702
|
throw new Error("--only requires at least one class");
|
|
4087
4703
|
}
|
|
4088
|
-
return
|
|
4704
|
+
return selected2;
|
|
4089
4705
|
}
|
|
4090
4706
|
const selected = new Set(SWEEP_CLASSES);
|
|
4091
4707
|
if (skip) {
|
|
@@ -5188,8 +5804,8 @@ async function walletListOutputs(args, opts) {
|
|
|
5188
5804
|
${result.totalOutputs} total outputs in basket '${basket}':
|
|
5189
5805
|
`);
|
|
5190
5806
|
for (const out of result.outputs) {
|
|
5191
|
-
const
|
|
5192
|
-
console.log(` ${out.outpoint} | ${out.satoshis} sats | tags: ${
|
|
5807
|
+
const tags2 = out.tags?.join(", ") || "none";
|
|
5808
|
+
console.log(` ${out.outpoint} | ${out.satoshis} sats | tags: ${tags2}`);
|
|
5193
5809
|
}
|
|
5194
5810
|
console.log();
|
|
5195
5811
|
}
|
|
@@ -5447,13 +6063,14 @@ var init_wallet = __esm(() => {
|
|
|
5447
6063
|
});
|
|
5448
6064
|
|
|
5449
6065
|
// src/main.ts
|
|
5450
|
-
|
|
6066
|
+
var exports_main = {};
|
|
6067
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
5451
6068
|
import { resolve } from "node:path";
|
|
5452
6069
|
import { config as loadEnv } from "dotenv";
|
|
5453
6070
|
function applyEnvFiles(paths) {
|
|
5454
6071
|
for (const p of paths) {
|
|
5455
6072
|
const abs = resolve(p);
|
|
5456
|
-
if (!
|
|
6073
|
+
if (!existsSync5(abs)) {
|
|
5457
6074
|
throw new Error(`Env file not found: ${p}`);
|
|
5458
6075
|
}
|
|
5459
6076
|
const result = loadEnv({ path: abs, quiet: true, override: true });
|
|
@@ -5537,6 +6154,9 @@ async function main() {
|
|
|
5537
6154
|
case "serve":
|
|
5538
6155
|
await handleServeCommand(rest, flags);
|
|
5539
6156
|
break;
|
|
6157
|
+
case "permissions":
|
|
6158
|
+
await handlePermissionsCommand(rest, flags);
|
|
6159
|
+
break;
|
|
5540
6160
|
case "storage":
|
|
5541
6161
|
await handleStorageCommand(rest, flags);
|
|
5542
6162
|
break;
|
|
@@ -5564,6 +6184,7 @@ var init_main = __esm(() => {
|
|
|
5564
6184
|
init_messagebox();
|
|
5565
6185
|
init_opns();
|
|
5566
6186
|
init_ordinals();
|
|
6187
|
+
init_permissions();
|
|
5567
6188
|
init_remote();
|
|
5568
6189
|
init_serve();
|
|
5569
6190
|
init_social();
|
|
@@ -5586,4 +6207,4 @@ var init_main = __esm(() => {
|
|
|
5586
6207
|
|
|
5587
6208
|
// src/cli.ts
|
|
5588
6209
|
process.env.DOTENV_CONFIG_QUIET = "true";
|
|
5589
|
-
await Promise.resolve().then(() => (init_main(),
|
|
6210
|
+
await Promise.resolve().then(() => (init_main(), exports_main));
|