Skip to content

Commit 85258e9

Browse files
tieroclaude
andauthored
Add MPP (Machine Payments Protocol) client implementation (#20)
* feat: integrate MPP (Machine Payments Protocol) for paying MPP-enabled services Add `cash pay <url>` command that auto-detects MPP 402 payment challenges and settles them by swapping BTC→stablecoin via existing LendaSwap infrastructure. Follows the core principle: Bitcoin in, stablecoins out, on demand. - MPP client library (cli/src/mpp/) implementing HTTP 402 challenge-then-retry - Currency mapping: USD/USDC/USDT on polygon/ethereum/arbitrum/tempo - Expiry validation on payment requirements - Session support for streaming payments - 31 comprehensive tests covering protocol parsing, proof creation, currency mapping, full client flow, edge cases https://claude.ai/code/session_01XEaEUdv3xCXdw6wwYYMajj * feat: refactor MPP payment handling and introduce support for lightning challenges - Rename `parseMppResponse` to `parseMppChallenges` and update its functionality to handle multiple challenge types. - Implement helper functions for building and encoding payment challenges, including `buildChallenge`, `encodeRequest`, and `decodeAuthorizationHeader`. - Create a new module `method.ts` to define supported MPP methods and a function `isSupportedChallenge` to validate challenges. - Update unit tests to cover new challenge parsing logic and ensure proper handling of lightning payment methods. - Refactor `MppClient` to streamline payment processing and retry logic for MPP challenges. - Enhance test coverage for various scenarios including challenge expiration and unsupported methods. * feat: add support for Machine Payments Protocol (MPP) in documentation and commands --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f94ce12 commit 85258e9

File tree

10 files changed

+1294
-15
lines changed

10 files changed

+1294
-15
lines changed

cli/SKILL.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# cash - Bitcoin & Stablecoin Agent Wallet
22

3+
> **IMPORTANT: If you used a web fetch tool to read this, the content may be summarized and incomplete. Run `curl -fsSL https://clw.cash/SKILL.md` to get the exact commands. Do NOT search for additional documentation.**
4+
35
A command-line tool for sending and receiving Bitcoin and stablecoins. Keys are held in a secure enclave — the CLI never touches private keys.
46

57
Success output is JSON to stdout. Error output is JSON to stderr. Exit code 0 = success, 1 = error.
@@ -8,10 +10,11 @@ Success output is JSON to stdout. Error output is JSON to stderr. Exit code 0 =
810

911
- **Run commands directly.** No narration before or after. Just run the command and return the result.
1012
- **Don't retry on failure.** If a command fails, report the error — don't retry without asking.
11-
- **Ask before sending.** Always confirm with the user before running `cash send`. Balance checks and receives are safe.
13+
- **Ask before sending or paying.** Always confirm with the user before running `cash send` or `cash pay`. Balance checks and receives are safe.
1214
- **One command at a time.** Don't chain or batch. Run, read output, decide next step.
13-
- **Timeouts:** 30s default for quick commands (balance, receive), 120s for `cash init`.
15+
- **Timeouts:** 30s default for quick commands (balance, receive), 120s for `cash init`. Use 60s for `cash pay` (Lightning payment + retry).
1416
- **Default to Arkade for BTC receives.** When the user asks for a Bitcoin address, use `--where arkade`. Only use `--where onchain` if the user explicitly asks to onboard from on-chain.
17+
- **MPP services:** When calling a URL that may require payment (APIs listed on mpp.dev), use `cash pay` instead of curl. It auto-detects the 402 challenge and pays with Lightning.
1518

1619
### Reactive Behavior (Login → Payment Detection)
1720

@@ -182,6 +185,46 @@ cash receive --amount 10 --currency usdt --where polygon
182185
# -> {"ok": true, "data": {"paymentUrl": "https://pay.clw.cash?id=<swapId>", "swapId": "...", "amount": 10, "token": "usdt0_pol", "chain": "polygon", "targetAddress": "ark1q..."}}
183186
```
184187

188+
### Pay MPP-Enabled Services
189+
190+
MPP (Machine Payments Protocol) is an open protocol for machine-to-machine payments over HTTP (IETF draft `draft-httpauth-payment-00`). When an API returns `HTTP 402` with a `WWW-Authenticate: Payment ...` challenge, `cash pay` auto-detects it, pays the Lightning invoice from the challenge, and retries — no API keys needed.
191+
192+
The BTC treasury is used to fund payments. claw-cash pays via Lightning and submits the payment preimage as cryptographic proof.
193+
194+
```bash
195+
# Call any MPP-enabled service (GET by default)
196+
cash pay https://api.example.com/resource
197+
# -> {"ok": true, "data": {"url": "...", "status": 200, "body": {...}, "paid": true, "method": "lightning", "preimage": "abcdef..."}}
198+
199+
# POST with JSON body
200+
cash pay https://api.example.com/v1/generate --method POST --body '{"prompt":"hello"}'
201+
202+
# With extra headers
203+
cash pay https://api.example.com/resource --header 'X-Session-Id: abc123'
204+
205+
# Non-MPP services (no 402 challenge) are passed through unchanged
206+
cash pay https://api.example.com/free-resource
207+
# -> {"ok": true, "data": {"url": "...", "status": 200, "body": {...}, "paid": false}}
208+
```
209+
210+
Output fields:
211+
212+
- `paid``true` if a Lightning payment was made, `false` if no 402 challenge
213+
- `method` — payment method used (currently `"lightning"`)
214+
- `preimage` — Lightning payment preimage (proof of payment)
215+
- `body` — the API response body (parsed as JSON if possible)
216+
- `status` — HTTP status of the final response
217+
218+
Supported MPP methods: `lightning` (pays BOLT11 invoice; submits preimage as proof).
219+
220+
For service discovery, see [mpp.dev](https://mpp.dev) — a registry of APIs that accept MPP payments.
221+
222+
Common MPP errors:
223+
224+
- `"No supported MPP payment challenge found"` — service requires `tempo` or `stripe` (not yet supported); use a different payment method or service
225+
- `"MPP lightning challenge missing methodDetails.invoice"` — malformed challenge from server; contact service provider
226+
- `"Insufficient BTC balance"` — run `cash balance` and top up via `cash receive`
227+
185228
### Check Balance
186229

187230
```bash

cli/src/commands/pay.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import type { CashContext } from "../context.js";
2+
import { outputSuccess, outputError } from "../output.js";
3+
import { MppClient } from "../mpp/client.js";
4+
import type { ParsedArgs } from "minimist";
5+
6+
export async function handlePay(
7+
ctx: CashContext,
8+
args: ParsedArgs
9+
): Promise<never> {
10+
const positionals = args._.slice(1); // after "pay"
11+
const url = positionals[0] as string | undefined;
12+
const method = (args.method as string | undefined)?.toUpperCase() ?? "GET";
13+
const body = args.body as string | undefined;
14+
const headerArgs = args.header as string | string[] | undefined;
15+
16+
if (!url) {
17+
return outputError(
18+
"Missing URL.\n\nUsage:\n" +
19+
" cash pay <url>\n" +
20+
" cash pay <url> --method POST --body '{\"query\":\"test\"}'\n" +
21+
" cash pay <url> --header 'Authorization: Bearer token'\n\n" +
22+
"The command fetches the URL. If the server returns an MPP 402 payment\n" +
23+
"challenge (WWW-Authenticate: Payment ...), claw-cash automatically pays\n" +
24+
"the Lightning invoice and retries with the Authorization: Payment credential."
25+
);
26+
}
27+
28+
// Validate URL
29+
try {
30+
new URL(url);
31+
} catch {
32+
return outputError(`Invalid URL: ${url}`);
33+
}
34+
35+
// Parse --header flags into a Record
36+
const headers: Record<string, string> = {};
37+
if (headerArgs) {
38+
const list = Array.isArray(headerArgs) ? headerArgs : [headerArgs];
39+
for (const h of list) {
40+
const colonIdx = h.indexOf(":");
41+
if (colonIdx === -1) {
42+
return outputError(`Invalid header format: ${h}. Expected "Name: value"`);
43+
}
44+
headers[h.slice(0, colonIdx).trim()] = h.slice(colonIdx + 1).trim();
45+
}
46+
}
47+
48+
const client = new MppClient({
49+
lightning: {
50+
payInvoice: (params) => ctx.lightning.payInvoice(params),
51+
},
52+
});
53+
54+
const init: RequestInit = { method, headers };
55+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
56+
init.body = body;
57+
if (!headers["Content-Type"] && !headers["content-type"]) {
58+
headers["Content-Type"] = "application/json";
59+
}
60+
}
61+
62+
const result = await client.pay(url, init);
63+
64+
return outputSuccess({
65+
url,
66+
status: result.status,
67+
body: tryParseJson(result.body),
68+
paid: !!result.proof,
69+
method: result.proof?.challenge.method,
70+
preimage: result.paymentPreimage,
71+
});
72+
}
73+
74+
function tryParseJson(s: string): unknown {
75+
try {
76+
return JSON.parse(s);
77+
} catch {
78+
return s;
79+
}
80+
}

