routstrd 0.4.10 → 0.4.12
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 +55 -15
- package/SECURITY.md +0 -1
- package/SKILL.md +267 -52
- package/dist/daemon/index.js +93747 -70861
- package/dist/index.js +65958 -42561
- package/package.json +3 -2
- package/src/cli.ts +358 -23
- package/src/daemon/http/index.ts +323 -38
- package/src/daemon/http/request-body.ts +67 -0
- package/src/daemon/http/request-path.ts +39 -0
- package/src/daemon/index.ts +1 -1
- package/src/daemon/models.ts +46 -11
- package/src/daemon/wallet/auto-refill.ts +20 -8
- package/src/daemon/wallet/cleanup.ts +16 -2
- package/src/daemon/wallet/coco-client.ts +941 -115
- package/src/daemon/wallet/index.ts +168 -50
- package/src/daemon/wallet/mint-quote-recovery.ts +119 -0
- package/src/daemon/wallet/recovery-probe.ts +312 -0
- package/src/daemon/wallet/recovery-work.ts +45 -0
- package/src/daemon/wallet/testing/fake-mint.ts +347 -0
- package/src/daemon/wallet/trusted-mints.ts +87 -0
- package/src/daemon/wallet/wallet-client.ts +206 -0
- package/src/integrations/pi.ts +189 -34
- package/src/integrations/registry.ts +14 -0
- package/src/tui/usage/render.ts +161 -22
- package/src/utils/config.ts +3 -2
- package/src/utils/cooldowns.ts +134 -0
- package/src/utils/daemon-client.ts +53 -9
- package/src/utils/history.ts +9 -0
- package/src/utils/with-timeout.ts +21 -0
- package/src/TUI refactor.md +0 -113
- package/src/daemon/wallet/cocod-client.ts +0 -505
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { createHash } from "crypto";
|
|
3
|
-
import { isAbsolute } from "path";
|
|
4
|
-
import { logger } from "../../utils/logger";
|
|
5
|
-
import { withCrossProcessLock } from "../../utils/process-lock";
|
|
6
|
-
import type { HistoryEntry } from "@cashu/coco-core";
|
|
7
|
-
|
|
8
|
-
const DEFAULT_CONFIG_DIR =
|
|
9
|
-
process.env.COCOD_DIR || `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
|
|
10
|
-
const DEFAULT_SOCKET_PATH =
|
|
11
|
-
process.env.COCOD_SOCKET || `${DEFAULT_CONFIG_DIR}/cocod.sock`;
|
|
12
|
-
|
|
13
|
-
type UnixRequestInit = RequestInit & { unix: string };
|
|
14
|
-
|
|
15
|
-
type CommandResponse<T> = {
|
|
16
|
-
output?: T;
|
|
17
|
-
error?: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type CocodFetch = (
|
|
21
|
-
input: string | URL | Request,
|
|
22
|
-
init?: UnixRequestInit,
|
|
23
|
-
) => Promise<Response>;
|
|
24
|
-
|
|
25
|
-
type SpawnedProcess = {
|
|
26
|
-
exited: Promise<number>;
|
|
27
|
-
unref?: () => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
type SpawnDaemon = (
|
|
31
|
-
args: string[],
|
|
32
|
-
env: Record<string, string>,
|
|
33
|
-
) => SpawnedProcess;
|
|
34
|
-
|
|
35
|
-
export type CocodState =
|
|
36
|
-
| "UNINITIALIZED"
|
|
37
|
-
| "LOCKED"
|
|
38
|
-
| "UNLOCKED"
|
|
39
|
-
| "RECOVERING"
|
|
40
|
-
| "ERROR";
|
|
41
|
-
|
|
42
|
-
/** Live progress for background wallet recovery started at daemon startup. */
|
|
43
|
-
export interface WalletRecoveryProgress {
|
|
44
|
-
state: "RECOVERING" | "UNLOCKED" | "ERROR";
|
|
45
|
-
/** Current recovery phase, e.g. "Mint recovery" or "done". */
|
|
46
|
-
phase: string;
|
|
47
|
-
pendingSends: number;
|
|
48
|
-
inflightProofs: number;
|
|
49
|
-
pendingMints: number;
|
|
50
|
-
/** Expired unpaid mint quotes failed locally without a mint round-trip. */
|
|
51
|
-
failedMintQuotes: number;
|
|
52
|
-
error?: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export type CocodBalanceOutput = Record<string, { sats?: number } | number>;
|
|
56
|
-
|
|
57
|
-
/** NPC (npubx.cash) Lightning address details for this wallet. */
|
|
58
|
-
export interface NpcAddress {
|
|
59
|
-
/** Full Lightning address, e.g. "alice@npubx.cash" (npub fallback when no username is set). */
|
|
60
|
-
address: string;
|
|
61
|
-
/** NPC username, when one has been claimed. */
|
|
62
|
-
name?: string;
|
|
63
|
-
/** Nostr hex pubkey of the NPC account (only available from the in-process wallet). */
|
|
64
|
-
pubkey?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Result of an NPC username claim attempt. */
|
|
68
|
-
export interface NpcUsernameResult {
|
|
69
|
-
success: boolean;
|
|
70
|
-
/** Present when NPC requires payment to claim the username. */
|
|
71
|
-
paymentRequest?: {
|
|
72
|
-
amount?: number;
|
|
73
|
-
mints?: string[];
|
|
74
|
-
[key: string]: unknown;
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Options for the wallet cleanup command. */
|
|
79
|
-
export interface WalletCleanupOptions {
|
|
80
|
-
/** Only clean up operations for this mint URL. */
|
|
81
|
-
mintUrl?: string;
|
|
82
|
-
/** Minimum operation age in milliseconds (defaults to 7 days / 1 week). */
|
|
83
|
-
minAgeMs?: number;
|
|
84
|
-
/** Report what would be cleaned without applying changes. */
|
|
85
|
-
dryRun?: boolean;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Summary of a wallet cleanup run. */
|
|
89
|
-
export interface WalletCleanupResult {
|
|
90
|
-
dryRun: boolean;
|
|
91
|
-
/** Number of expired pending mint quotes marked as failed. */
|
|
92
|
-
failedMintQuotes: number;
|
|
93
|
-
/** Number of stale pending send operations reclaimed. */
|
|
94
|
-
reclaimedSends: number;
|
|
95
|
-
/** Number of stale prepared melt operations cancelled. */
|
|
96
|
-
cancelledMelts: number;
|
|
97
|
-
/** Number of in-flight operations that were left untouched. */
|
|
98
|
-
skipped: number;
|
|
99
|
-
errors: Array<{ operationId: string; error: string }>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export class CocodHttpError extends Error {
|
|
103
|
-
status: number;
|
|
104
|
-
|
|
105
|
-
constructor(status: number, message: string) {
|
|
106
|
-
super(message);
|
|
107
|
-
this.name = "CocodHttpError";
|
|
108
|
-
this.status = status;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Progress of a Lightning top-up created with `receiveBolt11`. */
|
|
113
|
-
export interface MintQuoteStatus {
|
|
114
|
-
operationId: string;
|
|
115
|
-
state: "pending" | "executing" | "finalized" | "failed";
|
|
116
|
-
/** Last quote state reported by the mint (UNPAID, PAID, ISSUED). */
|
|
117
|
-
mintState?: string;
|
|
118
|
-
amount: number;
|
|
119
|
-
mintUrl: string;
|
|
120
|
-
error?: string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface CocodClient {
|
|
124
|
-
ping(): Promise<boolean>;
|
|
125
|
-
getStatus(): Promise<CocodState>;
|
|
126
|
-
unlock(passphrase: string): Promise<string>;
|
|
127
|
-
getBalances(): Promise<Record<string, number>>;
|
|
128
|
-
receiveCashu(token: string): Promise<string>;
|
|
129
|
-
receiveBolt11(
|
|
130
|
-
amount: number,
|
|
131
|
-
mintUrl?: string,
|
|
132
|
-
): Promise<{ invoice: string; operationId?: string }>;
|
|
133
|
-
/** Progress of a top-up, when the wallet tracks mint operations. */
|
|
134
|
-
getMintQuote?(operationId: string): Promise<MintQuoteStatus | null>;
|
|
135
|
-
sendCashu(amount: number, mintUrl?: string): Promise<string>;
|
|
136
|
-
sendBolt11(invoice: string, mintUrl?: string): Promise<string>;
|
|
137
|
-
listMints(): Promise<string[]>;
|
|
138
|
-
addMint(url: string): Promise<string>;
|
|
139
|
-
getMintInfo(url: string): Promise<unknown>;
|
|
140
|
-
getDefaultMint(): Promise<string | null>;
|
|
141
|
-
setDefaultMint(url: string): Promise<string>;
|
|
142
|
-
/** Release resources held by in-process wallet implementations. */
|
|
143
|
-
dispose?(): Promise<void>;
|
|
144
|
-
getHistory(offset?: number, limit?: number): Promise<HistoryEntry[]>;
|
|
145
|
-
/** NPC (npubx.cash) Lightning address for this wallet. */
|
|
146
|
-
getNpcAddress(): Promise<NpcAddress>;
|
|
147
|
-
/** Claim an NPC username; pass confirm=true to pay the claim fee from the wallet. */
|
|
148
|
-
setNpcUsername(username: string, confirm?: boolean): Promise<NpcUsernameResult>;
|
|
149
|
-
/** Manually trigger an NPC quote sync into the wallet. */
|
|
150
|
-
syncNpc(): Promise<void>;
|
|
151
|
-
/** Clear stuck pending/in-flight wallet operations that are safe to resolve. */
|
|
152
|
-
cleanupStuckOperations?(
|
|
153
|
-
options?: WalletCleanupOptions,
|
|
154
|
-
): Promise<WalletCleanupResult>;
|
|
155
|
-
/** Report background wallet recovery progress, when the wallet supports it. */
|
|
156
|
-
getRecoveryProgress?(): Promise<WalletRecoveryProgress>;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function resolveCocodExecutable(cocodPath?: string | null): string {
|
|
160
|
-
const trimmed = cocodPath?.trim();
|
|
161
|
-
return trimmed || "cocod";
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export async function isCocodInstalled(
|
|
165
|
-
cocodPath?: string | null,
|
|
166
|
-
): Promise<boolean> {
|
|
167
|
-
const executable = resolveCocodExecutable(cocodPath);
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
isAbsolute(executable) ||
|
|
171
|
-
executable.includes("/") ||
|
|
172
|
-
executable.includes("\\")
|
|
173
|
-
) {
|
|
174
|
-
return existsSync(executable);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const command = process.platform === "win32" ? "where.exe" : "which";
|
|
179
|
-
const proc = Bun.spawn({
|
|
180
|
-
cmd: [command, executable],
|
|
181
|
-
stdout: "ignore",
|
|
182
|
-
stderr: "ignore",
|
|
183
|
-
});
|
|
184
|
-
return (await proc.exited) === 0;
|
|
185
|
-
} catch {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function normalizeBalances(
|
|
191
|
-
output: CocodBalanceOutput | undefined,
|
|
192
|
-
): Record<string, number> {
|
|
193
|
-
if (!output) return {};
|
|
194
|
-
|
|
195
|
-
return Object.fromEntries(
|
|
196
|
-
Object.entries(output).map(([mintUrl, value]) => {
|
|
197
|
-
if (typeof value === "number") {
|
|
198
|
-
return [mintUrl, value];
|
|
199
|
-
}
|
|
200
|
-
return [mintUrl, Number(value?.sats ?? 0)];
|
|
201
|
-
}),
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function parseMintList(output: string | undefined): string[] {
|
|
206
|
-
return (output || "")
|
|
207
|
-
.split("\n")
|
|
208
|
-
.map((line) => line.trim())
|
|
209
|
-
.filter(Boolean);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function delay(ms: number): Promise<void> {
|
|
213
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function toErrorText(value: unknown): string {
|
|
217
|
-
if (typeof value === "string") {
|
|
218
|
-
return value.trim();
|
|
219
|
-
}
|
|
220
|
-
if (value === null || value === undefined) {
|
|
221
|
-
return "";
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
return JSON.stringify(value);
|
|
225
|
-
} catch {
|
|
226
|
-
return String(value);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function tokenFingerprint(token: string): string {
|
|
231
|
-
return createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export function createCocodClient(
|
|
235
|
-
options: {
|
|
236
|
-
cocodPath?: string | null;
|
|
237
|
-
socketPath?: string;
|
|
238
|
-
fetchImpl?: CocodFetch;
|
|
239
|
-
spawnDaemon?: SpawnDaemon;
|
|
240
|
-
pollIntervalMs?: number;
|
|
241
|
-
startupTimeoutMs?: number;
|
|
242
|
-
} = {},
|
|
243
|
-
): CocodClient {
|
|
244
|
-
const executable = resolveCocodExecutable(options.cocodPath);
|
|
245
|
-
const socketPath = options.socketPath || DEFAULT_SOCKET_PATH;
|
|
246
|
-
const startupLockPath = `${socketPath}.startup.lock`;
|
|
247
|
-
const fetchImpl = options.fetchImpl || (fetch as CocodFetch);
|
|
248
|
-
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
249
|
-
const startupTimeoutMs = options.startupTimeoutMs ?? 5000;
|
|
250
|
-
|
|
251
|
-
const spawnDaemon: SpawnDaemon =
|
|
252
|
-
options.spawnDaemon ||
|
|
253
|
-
((args, env) => {
|
|
254
|
-
const proc = Bun.spawn(args, {
|
|
255
|
-
stdin: "ignore",
|
|
256
|
-
stdout: "ignore",
|
|
257
|
-
stderr: "ignore",
|
|
258
|
-
detached: true,
|
|
259
|
-
env,
|
|
260
|
-
});
|
|
261
|
-
proc.unref();
|
|
262
|
-
return proc;
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
let startPromise: Promise<void> | null = null;
|
|
266
|
-
|
|
267
|
-
async function fetchJson<T>(
|
|
268
|
-
path: string,
|
|
269
|
-
init: Omit<UnixRequestInit, "unix"> = {},
|
|
270
|
-
): Promise<CommandResponse<T>> {
|
|
271
|
-
const method = init.method || "GET";
|
|
272
|
-
const requestInit: UnixRequestInit = {
|
|
273
|
-
...init,
|
|
274
|
-
unix: socketPath,
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
const response = await fetchImpl(`http://localhost${path}`, requestInit);
|
|
278
|
-
const rawText = await response.text();
|
|
279
|
-
// logger.debug(
|
|
280
|
-
// `[fetchJson] ${method} ${path} status=${response.status} body=${rawText}`,
|
|
281
|
-
// );
|
|
282
|
-
|
|
283
|
-
if (!rawText.trim()) {
|
|
284
|
-
throw new CocodHttpError(
|
|
285
|
-
response.ok ? 502 : response.status,
|
|
286
|
-
`Empty response from cocod for ${method} ${path}`,
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let data: CommandResponse<T>;
|
|
291
|
-
try {
|
|
292
|
-
data = JSON.parse(rawText) as CommandResponse<T>;
|
|
293
|
-
} catch {
|
|
294
|
-
throw new CocodHttpError(
|
|
295
|
-
response.ok ? 502 : response.status,
|
|
296
|
-
`Invalid JSON response from cocod for ${method} ${path}`,
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (!data || typeof data !== "object") {
|
|
301
|
-
throw new CocodHttpError(
|
|
302
|
-
response.ok ? 502 : response.status,
|
|
303
|
-
`Unexpected response shape from cocod for ${method} ${path}`,
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const errorMessage = toErrorText((data as CommandResponse<T>).error);
|
|
308
|
-
if (errorMessage) {
|
|
309
|
-
throw new CocodHttpError(
|
|
310
|
-
response.ok ? 400 : response.status,
|
|
311
|
-
errorMessage,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (!response.ok) {
|
|
316
|
-
throw new CocodHttpError(
|
|
317
|
-
response.status,
|
|
318
|
-
data.error || response.statusText || `HTTP ${response.status}`,
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return data;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
async function pingInternal(): Promise<boolean> {
|
|
326
|
-
try {
|
|
327
|
-
await fetchJson<string>("/ping");
|
|
328
|
-
return true;
|
|
329
|
-
} catch {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
async function startDaemon(): Promise<void> {
|
|
335
|
-
const env = { ...process.env, COCOD_SOCKET: socketPath };
|
|
336
|
-
const proc = spawnDaemon([executable, "init"], env);
|
|
337
|
-
const maxPolls = Math.ceil(startupTimeoutMs / pollIntervalMs);
|
|
338
|
-
let exitCode: number | null = null;
|
|
339
|
-
|
|
340
|
-
void proc.exited.then((code) => {
|
|
341
|
-
exitCode = code;
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
for (let i = 0; i < maxPolls; i++) {
|
|
345
|
-
await delay(pollIntervalMs);
|
|
346
|
-
|
|
347
|
-
if (exitCode !== null && exitCode !== 0) {
|
|
348
|
-
throw new Error(`cocod init exited early with code ${exitCode}`);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (await pingInternal()) {
|
|
352
|
-
logger.debug(`Connected to cocod daemon on ${socketPath}`);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
throw new Error(
|
|
358
|
-
`cocod failed to start within ${Math.round(startupTimeoutMs / 1000)} seconds`,
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function ensureDaemonRunning(): Promise<void> {
|
|
363
|
-
if (await pingInternal()) {
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (!startPromise) {
|
|
368
|
-
startPromise = withCrossProcessLock(
|
|
369
|
-
startupLockPath,
|
|
370
|
-
async () => {
|
|
371
|
-
if (await pingInternal()) {
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
logger.debug(`Starting cocod daemon via ${executable} init...`);
|
|
376
|
-
await startDaemon();
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
acquireTimeoutMs: startupTimeoutMs + 30_000,
|
|
380
|
-
staleAfterMs: startupTimeoutMs + 30_000,
|
|
381
|
-
log: (message) => logger.debug(message),
|
|
382
|
-
},
|
|
383
|
-
).finally(() => {
|
|
384
|
-
startPromise = null;
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
await startPromise;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async function callDaemon<T>(
|
|
392
|
-
path: string,
|
|
393
|
-
init: Omit<UnixRequestInit, "unix"> = {},
|
|
394
|
-
): Promise<T> {
|
|
395
|
-
await ensureDaemonRunning();
|
|
396
|
-
const response = await fetchJson<T>(path, init);
|
|
397
|
-
return response.output as T;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function post<T>(path: string, body: Record<string, unknown>): Promise<T> {
|
|
401
|
-
return callDaemon<T>(path, {
|
|
402
|
-
method: "POST",
|
|
403
|
-
headers: { "Content-Type": "application/json" },
|
|
404
|
-
body: JSON.stringify(body),
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
async ping(): Promise<boolean> {
|
|
410
|
-
return pingInternal();
|
|
411
|
-
},
|
|
412
|
-
async getStatus(): Promise<CocodState> {
|
|
413
|
-
return callDaemon<CocodState>("/status");
|
|
414
|
-
},
|
|
415
|
-
async unlock(passphrase: string): Promise<string> {
|
|
416
|
-
return post<string>("/unlock", { passphrase });
|
|
417
|
-
},
|
|
418
|
-
async getBalances(): Promise<Record<string, number>> {
|
|
419
|
-
const output = await callDaemon<CocodBalanceOutput>("/balance");
|
|
420
|
-
return normalizeBalances(output);
|
|
421
|
-
},
|
|
422
|
-
async receiveCashu(token: string): Promise<string> {
|
|
423
|
-
logger.debug(
|
|
424
|
-
`[receiveCashu] Receiving Cashu token ${tokenFingerprint(token)}`,
|
|
425
|
-
);
|
|
426
|
-
const message = await callDaemon<string>("/receive/cashu", {
|
|
427
|
-
method: "POST",
|
|
428
|
-
headers: { "Content-Type": "application/json" },
|
|
429
|
-
body: JSON.stringify({ token }),
|
|
430
|
-
});
|
|
431
|
-
if (typeof message !== "string" || !message.trim()) {
|
|
432
|
-
throw new CocodHttpError(
|
|
433
|
-
502,
|
|
434
|
-
"Unexpected response from cocod while receiving Cashu token.",
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
logger.debug(`[receiveCashu] Full response.output:`, message);
|
|
438
|
-
return message;
|
|
439
|
-
},
|
|
440
|
-
async receiveBolt11(amount: number, mintUrl?: string) {
|
|
441
|
-
return { invoice: await post<string>("/receive/bolt11", { amount, mintUrl }) };
|
|
442
|
-
},
|
|
443
|
-
async sendCashu(amount: number, mintUrl?: string): Promise<string> {
|
|
444
|
-
return post<string>("/send/cashu", { amount, mintUrl });
|
|
445
|
-
},
|
|
446
|
-
async sendBolt11(invoice: string, mintUrl?: string): Promise<string> {
|
|
447
|
-
return post<string>("/send/bolt11", { invoice, mintUrl });
|
|
448
|
-
},
|
|
449
|
-
async listMints(): Promise<string[]> {
|
|
450
|
-
const output = await callDaemon<string>("/mints/list");
|
|
451
|
-
return parseMintList(output);
|
|
452
|
-
},
|
|
453
|
-
async addMint(url: string): Promise<string> {
|
|
454
|
-
return post<string>("/mints/add", { url });
|
|
455
|
-
},
|
|
456
|
-
async getMintInfo(url: string): Promise<unknown> {
|
|
457
|
-
return post<unknown>("/mints/info", { url });
|
|
458
|
-
},
|
|
459
|
-
async getDefaultMint(): Promise<string | null> {
|
|
460
|
-
return callDaemon<string | null>("/mints/default");
|
|
461
|
-
},
|
|
462
|
-
async setDefaultMint(url: string): Promise<string> {
|
|
463
|
-
return post<string>("/mints/default", { url });
|
|
464
|
-
},
|
|
465
|
-
async getHistory(_offset?: number, _limit?: number): Promise<HistoryEntry[]> {
|
|
466
|
-
return [];
|
|
467
|
-
},
|
|
468
|
-
async getNpcAddress(): Promise<NpcAddress> {
|
|
469
|
-
const address = await callDaemon<string>("/npc/address");
|
|
470
|
-
if (typeof address !== "string" || !address.trim()) {
|
|
471
|
-
throw new CocodHttpError(
|
|
472
|
-
502,
|
|
473
|
-
"Unexpected response from cocod while fetching NPC address.",
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
const trimmed = address.trim();
|
|
477
|
-
const localPart = trimmed.split("@")[0];
|
|
478
|
-
return {
|
|
479
|
-
address: trimmed,
|
|
480
|
-
...(localPart && !localPart.startsWith("npub1")
|
|
481
|
-
? { name: localPart }
|
|
482
|
-
: {}),
|
|
483
|
-
};
|
|
484
|
-
},
|
|
485
|
-
async setNpcUsername(
|
|
486
|
-
username: string,
|
|
487
|
-
confirm?: boolean,
|
|
488
|
-
): Promise<NpcUsernameResult> {
|
|
489
|
-
// cocod answers 402 with a payment-required message when the claim fee
|
|
490
|
-
// has not been confirmed; fetchJson preserves that status on the thrown
|
|
491
|
-
// CocodHttpError, so callers can surface it unchanged.
|
|
492
|
-
const result = await post<{ success?: boolean }>("/npc/username", {
|
|
493
|
-
username,
|
|
494
|
-
confirm: confirm === true,
|
|
495
|
-
});
|
|
496
|
-
return { success: result?.success !== false };
|
|
497
|
-
},
|
|
498
|
-
async syncNpc(): Promise<void> {
|
|
499
|
-
throw new CocodHttpError(
|
|
500
|
-
501,
|
|
501
|
-
"Manual NPC sync is not supported by the legacy cocod client.",
|
|
502
|
-
);
|
|
503
|
-
},
|
|
504
|
-
};
|
|
505
|
-
}
|