@getalby/cli 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -110
- package/build/amount.js +141 -0
- package/build/commands/auth.js +11 -7
- package/build/commands/connect.js +2 -1
- package/build/commands/fetch.js +50 -1
- package/build/commands/fiat-to-sats.js +2 -1
- package/build/commands/list-wallets.js +12 -0
- package/build/commands/make-hold-invoice.js +15 -3
- package/build/commands/make-invoice.js +16 -3
- package/build/commands/pay-crypto.js +58 -0
- package/build/commands/pay-invoice.js +24 -6
- package/build/commands/pay-keysend.js +19 -3
- package/build/commands/pay.js +237 -0
- package/build/commands/receive.js +56 -0
- package/build/commands/request-invoice-from-lightning-address.js +15 -3
- package/build/commands/sats-to-fiat.js +23 -4
- package/build/index.js +16 -2
- package/build/lendaswap/swap.js +177 -0
- package/build/test/amount-model.test.js +111 -0
- package/build/test/connection-secret.test.js +2 -2
- package/build/test/fetch-max-amount.test.js +30 -0
- package/build/test/lightning-tools.test.js +17 -4
- package/build/test/list-wallets.test.js +75 -0
- package/build/test/nwc-hold-invoices.test.js +3 -5
- package/build/test/nwc-payments.test.js +5 -5
- package/build/test/pay-command.test.js +131 -0
- package/build/test/pay-crypto.test.js +175 -0
- package/build/test/receive-command.test.js +79 -0
- package/build/tools/lightning/discover.js +1 -1
- package/build/tools/lightning/fetch.js +1 -1
- package/build/utils.js +74 -12
- package/package.json +4 -3
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { createServer } from "http";
|
|
4
|
+
// Stand up a local HTTP mock for the Lendaswap API endpoints the CLI hits
|
|
5
|
+
// during validation. The CLI spawns a fresh subprocess per test, so the
|
|
6
|
+
// `LENDASWAP_API_URL` env var (consumed in src/lendaswap/swap.ts) points it
|
|
7
|
+
// at this mock instead of api.satora.io. We only need `/tokens` and
|
|
8
|
+
// `/swap-pairs` — pair validation runs before wallet load, so tests never
|
|
9
|
+
// reach the swap-creation endpoints.
|
|
10
|
+
//
|
|
11
|
+
// IMPORTANT: we use async `spawn`, not the shared `execSync`-based runCli
|
|
12
|
+
// helper. `execSync` blocks the event loop, which would prevent this
|
|
13
|
+
// in-process mock from accepting the subprocess's TCP connection — fetch
|
|
14
|
+
// would just time out.
|
|
15
|
+
let server;
|
|
16
|
+
let mockUrl = "";
|
|
17
|
+
const MOCK_TOKENS = {
|
|
18
|
+
btc_tokens: [],
|
|
19
|
+
evm_tokens: [
|
|
20
|
+
{
|
|
21
|
+
chain: "42161",
|
|
22
|
+
decimals: 6,
|
|
23
|
+
name: "USD Coin",
|
|
24
|
+
symbol: "USDC",
|
|
25
|
+
token_id: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
chain: "42161",
|
|
29
|
+
decimals: 6,
|
|
30
|
+
name: "Tether USD",
|
|
31
|
+
symbol: "USDT",
|
|
32
|
+
token_id: "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
// Only the Lightning → 42161 (Arbitrum) pair is enabled in the mock. So
|
|
37
|
+
// USDC/USDT on Arbitrum resolve; the same symbols on any other chain are
|
|
38
|
+
// rejected as unsupported.
|
|
39
|
+
const MOCK_SWAP_PAIRS = {
|
|
40
|
+
pairs: [
|
|
41
|
+
{
|
|
42
|
+
fee_percentage: 0.0025,
|
|
43
|
+
max_sats: 100_000_000,
|
|
44
|
+
min_sats: 1000,
|
|
45
|
+
source: "Lightning",
|
|
46
|
+
target: "42161",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
beforeAll(async () => {
|
|
51
|
+
server = createServer((req, res) => {
|
|
52
|
+
res.setHeader("Content-Type", "application/json");
|
|
53
|
+
if (req.url === "/tokens") {
|
|
54
|
+
res.end(JSON.stringify(MOCK_TOKENS));
|
|
55
|
+
}
|
|
56
|
+
else if (req.url === "/swap-pairs") {
|
|
57
|
+
res.end(JSON.stringify(MOCK_SWAP_PAIRS));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
res.statusCode = 404;
|
|
61
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
|
|
65
|
+
const addr = server.address();
|
|
66
|
+
mockUrl = `http://127.0.0.1:${addr.port}`;
|
|
67
|
+
});
|
|
68
|
+
afterAll(() => new Promise((resolve) => server.close(() => resolve())));
|
|
69
|
+
function runCliAsync(args) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const child = spawn("node", ["build/index.js", ...args.split(" ")], {
|
|
72
|
+
env: {
|
|
73
|
+
...process.env,
|
|
74
|
+
// Wallet load always fails — exposes the validation gates as the
|
|
75
|
+
// only thing that can succeed or fail before that point.
|
|
76
|
+
HOME: "/tmp/nonexistent-alby-cli-test-home",
|
|
77
|
+
NWC_URL: "",
|
|
78
|
+
LENDASWAP_API_URL: mockUrl,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
let stdout = "";
|
|
82
|
+
let stderr = "";
|
|
83
|
+
child.stdout.on("data", (d) => (stdout += d.toString()));
|
|
84
|
+
child.stderr.on("data", (d) => (stderr += d.toString()));
|
|
85
|
+
child.on("close", (code) => {
|
|
86
|
+
const raw = code === 0 ? stdout : stderr || stdout || "{}";
|
|
87
|
+
try {
|
|
88
|
+
resolve({ success: code === 0, output: JSON.parse(raw) });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
resolve({ success: code === 0, output: { error: raw } });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
describe("pay-crypto validation", () => {
|
|
97
|
+
describe("unsupported currency/network combination", () => {
|
|
98
|
+
test("unknown currency is rejected and lists supported pairs", async () => {
|
|
99
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 10 --currency XYZ --network arbitrum");
|
|
100
|
+
expect(result.success).toBe(false);
|
|
101
|
+
expect(result.output.error).toContain("Unsupported currency/network combination");
|
|
102
|
+
expect(result.output.error).toContain("USDC on Arbitrum");
|
|
103
|
+
});
|
|
104
|
+
test("USDC on ethereum is rejected (chain not in swap-pairs)", async () => {
|
|
105
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 10 --currency USDC --network ethereum");
|
|
106
|
+
expect(result.success).toBe(false);
|
|
107
|
+
expect(result.output.error).toContain("Unsupported currency/network combination");
|
|
108
|
+
expect(result.output.error).toContain("USDC on Arbitrum");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("malformed EVM address", () => {
|
|
112
|
+
test("completely non-hex string is rejected", async () => {
|
|
113
|
+
const result = await runCliAsync("pay-crypto notanaddress --amount 10 --currency USDC --network arbitrum");
|
|
114
|
+
expect(result.success).toBe(false);
|
|
115
|
+
expect(result.output.error).toContain("address does not look valid");
|
|
116
|
+
});
|
|
117
|
+
test("too-short hex with 0x prefix is rejected", async () => {
|
|
118
|
+
const result = await runCliAsync("pay-crypto 0xabc --amount 10 --currency USDC --network arbitrum");
|
|
119
|
+
expect(result.success).toBe(false);
|
|
120
|
+
expect(result.output.error).toContain("address does not look valid");
|
|
121
|
+
});
|
|
122
|
+
test("40-char hex without 0x prefix is rejected", async () => {
|
|
123
|
+
const result = await runCliAsync("pay-crypto 000000000000000000000000000000000000dead --amount 10 --currency USDC --network arbitrum");
|
|
124
|
+
expect(result.success).toBe(false);
|
|
125
|
+
expect(result.output.error).toContain("address does not look valid");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe("invalid amount", () => {
|
|
129
|
+
test("--amount 0 is rejected", async () => {
|
|
130
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 0 --currency USDC --network arbitrum");
|
|
131
|
+
expect(result.success).toBe(false);
|
|
132
|
+
expect(result.output.error).toContain("Amount must be a positive number");
|
|
133
|
+
});
|
|
134
|
+
test("--amount -1 is rejected", async () => {
|
|
135
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount -1 --currency USDC --network arbitrum");
|
|
136
|
+
expect(result.success).toBe(false);
|
|
137
|
+
expect(result.output.error).toContain("Amount must be a positive number");
|
|
138
|
+
});
|
|
139
|
+
test("--amount abc (NaN) is rejected", async () => {
|
|
140
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount abc --currency USDC --network arbitrum");
|
|
141
|
+
expect(result.success).toBe(false);
|
|
142
|
+
expect(result.output.error).toContain("Amount must be a positive number");
|
|
143
|
+
});
|
|
144
|
+
// Unit-suffixed input must not be truncated to its leading digits
|
|
145
|
+
// (Number("123usd") is NaN, unlike parseFloat which would yield 123).
|
|
146
|
+
test("--amount 123usd is rejected", async () => {
|
|
147
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 123usd --currency USDC --network arbitrum");
|
|
148
|
+
expect(result.success).toBe(false);
|
|
149
|
+
expect(result.output.error).toContain("Amount must be a positive number");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe("missing required options", () => {
|
|
153
|
+
test("missing --currency is rejected", async () => {
|
|
154
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 10 --network arbitrum");
|
|
155
|
+
expect(result.success).toBe(false);
|
|
156
|
+
expect(result.output.error).toContain("--currency");
|
|
157
|
+
});
|
|
158
|
+
test("missing --network is rejected", async () => {
|
|
159
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 10 --currency USDC");
|
|
160
|
+
expect(result.success).toBe(false);
|
|
161
|
+
expect(result.output.error).toContain("--network");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe("happy-path validation", () => {
|
|
165
|
+
test("valid USDC/arbitrum inputs get past validation and fail only at wallet load", async () => {
|
|
166
|
+
// The mocked supported list includes USDC on 42161 (Arbitrum), so
|
|
167
|
+
// findSupportedPair succeeds. With the wallet env disabled, the only
|
|
168
|
+
// error left is "No wallet connection found" from getClient() —
|
|
169
|
+
// proof that amount, address, and pair gates all accepted the input.
|
|
170
|
+
const result = await runCliAsync("pay-crypto 0x000000000000000000000000000000000000dead --amount 10 --currency USDC --network arbitrum");
|
|
171
|
+
expect(result.success).toBe(false);
|
|
172
|
+
expect(result.output.error).toContain("No wallet connection found");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll } from "vitest";
|
|
2
|
+
import { createTestWallet, runCli } from "./helpers.js";
|
|
3
|
+
describe("receive command — validation", () => {
|
|
4
|
+
test("--description without --amount is rejected", () => {
|
|
5
|
+
const result = runCli(`receive --description "hi"`);
|
|
6
|
+
expect(result.success).toBe(false);
|
|
7
|
+
expect(result.output.error).toContain("--description requires --amount");
|
|
8
|
+
});
|
|
9
|
+
test("--currency without --amount is rejected", () => {
|
|
10
|
+
const result = runCli(`receive --currency USD`);
|
|
11
|
+
expect(result.success).toBe(false);
|
|
12
|
+
expect(result.output.error).toContain("--currency requires --amount");
|
|
13
|
+
});
|
|
14
|
+
test("--amount 0 is rejected at parse time", () => {
|
|
15
|
+
const result = runCli(`receive --amount 0`);
|
|
16
|
+
expect(result.success).toBe(false);
|
|
17
|
+
expect(result.output.error).toContain("Amount must be a positive number");
|
|
18
|
+
});
|
|
19
|
+
test("--amount abc (NaN) is rejected at parse time", () => {
|
|
20
|
+
const result = runCli(`receive --amount abc`);
|
|
21
|
+
expect(result.success).toBe(false);
|
|
22
|
+
expect(result.output.error).toContain("Amount must be a positive number");
|
|
23
|
+
});
|
|
24
|
+
test("--amount without --currency is rejected", () => {
|
|
25
|
+
const result = runCli(`receive --amount 100 --network lightning`);
|
|
26
|
+
expect(result.success).toBe(false);
|
|
27
|
+
expect(result.output.error).toContain("--currency");
|
|
28
|
+
});
|
|
29
|
+
test("--amount --currency BTC without --unit is rejected", () => {
|
|
30
|
+
const result = runCli(`receive --amount 100 --currency BTC --network lightning`);
|
|
31
|
+
expect(result.success).toBe(false);
|
|
32
|
+
expect(result.output.error).toContain("--unit");
|
|
33
|
+
});
|
|
34
|
+
test("--unit on a fiat currency is rejected", () => {
|
|
35
|
+
const result = runCli(`receive --amount 5 --currency USD --unit sats --network lightning`);
|
|
36
|
+
expect(result.success).toBe(false);
|
|
37
|
+
expect(result.output.error).toContain("--unit is not valid");
|
|
38
|
+
});
|
|
39
|
+
test("a chain network is rejected (invoices are lightning-only)", () => {
|
|
40
|
+
const result = runCli(`receive --amount 10 --currency USDC --network arbitrum`);
|
|
41
|
+
expect(result.success).toBe(false);
|
|
42
|
+
expect(result.output.error).toContain("lightning");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("receive command — live integration", () => {
|
|
46
|
+
let wallet;
|
|
47
|
+
beforeAll(async () => {
|
|
48
|
+
wallet = await createTestWallet();
|
|
49
|
+
}, 60000);
|
|
50
|
+
test("receive (no amount) returns the wallet's lightning address", () => {
|
|
51
|
+
const result = runCli(`-c "${wallet.nwcUrl}" receive`);
|
|
52
|
+
expect(result.success).toBe(true);
|
|
53
|
+
expect(result.output.lightning_address).toBe(wallet.lightningAddress);
|
|
54
|
+
});
|
|
55
|
+
test("receive --amount --currency BTC --unit sats returns a BOLT-11 invoice", () => {
|
|
56
|
+
const result = runCli(`-c "${wallet.nwcUrl}" receive --amount 100 --currency BTC --unit sats --network lightning`);
|
|
57
|
+
expect(result.success).toBe(true);
|
|
58
|
+
expect(result.output.invoice).toMatch(/^lnbc/i);
|
|
59
|
+
expect(result.output.amount_in_sats).toBe(100);
|
|
60
|
+
});
|
|
61
|
+
test("receive --unit BTC converts to sats", () => {
|
|
62
|
+
const result = runCli(`-c "${wallet.nwcUrl}" receive --amount 0.000001 --currency BTC --unit BTC --network lightning`);
|
|
63
|
+
expect(result.success).toBe(true);
|
|
64
|
+
expect(result.output.amount_in_sats).toBe(100);
|
|
65
|
+
});
|
|
66
|
+
test("receive --amount --currency USD resolves fiat to sats", () => {
|
|
67
|
+
const result = runCli(`-c "${wallet.nwcUrl}" receive --amount 5 --currency USD --network lightning`);
|
|
68
|
+
expect(result.success).toBe(true);
|
|
69
|
+
expect(result.output.invoice).toMatch(/^lnbc/i);
|
|
70
|
+
expect(result.output.amount_in_sats).toBeGreaterThan(0);
|
|
71
|
+
expect(result.output.fiat).toEqual({ amount: 5, currency: "USD" });
|
|
72
|
+
});
|
|
73
|
+
test("receive --amount --description produces an invoice", () => {
|
|
74
|
+
const result = runCli(`-c "${wallet.nwcUrl}" receive --amount 100 --currency BTC --unit sats --network lightning --description "test"`);
|
|
75
|
+
expect(result.success).toBe(true);
|
|
76
|
+
expect(result.output.invoice).toMatch(/^lnbc/i);
|
|
77
|
+
expect(result.output.amount_in_sats).toBe(100);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -9,7 +9,7 @@ export async function discover(params) {
|
|
|
9
9
|
url.searchParams.set("health", params.health);
|
|
10
10
|
if (params.sort)
|
|
11
11
|
url.searchParams.set("sort", params.sort);
|
|
12
|
-
// Filter to BTC (
|
|
12
|
+
// Filter to BTC (lightning) services server-side
|
|
13
13
|
url.searchParams.set("payment_asset", "BTC");
|
|
14
14
|
url.searchParams.set("limit", String(requestedLimit));
|
|
15
15
|
const controller = new AbortController();
|
|
@@ -18,7 +18,7 @@ export async function fetch402(client, params) {
|
|
|
18
18
|
const maxAmountSats = params.maxAmountSats ?? DEFAULT_MAX_AMOUNT_SATS;
|
|
19
19
|
const result = await fetch402Lib(params.url, requestOptions, {
|
|
20
20
|
wallet: client,
|
|
21
|
-
maxAmount: maxAmountSats
|
|
21
|
+
maxAmount: maxAmountSats,
|
|
22
22
|
});
|
|
23
23
|
const responseContent = await result.text();
|
|
24
24
|
if (!result.ok) {
|
package/build/utils.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { NWAClient, NWCClient } from "@getalby/sdk";
|
|
2
2
|
import { getInfo } from "./tools/nwc/get_info.js";
|
|
3
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
export const DEFAULT_RELAY_URLS = [
|
|
7
|
+
"wss://relay.getalby.com",
|
|
8
|
+
"wss://relay2.getalby.com",
|
|
9
|
+
];
|
|
10
|
+
export function getAlbyCliDir() {
|
|
11
|
+
return join(homedir(), ".alby-cli");
|
|
12
|
+
}
|
|
6
13
|
function sanitizeWalletName(name) {
|
|
7
14
|
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
8
15
|
}
|
|
@@ -10,23 +17,70 @@ export function getConnectionSecretPath(name) {
|
|
|
10
17
|
const filename = name
|
|
11
18
|
? `connection-secret-${sanitizeWalletName(name)}.key`
|
|
12
19
|
: "connection-secret.key";
|
|
13
|
-
return join(
|
|
20
|
+
return join(getAlbyCliDir(), filename);
|
|
14
21
|
}
|
|
15
22
|
export function getPendingConnectionSecretPath(name) {
|
|
16
23
|
const filename = name
|
|
17
24
|
? `pending-connection-secret-${sanitizeWalletName(name)}.key`
|
|
18
25
|
: "pending-connection-secret.key";
|
|
19
|
-
return join(
|
|
26
|
+
return join(getAlbyCliDir(), filename);
|
|
20
27
|
}
|
|
21
28
|
export function getPendingConnectionRelayPath(name) {
|
|
22
29
|
const filename = name
|
|
23
30
|
? `pending-connection-relay-${sanitizeWalletName(name)}.txt`
|
|
24
31
|
: "pending-connection-relay.txt";
|
|
25
|
-
return join(
|
|
32
|
+
return join(getAlbyCliDir(), filename);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* List configured wallets by scanning ~/.alby-cli for connection secret files.
|
|
36
|
+
* Never reads or returns secret contents - only wallet names and status.
|
|
37
|
+
*/
|
|
38
|
+
export function listWallets() {
|
|
39
|
+
const dir = getAlbyCliDir();
|
|
40
|
+
let files;
|
|
41
|
+
try {
|
|
42
|
+
files = readdirSync(dir);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const err = error;
|
|
46
|
+
if (err.code === "ENOENT")
|
|
47
|
+
return [];
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
// Map of wallet name (null for default) -> status. Connected takes precedence
|
|
51
|
+
// over pending so a re-authed wallet still shows as usable.
|
|
52
|
+
const wallets = new Map();
|
|
53
|
+
const patterns = [
|
|
54
|
+
{ regex: /^connection-secret(?:-(.+))?\.key$/, status: "connected" },
|
|
55
|
+
{ regex: /^pending-connection-secret(?:-(.+))?\.key$/, status: "pending" },
|
|
56
|
+
];
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
for (const { regex, status } of patterns) {
|
|
59
|
+
const match = file.match(regex);
|
|
60
|
+
if (!match)
|
|
61
|
+
continue;
|
|
62
|
+
const name = match[1] ?? null;
|
|
63
|
+
if (status === "connected" || !wallets.has(name)) {
|
|
64
|
+
wallets.set(name, wallets.get(name) === "connected" ? "connected" : status);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [...wallets.entries()]
|
|
70
|
+
.map(([name, status]) => ({
|
|
71
|
+
name,
|
|
72
|
+
isDefault: name === null,
|
|
73
|
+
status,
|
|
74
|
+
}))
|
|
75
|
+
.sort((a, b) => {
|
|
76
|
+
if (a.isDefault !== b.isDefault)
|
|
77
|
+
return a.isDefault ? -1 : 1;
|
|
78
|
+
return (a.name ?? "").localeCompare(b.name ?? "");
|
|
79
|
+
});
|
|
26
80
|
}
|
|
27
81
|
export function saveConnectionSecret(path, secret, verbose) {
|
|
28
82
|
const alreadyExists = existsSync(path);
|
|
29
|
-
const dir =
|
|
83
|
+
const dir = getAlbyCliDir();
|
|
30
84
|
if (!existsSync(dir)) {
|
|
31
85
|
mkdirSync(dir, { recursive: true });
|
|
32
86
|
}
|
|
@@ -43,16 +97,20 @@ export async function testAndLogConnection(client) {
|
|
|
43
97
|
const info = await getInfo(client);
|
|
44
98
|
console.log(`Connected to ${info.alias || "wallet"} (${info.network || "unknown network"})`);
|
|
45
99
|
}
|
|
46
|
-
export async function completePendingConnection(pendingSecretPath, connectionSecretPath,
|
|
100
|
+
export async function completePendingConnection(pendingSecretPath, connectionSecretPath, relayUrls, verbose, pendingRelayPath) {
|
|
47
101
|
const secret = readFileSync(pendingSecretPath, "utf-8").trim();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
102
|
+
if ((!relayUrls || relayUrls.length === 0) &&
|
|
103
|
+
pendingRelayPath &&
|
|
104
|
+
existsSync(pendingRelayPath)) {
|
|
105
|
+
relayUrls = readFileSync(pendingRelayPath, "utf-8")
|
|
106
|
+
.split("\n")
|
|
107
|
+
.map((line) => line.trim())
|
|
108
|
+
.filter((line) => line.length > 0);
|
|
51
109
|
}
|
|
52
|
-
const
|
|
110
|
+
const resolvedRelays = relayUrls && relayUrls.length > 0 ? relayUrls : DEFAULT_RELAY_URLS;
|
|
53
111
|
const nwaClient = new NWAClient({
|
|
54
112
|
appSecretKey: secret,
|
|
55
|
-
relayUrls:
|
|
113
|
+
relayUrls: resolvedRelays,
|
|
56
114
|
requestMethods: [],
|
|
57
115
|
});
|
|
58
116
|
return new Promise((resolve, reject) => {
|
|
@@ -117,7 +175,11 @@ export async function getClient(program) {
|
|
|
117
175
|
}
|
|
118
176
|
}
|
|
119
177
|
if (!connectionSecret) {
|
|
120
|
-
throw new Error("No connection
|
|
178
|
+
throw new Error("No wallet connection found. Run 'auth' or 'connect' first to set up a wallet:\n" +
|
|
179
|
+
" npx @getalby/cli auth <wallet-url> # e.g. https://my.albyhub.com\n" +
|
|
180
|
+
' npx @getalby/cli connect "nostr+walletconnect://..."\n' +
|
|
181
|
+
"\n" +
|
|
182
|
+
"Already have a connection secret? Pass -c <secret or file path>, set NWC_URL, use --wallet-name <name>, or create ~/.alby-cli/connection-secret.key");
|
|
121
183
|
}
|
|
122
184
|
// Auto-detect: if it doesn't start with the protocol, treat as file path
|
|
123
185
|
if (!connectionSecret.startsWith("nostr+walletconnect://")) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@getalby/cli",
|
|
3
3
|
"description": "CLI for Nostr Wallet Connect (NIP-47) with a few additional useful lightning tools",
|
|
4
4
|
"repository": "https://github.com/getAlby/cli.git",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.8.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "build/index.js",
|
|
8
8
|
"bin": {
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
"node": ">=20"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@getalby/lightning-tools": "^8.
|
|
40
|
-
"@getalby/sdk": "^
|
|
39
|
+
"@getalby/lightning-tools": "^8.1.1",
|
|
40
|
+
"@getalby/sdk": "^8.0.1",
|
|
41
|
+
"@lendasat/lendaswap-sdk-pure": "^0.2.38",
|
|
41
42
|
"@noble/hashes": "^2.0.1",
|
|
42
43
|
"commander": "^14.0.3",
|
|
43
44
|
"nostr-tools": "^2.23.3"
|