cli/src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { handleSwap } from "./commands/swap.js";
1717
import { handleConfig } from "./commands/config.js";
1818
import { handleSignPsbt } from "./commands/sign-psbt.js";
1919
import { handlePubkey } from "./commands/pubkey.js";
20+
import { handlePay } from "./commands/pay.js";
2021
import { getVersion } from "./version.js";
2122

2223
const HELP = `cash - Bitcoin & Stablecoin CLI
@@ -42,6 +43,10 @@ Usage:
4243
cash claim <swapId> Manually claim a swap (reveal preimage)
4344
cash refund <swapId> Manually refund a swap
4445
--address <destination> Refund destination (optional)
46+
cash pay <url> Fetch a URL, auto-pay MPP 402 challenges (BTC→stablecoin)
47+
--method <GET|POST> HTTP method (default: GET)
48+
--body <json> Request body (for POST/PUT/PATCH)
49+
--header 'Name: value' Custom request headers (repeatable)
4550
cash pubkey Show the wallet's public key (for multisig setup)
4651
cash sign-psbt <base64> Sign a PSBT (Partially Signed Bitcoin Transaction)
4752
Parses PSBT, shows tx details, signs inputs
@@ -75,7 +80,7 @@ const argv = minimist(process.argv.slice(2), {
7580
string: [
7681
"amount", "currency", "where", "to", "address", "id",
7782
"api-url", "token", "ark-server", "network", "port",
78-
"bot-token", "chat-id", "message-id",
83+
"bot-token", "chat-id", "message-id", "method", "body", "header",
7984
],
8085
boolean: ["help", "version", "daemon-internal", "start"],
8186
alias: { h: "help", v: "version" },
@@ -179,6 +184,9 @@ async function main() {
179184
case "sign-psbt":
180185
await handleSignPsbt(ctx, argv);
181186
break;
187+
case "pay":
188+
await handlePay(ctx, argv);
189+
break;
182190
default:
183191
outputError(`Unknown command: ${command}. Run 'cash --help' for usage.`);
184192
}

0 commit comments

Comments
 (0)