moltspay 1.4.0 → 1.5.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/.env.example +14 -0
- package/README.md +185 -93
- package/dist/cli/index.js +69 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +69 -33
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +3 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +46 -26
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +46 -26
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +50 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -28
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.d.mts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1498 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +1492 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/server/index.js +4 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +4 -2
- package/dist/server/index.mjs.map +1 -1
- package/package.json +12 -4
package/.env.example
CHANGED
|
@@ -60,3 +60,17 @@ CDP_API_KEY_SECRET=
|
|
|
60
60
|
# https://facilitator.questflow.ai/
|
|
61
61
|
# QUESTFLOW_ENDPOINT=https://facilitator.questflow.ai
|
|
62
62
|
# QUESTFLOW_API_KEY=
|
|
63
|
+
|
|
64
|
+
# ===========================================
|
|
65
|
+
# Solana Relay Fee Payer
|
|
66
|
+
# ===========================================
|
|
67
|
+
# Private key for paying Solana transaction fees (base58 encoded)
|
|
68
|
+
# Required for server-side settlement on Solana chains
|
|
69
|
+
# SOLANA_FEE_PAYER_KEY=
|
|
70
|
+
|
|
71
|
+
# ===========================================
|
|
72
|
+
# BNB Chain Relay Fee Payer
|
|
73
|
+
# ===========================================
|
|
74
|
+
# Private key for paying BNB Chain transaction fees
|
|
75
|
+
# Required for server-side settlement on BNB chains (bsc, opbnb)
|
|
76
|
+
# BNB_SERVER_PRIVATE_KEY=
|
package/README.md
CHANGED
|
@@ -19,14 +19,15 @@ MoltsPay enables agent-to-agent commerce using the [x402 protocol](https://www.x
|
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
22
|
+
- **Skill Integration** - Add `moltspay.services.json` to any existing skill
|
|
23
|
+
- **x402 Protocol** - HTTP-native payments (402 Payment Required)
|
|
24
|
+
- **Gasless** - Both client and server pay no gas (facilitators handle it)
|
|
25
|
+
- **Payment Verification** - Automatic on-chain verification
|
|
26
|
+
- **Secure Wallet** - Spending limits, whitelist, and audit logging
|
|
27
|
+
- **Multi-chain** - Base, Polygon, Solana, BNB, Tempo (mainnet & testnet)
|
|
28
|
+
- **Agent-to-Agent** - Complete A2A payment flow support
|
|
29
|
+
- **Multi-VM** - EVM chains + Solana (SVM) with unified API
|
|
30
|
+
- **MCP Server** - Expose wallet + payments to Claude Desktop, Cursor, and other MCP hosts
|
|
30
31
|
|
|
31
32
|
## Installation
|
|
32
33
|
|
|
@@ -133,6 +134,50 @@ npx moltspay pay https://server.com service-id \
|
|
|
133
134
|
--chain tempo_moderato --prompt "test"
|
|
134
135
|
```
|
|
135
136
|
|
|
137
|
+
## MCP Server (For AI Assistants)
|
|
138
|
+
|
|
139
|
+
MoltsPay ships an [MCP (Model Context Protocol)](https://modelcontextprotocol.io) stdio server that lets MCP-compatible hosts (Cursor, Windsurf, Claude Code, Zed, etc.) browse services, check wallet status, and pay for x402 services on your behalf.
|
|
140
|
+
|
|
141
|
+
It is a thin wrapper around `MoltsPayClient` — wallet custody, spending limits, and all payment protocols (x402, MPP, Solana, BNB) are reused from the SDK.
|
|
142
|
+
|
|
143
|
+
### Setup
|
|
144
|
+
|
|
145
|
+
**1. Create a wallet and set spending limits** (the MCP server refuses to start without a wallet):
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx moltspay init
|
|
149
|
+
npx moltspay config --max-per-tx 2 --max-per-day 10
|
|
150
|
+
npx moltspay fund 5 # or: npx moltspay faucet for testnet
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**2. Point your MCP host at the `moltspay-mcp` binary over stdio:**
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx -y moltspay-mcp # normal mode
|
|
157
|
+
npx -y moltspay-mcp --dry-run # preview payments without signing
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Each host has its own config file for registering stdio MCP servers — check your host's docs for the exact location. For a safer first run, use `--dry-run` so `moltspay_pay` returns a preview instead of spending real funds.
|
|
161
|
+
|
|
162
|
+
### Tools
|
|
163
|
+
|
|
164
|
+
| Tool | What it does | Destructive? |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `moltspay_status` | Wallet address, balances across all supported chains, spending limits | No |
|
|
167
|
+
| `moltspay_services` | Fetch services manifest from a provider URL; optional `query`/`maxPrice` filter | No |
|
|
168
|
+
| `moltspay_pay` | Execute an x402/MPP/SOL/BNB payment and return the service result | **Yes** |
|
|
169
|
+
| `moltspay_config` | Read or update `maxPerTx` / `maxPerDay` limits | Updates config file |
|
|
170
|
+
|
|
171
|
+
### Safety Layers
|
|
172
|
+
|
|
173
|
+
`moltspay_pay` is the only tool that moves money. Three guards stack on top of the MCP host's own tool-approval prompt:
|
|
174
|
+
|
|
175
|
+
1. **SDK spending limits** — `maxPerTx` / `maxPerDay` enforced before signing.
|
|
176
|
+
2. **Dry-run mode** — launch with `--dry-run` and payments return a preview instead of signing.
|
|
177
|
+
3. **Confirmation gate** — set `MOLTSPAY_MCP_REQUIRE_CONFIRM=1` to require a second tool call (`confirmed: true`) for any payment exceeding `maxPerTx / 10`.
|
|
178
|
+
|
|
179
|
+
Private keys and mnemonics are never exposed over MCP — wallet creation stays on the CLI (`npx moltspay init`) by design. See [`docs/MCP-USAGE.md`](docs/MCP-USAGE.md) for full tool arguments and troubleshooting.
|
|
180
|
+
|
|
136
181
|
## Payment Protocols
|
|
137
182
|
|
|
138
183
|
MoltsPay supports multiple payment protocols, each optimized for different chains:
|
|
@@ -148,24 +193,24 @@ MoltsPay supports multiple payment protocols, each optimized for different chain
|
|
|
148
193
|
|
|
149
194
|
```
|
|
150
195
|
Client Server CDP Facilitator
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
196
|
+
| | |
|
|
197
|
+
| POST /execute | |
|
|
198
|
+
| --------------------------------------------------> | |
|
|
199
|
+
| | |
|
|
200
|
+
| 402 + payment requirements | |
|
|
201
|
+
| <-------------------------------------------------- | |
|
|
202
|
+
| | |
|
|
203
|
+
| [Sign EIP-3009 - NO GAS] | |
|
|
204
|
+
| | |
|
|
205
|
+
| POST + X-Payment header | |
|
|
206
|
+
| --------------------------------------------------> | Verify signature |
|
|
207
|
+
| | --------------------------------------------------> |
|
|
208
|
+
| | |
|
|
209
|
+
| | Execute transfer (pays gas) |
|
|
210
|
+
| | <-------------------------------------------------- |
|
|
211
|
+
| | |
|
|
212
|
+
| 200 OK + result | |
|
|
213
|
+
| <-------------------------------------------------- | |
|
|
169
214
|
```
|
|
170
215
|
|
|
171
216
|
**Key insight:** Client signs a payment authorization, server submits it. Neither party pays gas - the CDP facilitator handles settlement.
|
|
@@ -176,23 +221,23 @@ MPP (Machine Payments Protocol) is simpler - client executes the transfer direct
|
|
|
176
221
|
|
|
177
222
|
```
|
|
178
223
|
Client Server
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
224
|
+
| |
|
|
225
|
+
| POST /service |
|
|
226
|
+
| --------------------------------------------------> |
|
|
227
|
+
| |
|
|
228
|
+
| 402 + WWW-Authenticate |
|
|
229
|
+
| <-------------------------------------------------- |
|
|
230
|
+
| |
|
|
231
|
+
| [Execute TIP-20 transfer] |
|
|
232
|
+
| [No gas needed on Tempo] |
|
|
233
|
+
| |
|
|
234
|
+
| POST + Authorization: Payment|
|
|
235
|
+
| --------------------------------------------------> |
|
|
236
|
+
| |
|
|
237
|
+
| [Server verifies on-chain] |
|
|
238
|
+
| |
|
|
239
|
+
| 200 OK + Payment-Receipt |
|
|
240
|
+
| <-------------------------------------------------- |
|
|
196
241
|
```
|
|
197
242
|
|
|
198
243
|
**Key insight:** On Tempo, the client executes the transfer directly (gas-free), then retries with the transaction hash. No CDP facilitator needed.
|
|
@@ -201,24 +246,24 @@ Client Server
|
|
|
201
246
|
|
|
202
247
|
```
|
|
203
248
|
Client Server (Fee Payer) Solana Network
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
249
|
+
| | |
|
|
250
|
+
| POST /execute | |
|
|
251
|
+
| --------------------------------------------------> | |
|
|
252
|
+
| | |
|
|
253
|
+
| 402 + payment requirements | |
|
|
254
|
+
| (includes solana_wallet) | |
|
|
255
|
+
| <-------------------------------------------------- | |
|
|
256
|
+
| | |
|
|
257
|
+
| [Sign SPL Transfer] | |
|
|
258
|
+
| [NO GAS - just signing] | |
|
|
259
|
+
| | |
|
|
260
|
+
| POST + X-Payment (signature) | |
|
|
261
|
+
| --------------------------------------------------> | Execute transfer |
|
|
262
|
+
| | (server pays ~$0.001 SOL) |
|
|
263
|
+
| | --------------------------------------------------> |
|
|
264
|
+
| | |
|
|
265
|
+
| 200 OK + result | |
|
|
266
|
+
| <-------------------------------------------------- | |
|
|
222
267
|
```
|
|
223
268
|
|
|
224
269
|
**Key insight:** Client only signs the SPL transfer (gasless). Server acts as fee payer and executes the transaction on-chain.
|
|
@@ -227,24 +272,24 @@ Client Server (Fee Payer) Solana Network
|
|
|
227
272
|
|
|
228
273
|
```
|
|
229
274
|
Client Server BNB Network
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
275
|
+
| | |
|
|
276
|
+
| POST /execute | |
|
|
277
|
+
| --------------------------------------------------> | |
|
|
278
|
+
| | |
|
|
279
|
+
| 402 + payment requirements | |
|
|
280
|
+
| (includes bnbSpender) | |
|
|
281
|
+
| <-------------------------------------------------- | |
|
|
282
|
+
| | |
|
|
283
|
+
| [Sign EIP-712 intent] | |
|
|
284
|
+
| [NO GAS - just signing] | |
|
|
285
|
+
| | |
|
|
286
|
+
| POST + X-Payment (signature) | |
|
|
287
|
+
| --------------------------------------------------> | Execute transferFrom |
|
|
288
|
+
| | (server pays ~$0.0001 gas) |
|
|
289
|
+
| | --------------------------------------------------> |
|
|
290
|
+
| | |
|
|
291
|
+
| 200 OK + result | |
|
|
292
|
+
| <-------------------------------------------------- | |
|
|
248
293
|
```
|
|
249
294
|
|
|
250
295
|
**Key insight:** Client only signs an intent (gasless). Server executes the actual transfer and pays the minimal gas (~$0.0001). This is the "pay-for-success" model - payment only happens if service succeeds.
|
|
@@ -255,9 +300,9 @@ MoltsPay reads your skill's existing structure:
|
|
|
255
300
|
|
|
256
301
|
```
|
|
257
302
|
my-skill/
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
303
|
+
+------ package.json # MoltsPay reads "main" field
|
|
304
|
+
+------ index.js # Your existing exports
|
|
305
|
+
+------ moltspay.services.json # Only file you add!
|
|
261
306
|
```
|
|
262
307
|
|
|
263
308
|
**Your functions stay untouched.** Just add the JSON config.
|
|
@@ -350,8 +395,8 @@ Server does NOT need a private key - the x402 facilitator handles settlement.
|
|
|
350
395
|
|
|
351
396
|
The server automatically detects which chain to verify payments on based on the client's payment header:
|
|
352
397
|
|
|
353
|
-
- Client pays with `--chain base`
|
|
354
|
-
- Client pays with `--chain base_sepolia`
|
|
398
|
+
- Client pays with `--chain base` -> Server verifies on Base mainnet
|
|
399
|
+
- Client pays with `--chain base_sepolia` -> Server verifies on Base Sepolia
|
|
355
400
|
|
|
356
401
|
**No `USE_MAINNET` env var needed!** Just configure your accepted chains in the manifest.
|
|
357
402
|
|
|
@@ -425,16 +470,63 @@ npx moltspay validate <path> # Validate manifest
|
|
|
425
470
|
```typescript
|
|
426
471
|
import { MoltsPayClient } from 'moltspay/client';
|
|
427
472
|
|
|
428
|
-
|
|
473
|
+
// Initialize client (uses wallet from ~/.moltspay/wallet.json)
|
|
474
|
+
const client = new MoltsPayClient();
|
|
429
475
|
|
|
430
|
-
//
|
|
431
|
-
const result = await client.
|
|
432
|
-
|
|
433
|
-
|
|
476
|
+
// Standard service call (params wrapped in { params: {...} })
|
|
477
|
+
const result = await client.pay(
|
|
478
|
+
'https://server.com',
|
|
479
|
+
'text-to-video',
|
|
480
|
+
{ prompt: 'a cat dancing' },
|
|
481
|
+
{ chain: 'base' }
|
|
482
|
+
);
|
|
434
483
|
|
|
435
484
|
console.log(result.video_url);
|
|
436
485
|
```
|
|
437
486
|
|
|
487
|
+
#### Custom Input Formats (rawData)
|
|
488
|
+
|
|
489
|
+
Some services have custom input formats instead of the standard `{ params: { prompt } }`.
|
|
490
|
+
Use `rawData: true` to send your data at the top level:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// Service expects: { text: "...", target_lang: "..." }
|
|
494
|
+
// NOT: { params: { text: "...", target_lang: "..." } }
|
|
495
|
+
|
|
496
|
+
const result = await client.pay(
|
|
497
|
+
'https://server.com',
|
|
498
|
+
'translate',
|
|
499
|
+
{ text: 'Hello world', target_lang: 'es' },
|
|
500
|
+
{
|
|
501
|
+
chain: 'base_sepolia',
|
|
502
|
+
rawData: true // Send data at top level
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// Server receives: { service: "translate", text: "Hello world", target_lang: "es", chain: "base_sepolia" }
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### PayOptions Reference
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
interface PayOptions {
|
|
513
|
+
token?: 'USDC' | 'USDT'; // Token to pay with (default: USDC)
|
|
514
|
+
autoSelect?: boolean; // Auto-select token based on balance
|
|
515
|
+
chain?: string; // Chain: base, polygon, solana, bnb, tempo_moderato, + testnets
|
|
516
|
+
rawData?: boolean; // Send data at top level (for custom input formats)
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
#### CLI Equivalent
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
# Standard format (uses { params: { prompt } })
|
|
524
|
+
npx moltspay pay https://server.com text-to-video --prompt "a cat dancing"
|
|
525
|
+
|
|
526
|
+
# Custom format (uses rawData, sends at top level)
|
|
527
|
+
npx moltspay pay https://server.com translate --data '{"text": "Hello", "target_lang": "es"}'
|
|
528
|
+
```
|
|
529
|
+
|
|
438
530
|
### Server
|
|
439
531
|
|
|
440
532
|
```typescript
|
|
@@ -493,9 +585,9 @@ A **facilitator** is the entity that executes the on-chain payment and pays the
|
|
|
493
585
|
|
|
494
586
|
| Aspect | CDP (External) | Self-hosted |
|
|
495
587
|
|--------|----------------|-------------|
|
|
496
|
-
| Single point of failure |
|
|
497
|
-
| Censorship risk |
|
|
498
|
-
| Dependency |
|
|
588
|
+
| Single point of failure | Coinbase down = everyone stuck | Each provider independent |
|
|
589
|
+
| Censorship risk | Coinbase can block accounts | Cannot be censored |
|
|
590
|
+
| Dependency | Relies on third-party | Fully autonomous |
|
|
499
591
|
|
|
500
592
|
This self-hosted approach is a key innovation: **any service provider can become their own facilitator** without relying on third-party infrastructure. Unlike CDP where all users depend on Coinbase, self-hosted facilitators create a truly decentralized network with no single point of failure.
|
|
501
593
|
|
|
@@ -628,7 +720,7 @@ npx moltspay pay https://moltspay.com/a/zen7 text-to-video --chain solana_devnet
|
|
|
628
720
|
|
|
629
721
|
Join our Discord for help, feedback, and updates:
|
|
630
722
|
|
|
631
|
-
|
|
723
|
+
--> **[MoltsPay Discord](https://discord.gg/QwCJgVBxVK)**
|
|
632
724
|
|
|
633
725
|
Or visit the [#moltspay-support](https://discord.com/channels/1472602423267819734/1480968496346304522) channel directly.
|
|
634
726
|
|
package/dist/cli/index.js
CHANGED
|
@@ -682,11 +682,26 @@ var MoltsPayClient = class {
|
|
|
682
682
|
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
683
683
|
}
|
|
684
684
|
console.log(`[MoltsPay] Requesting service: ${service}`);
|
|
685
|
-
|
|
685
|
+
let executeUrl = `${serverUrl}/execute`;
|
|
686
|
+
try {
|
|
687
|
+
const services = await this.getServices(serverUrl);
|
|
688
|
+
const svc = services.services?.find((s) => s.id === service);
|
|
689
|
+
if (svc?.endpoint) {
|
|
690
|
+
executeUrl = `${serverUrl}${svc.endpoint}`;
|
|
691
|
+
console.log(`[MoltsPay] Using service endpoint: ${svc.endpoint}`);
|
|
692
|
+
}
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
let requestBody;
|
|
696
|
+
if (options.rawData) {
|
|
697
|
+
requestBody = { service, ...params };
|
|
698
|
+
} else {
|
|
699
|
+
requestBody = { service, params };
|
|
700
|
+
}
|
|
686
701
|
if (options.chain) {
|
|
687
702
|
requestBody.chain = options.chain;
|
|
688
703
|
}
|
|
689
|
-
const initialRes = await fetch(
|
|
704
|
+
const initialRes = await fetch(executeUrl, {
|
|
690
705
|
method: "POST",
|
|
691
706
|
headers: { "Content-Type": "application/json" },
|
|
692
707
|
body: JSON.stringify(requestBody)
|
|
@@ -702,7 +717,7 @@ var MoltsPayClient = class {
|
|
|
702
717
|
const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
703
718
|
if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
|
|
704
719
|
console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
|
|
705
|
-
return await this.handleMPPPayment(
|
|
720
|
+
return await this.handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options);
|
|
706
721
|
}
|
|
707
722
|
if (!paymentRequiredHeader) {
|
|
708
723
|
throw new Error("Missing payment header (x-payment-required or www-authenticate)");
|
|
@@ -763,7 +778,7 @@ Please specify: --chain <chain_name>`
|
|
|
763
778
|
if (!req2) {
|
|
764
779
|
throw new Error(`Failed to find payment requirement for ${selectedChain}`);
|
|
765
780
|
}
|
|
766
|
-
return await this.handleSolanaPayment(
|
|
781
|
+
return await this.handleSolanaPayment(executeUrl, service, params, req2, solanaChain, options);
|
|
767
782
|
}
|
|
768
783
|
const chainName = selectedChain;
|
|
769
784
|
const chain = getChain(chainName);
|
|
@@ -810,14 +825,14 @@ Please specify: --chain <chain_name>`
|
|
|
810
825
|
if (!bnbSpender) {
|
|
811
826
|
throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
|
|
812
827
|
}
|
|
813
|
-
return await this.handleBNBPayment(
|
|
828
|
+
return await this.handleBNBPayment(executeUrl, service, params, {
|
|
814
829
|
to: payTo2,
|
|
815
830
|
amount,
|
|
816
831
|
token,
|
|
817
832
|
chainName,
|
|
818
833
|
chain,
|
|
819
834
|
spender: bnbSpender
|
|
820
|
-
});
|
|
835
|
+
}, options);
|
|
821
836
|
}
|
|
822
837
|
const payTo = req.payTo || req.resource;
|
|
823
838
|
if (!payTo) {
|
|
@@ -848,11 +863,11 @@ Please specify: --chain <chain_name>`
|
|
|
848
863
|
};
|
|
849
864
|
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
850
865
|
console.log(`[MoltsPay] Sending request with payment...`);
|
|
851
|
-
const paidRequestBody = { service, params };
|
|
866
|
+
const paidRequestBody = options.rawData ? { service, ...params } : { service, params };
|
|
852
867
|
if (options.chain) {
|
|
853
868
|
paidRequestBody.chain = options.chain;
|
|
854
869
|
}
|
|
855
|
-
const paidRes = await fetch(
|
|
870
|
+
const paidRes = await fetch(executeUrl, {
|
|
856
871
|
method: "POST",
|
|
857
872
|
headers: {
|
|
858
873
|
"Content-Type": "application/json",
|
|
@@ -866,13 +881,13 @@ Please specify: --chain <chain_name>`
|
|
|
866
881
|
}
|
|
867
882
|
this.recordSpending(amount);
|
|
868
883
|
console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
|
|
869
|
-
return result.result;
|
|
884
|
+
return result.result || result;
|
|
870
885
|
}
|
|
871
886
|
/**
|
|
872
887
|
* Handle MPP (Machine Payments Protocol) payment flow
|
|
873
888
|
* Called when pay() detects WWW-Authenticate header in 402 response
|
|
874
889
|
*/
|
|
875
|
-
async handleMPPPayment(
|
|
890
|
+
async handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options = {}) {
|
|
876
891
|
const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
|
|
877
892
|
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
878
893
|
const { tempoModerato } = await import("viem/chains");
|
|
@@ -933,13 +948,14 @@ Please specify: --chain <chain_name>`
|
|
|
933
948
|
source: `did:pkh:eip155:${chainId}:${account.address}`
|
|
934
949
|
};
|
|
935
950
|
const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
936
|
-
const
|
|
951
|
+
const retryBody = options.rawData ? { service, ...params, chain: "tempo_moderato" } : { service, params, chain: "tempo_moderato" };
|
|
952
|
+
const paidRes = await fetch(executeUrl, {
|
|
937
953
|
method: "POST",
|
|
938
954
|
headers: {
|
|
939
955
|
"Content-Type": "application/json",
|
|
940
956
|
"Authorization": `Payment ${credentialB64}`
|
|
941
957
|
},
|
|
942
|
-
body: JSON.stringify(
|
|
958
|
+
body: JSON.stringify(retryBody)
|
|
943
959
|
});
|
|
944
960
|
const result = await paidRes.json();
|
|
945
961
|
if (!paidRes.ok) {
|
|
@@ -959,7 +975,7 @@ Please specify: --chain <chain_name>`
|
|
|
959
975
|
* 4. Server executes service
|
|
960
976
|
* 5. Server calls transferFrom if successful (pay-for-success)
|
|
961
977
|
*/
|
|
962
|
-
async handleBNBPayment(
|
|
978
|
+
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
963
979
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
964
980
|
const tokenConfig = chain.tokens[token];
|
|
965
981
|
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
@@ -1055,13 +1071,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1055
1071
|
};
|
|
1056
1072
|
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
1057
1073
|
console.log(`[MoltsPay] Sending BNB payment request...`);
|
|
1058
|
-
const
|
|
1074
|
+
const bnbRequestBody = options.rawData ? { service, ...params, chain: chainName } : { service, params, chain: chainName };
|
|
1075
|
+
const paidRes = await fetch(executeUrl, {
|
|
1059
1076
|
method: "POST",
|
|
1060
1077
|
headers: {
|
|
1061
1078
|
"Content-Type": "application/json",
|
|
1062
1079
|
"X-Payment": paymentHeader
|
|
1063
1080
|
},
|
|
1064
|
-
body: JSON.stringify(
|
|
1081
|
+
body: JSON.stringify(bnbRequestBody)
|
|
1065
1082
|
});
|
|
1066
1083
|
const result = await paidRes.json();
|
|
1067
1084
|
if (!paidRes.ok) {
|
|
@@ -1078,7 +1095,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1078
1095
|
* 1. Client creates and signs a transfer transaction
|
|
1079
1096
|
* 2. Server submits the transaction after service completes
|
|
1080
1097
|
*/
|
|
1081
|
-
async handleSolanaPayment(
|
|
1098
|
+
async handleSolanaPayment(executeUrl, service, params, requirements, chain, options = {}) {
|
|
1082
1099
|
const solanaWallet = loadSolanaWallet(this.configDir);
|
|
1083
1100
|
if (!solanaWallet) {
|
|
1084
1101
|
throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
|
|
@@ -1131,13 +1148,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1131
1148
|
}
|
|
1132
1149
|
};
|
|
1133
1150
|
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
1134
|
-
const
|
|
1151
|
+
const solanaRequestBody = options.rawData ? { service, ...params, chain } : { service, params, chain };
|
|
1152
|
+
const paidRes = await fetch(executeUrl, {
|
|
1135
1153
|
method: "POST",
|
|
1136
1154
|
headers: {
|
|
1137
1155
|
"Content-Type": "application/json",
|
|
1138
1156
|
"X-Payment": paymentHeader
|
|
1139
1157
|
},
|
|
1140
|
-
body: JSON.stringify(
|
|
1158
|
+
body: JSON.stringify(solanaRequestBody)
|
|
1141
1159
|
});
|
|
1142
1160
|
const result = await paidRes.json();
|
|
1143
1161
|
if (!paidRes.ok) {
|
|
@@ -1282,15 +1300,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1282
1300
|
loadWallet() {
|
|
1283
1301
|
const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
|
|
1284
1302
|
if ((0, import_fs2.existsSync)(walletPath)) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1303
|
+
if (process.platform !== "win32") {
|
|
1304
|
+
try {
|
|
1305
|
+
const stats = (0, import_fs2.statSync)(walletPath);
|
|
1306
|
+
const mode = stats.mode & 511;
|
|
1307
|
+
if (mode !== 384) {
|
|
1308
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
1309
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
1310
|
+
(0, import_fs2.chmodSync)(walletPath, 384);
|
|
1311
|
+
}
|
|
1312
|
+
} catch {
|
|
1292
1313
|
}
|
|
1293
|
-
} catch (err) {
|
|
1294
1314
|
}
|
|
1295
1315
|
const content = (0, import_fs2.readFileSync)(walletPath, "utf-8");
|
|
1296
1316
|
return JSON.parse(content);
|
|
@@ -3273,8 +3293,10 @@ var MoltsPayServer = class {
|
|
|
3273
3293
|
isProxyAllowed(clientIP) {
|
|
3274
3294
|
const allowedIPs = process.env.PROXY_ALLOWED_IPS?.split(",").map((ip) => ip.trim()) || [];
|
|
3275
3295
|
if (allowedIPs.length === 0) {
|
|
3276
|
-
|
|
3277
|
-
|
|
3296
|
+
return true;
|
|
3297
|
+
}
|
|
3298
|
+
if (allowedIPs.includes("*")) {
|
|
3299
|
+
return true;
|
|
3278
3300
|
}
|
|
3279
3301
|
const normalizedIP = clientIP === "::1" ? "127.0.0.1" : clientIP.replace("::ffff:", "");
|
|
3280
3302
|
const allowed = allowedIPs.includes(normalizedIP) || allowedIPs.includes(clientIP);
|
|
@@ -4870,14 +4892,23 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
|
|
|
4870
4892
|
process.exit(1);
|
|
4871
4893
|
}
|
|
4872
4894
|
});
|
|
4873
|
-
program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
|
|
4895
|
+
program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--data <json>", "Raw JSON data to send (for custom input formats)").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
|
|
4874
4896
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
4875
4897
|
if (!client.isInitialized) {
|
|
4876
4898
|
console.error("\u274C Wallet not initialized. Run: npx moltspay init");
|
|
4877
4899
|
process.exit(1);
|
|
4878
4900
|
}
|
|
4879
4901
|
let params = {};
|
|
4880
|
-
|
|
4902
|
+
let useRawData = false;
|
|
4903
|
+
if (options.data) {
|
|
4904
|
+
try {
|
|
4905
|
+
params = JSON.parse(options.data);
|
|
4906
|
+
useRawData = true;
|
|
4907
|
+
} catch {
|
|
4908
|
+
console.error("\u274C Invalid JSON in --data flag");
|
|
4909
|
+
process.exit(1);
|
|
4910
|
+
}
|
|
4911
|
+
} else if (paramsJson) {
|
|
4881
4912
|
try {
|
|
4882
4913
|
params = JSON.parse(paramsJson);
|
|
4883
4914
|
} catch {
|
|
@@ -4885,7 +4916,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
4885
4916
|
process.exit(1);
|
|
4886
4917
|
}
|
|
4887
4918
|
}
|
|
4888
|
-
if (options.prompt) params.prompt = options.prompt;
|
|
4919
|
+
if (!useRawData && options.prompt) params.prompt = options.prompt;
|
|
4889
4920
|
if (options.image) {
|
|
4890
4921
|
const imagePath = options.image;
|
|
4891
4922
|
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
|
|
@@ -4926,7 +4957,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
4926
4957
|
`);
|
|
4927
4958
|
console.log(` Server: ${server}`);
|
|
4928
4959
|
console.log(` Service: ${service}`);
|
|
4929
|
-
|
|
4960
|
+
if (useRawData) {
|
|
4961
|
+
console.log(` Data: ${JSON.stringify(params).slice(0, 50)}${JSON.stringify(params).length > 50 ? "..." : ""}`);
|
|
4962
|
+
} else {
|
|
4963
|
+
console.log(` Prompt: ${params.prompt}`);
|
|
4964
|
+
}
|
|
4930
4965
|
if (imageDisplay) console.log(` Image: ${imageDisplay}`);
|
|
4931
4966
|
console.log(` Chain: ${chain || "(auto)"}`);
|
|
4932
4967
|
console.log(` Token: ${token}`);
|
|
@@ -4936,7 +4971,8 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
4936
4971
|
try {
|
|
4937
4972
|
const result = await client.pay(server, service, params, {
|
|
4938
4973
|
token,
|
|
4939
|
-
chain
|
|
4974
|
+
chain,
|
|
4975
|
+
rawData: useRawData
|
|
4940
4976
|
});
|
|
4941
4977
|
if (options.json) {
|
|
4942
4978
|
console.log(JSON.stringify(result));
|