@lightninglabs/pi-402 0.0.2 → 0.0.3
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/package.json +1 -1
- package/src/format.ts +4 -0
- package/src/index.ts +31 -3
- package/src/payer.ts +19 -2
package/package.json
CHANGED
package/src/format.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { ExtensionAPI, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { registerReceiptCommands, registerWalletCommands } from "./commands.ts";
|
|
4
|
+
import { formatSat } from "./format.ts";
|
|
4
5
|
import { MockInvoicePayer, WalletInvoicePayer } from "./payer.ts";
|
|
5
6
|
import { buildBudgetPolicy } from "./policy.ts";
|
|
6
7
|
import { makeStreamSimple } from "./provider.ts";
|
|
@@ -23,10 +24,35 @@ export default function (pi: ExtensionAPI): void {
|
|
|
23
24
|
// The wallet holds the embedded walletdk instance for the whole session.
|
|
24
25
|
const wallet = new WalletService();
|
|
25
26
|
|
|
27
|
+
// The footer status shows the wallet balance, and a one-line notice marks
|
|
28
|
+
// each payment, so an agent that spends on its own never does it silently.
|
|
29
|
+
// The UI handle arrives with session_start; it is a convenience only, so a
|
|
30
|
+
// failure here (no UI, a stale session, a balance read that errors) is
|
|
31
|
+
// swallowed and never reaches a payment.
|
|
32
|
+
let ui: ExtensionUIContext | undefined;
|
|
33
|
+
const showBalance = async (paidSat?: number): Promise<void> => {
|
|
34
|
+
const target = ui;
|
|
35
|
+
if (!target) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const balance = await wallet.confirmedBalanceSat();
|
|
41
|
+
target.setStatus("pi-402", `⚡ ${formatSat(balance)} sat`);
|
|
42
|
+
if (paidSat !== undefined) {
|
|
43
|
+
target.notify(`pi-402 paid ${formatSat(paidSat)} sat (${formatSat(balance)} sat left)`, "info");
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Leave the last status in place.
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
26
50
|
// The mock payer exercises the buyer loop end to end against a mock proxy
|
|
27
51
|
// with no Lightning at all; the real payer settles through walletdk.
|
|
28
52
|
const mockMode = process.env.PI402_MOCK_PAYER === "1";
|
|
29
|
-
const payer = mockMode
|
|
53
|
+
const payer = mockMode
|
|
54
|
+
? new MockInvoicePayer()
|
|
55
|
+
: new WalletInvoicePayer(wallet, ({ amountPaidSat }) => showBalance(amountPaidSat));
|
|
30
56
|
|
|
31
57
|
// Durable state: paid tokens and the receipt ledger live under the data
|
|
32
58
|
// dir, so a paid bundle survives a restart instead of being re-purchased
|
|
@@ -44,8 +70,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
44
70
|
// Bootstrap the wallet once per session. In mock mode there is no wallet
|
|
45
71
|
// to load, so the session starts without touching the WASM at all.
|
|
46
72
|
if (!mockMode) {
|
|
47
|
-
pi.on("session_start", async () => {
|
|
73
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
48
74
|
await wallet.startAndUnlock();
|
|
75
|
+
ui = ctx.ui;
|
|
76
|
+
await showBalance();
|
|
49
77
|
});
|
|
50
78
|
|
|
51
79
|
// Release the wallet when Pi tears the extension down. Without this
|
package/src/payer.ts
CHANGED
|
@@ -8,6 +8,9 @@ interface PayInvoiceInput {
|
|
|
8
8
|
challengeId: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// PaidHook is told about each payment the wallet settles.
|
|
12
|
+
export type PaidHook = (paid: { amountPaidSat: number }) => void | Promise<void>;
|
|
13
|
+
|
|
11
14
|
// MockInvoicePayer returns a fixed preimage without paying anything. It exists
|
|
12
15
|
// so the buyer loop can be exercised end to end with no wallet and no
|
|
13
16
|
// Lightning at all.
|
|
@@ -23,12 +26,26 @@ export class MockInvoicePayer implements InvoicePayer {
|
|
|
23
26
|
// is read off the settled activity entry once the swap completes.
|
|
24
27
|
export class WalletInvoicePayer implements InvoicePayer {
|
|
25
28
|
private readonly wallet: WalletService;
|
|
29
|
+
private readonly onPaid?: PaidHook;
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
// onPaid runs after each successful payment, for display only: it is not
|
|
32
|
+
// awaited and a failure in it is swallowed, so it can never delay a
|
|
33
|
+
// payment's preimage or turn a settled payment into an error.
|
|
34
|
+
constructor(wallet: WalletService, onPaid?: PaidHook) {
|
|
28
35
|
this.wallet = wallet;
|
|
36
|
+
this.onPaid = onPaid;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
async payInvoice(input: PayInvoiceInput): Promise<{ preimageHex: string; amountPaidSat: number }> {
|
|
32
|
-
|
|
40
|
+
const result = await this.wallet.payInvoice(input.invoice, input.amountSat);
|
|
41
|
+
|
|
42
|
+
if (this.onPaid) {
|
|
43
|
+
const hook = this.onPaid;
|
|
44
|
+
void Promise.resolve()
|
|
45
|
+
.then(() => hook({ amountPaidSat: result.amountPaidSat }))
|
|
46
|
+
.catch(() => {});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result;
|
|
33
50
|
}
|
|
34
51
|
}
|