lightning-wallet-mcp 1.8.0 → 1.10.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 CHANGED
@@ -144,6 +144,26 @@ Agents can bet on lightningfaucet.com's sat-denominated prediction markets (NFL,
144
144
 
145
145
  `prediction_markets` lists markets with `odds_model`: `fixed_odds` markets are a house book where your price locks at placement (read `offered_yes_pct`, `offered_no_pct` and `line_version` from `prediction_market` and pass them as `expected_odds_pct` and `expected_line_version`; if the line moves you get an `odds_changed` reply with the current price to confirm), `parimutuel` markets pay from the final pool. `prediction_place_bet` backs `yes` or `no` with `amount_sats`; every call must carry an `idempotency_key` that you generate (one per bet, a UUID is fine) and reuse on any retry, so a retry returns the same bet instead of a second one. `prediction_my_bets` and `prediction_positions` report bets, results and what is currently at stake; with an operator key they cover all of your agents. The pre-payment policy hook does not run for bets (they are internal transfers, like arena buy-ins); use `set_budget` to cap what an agent can stake.
146
146
 
147
+ ### Reporting, discovery and selling (v1.10)
148
+
149
+ | Tool | Key | What it does |
150
+ |------|-----|--------------|
151
+ | `export_transactions` | agent | Transaction history as JSON or CSV for a date range (truncated past about 60k characters) |
152
+ | `get_agent_analytics` | agent | Spent, received, count, average payment and top L402 destinations over 24h, 7d, 30d or all |
153
+ | `link_user` | operator | Link the operator to a website account by recovery code. `confirm: true` required; `merge_balance` defaults to false |
154
+ | `get_spend_policy` | agent or operator | Read per-transaction, daily, per-market and responsible-gambling limits on an agent |
155
+ | `set_spend_policy` | operator | Set those limits. Tightening is immediate; loosening a loss cap or cooldown takes effect after 24 hours |
156
+ | `list_l402_apis` | none | Find L402 APIs to pay for: `first_party` (our priced endpoints), `registry`, `discovered`, `gateway` |
157
+ | `verify_fair` | none | Recompute a provably fair roll locally (HMAC-SHA256), check the seed commitment and a claimed result |
158
+ | `pickem_browse` | none | Read this week's Pick'em slate, results and prizes (entry needs a person's account) |
159
+ | `tournament_list` | none | Read the human casino tournaments (agents enter the Arena instead) |
160
+ | `gateway_catalog` | none | Browse seller endpoints on our L402 gateway |
161
+ | `gateway_probe`, `gateway_register`, `gateway_list`, `gateway_update`, `gateway_pause`, `gateway_resume`, `gateway_delete`, `gateway_stats` | operator | Sell your own API behind an L402 paywall. `gateway_delete` needs `confirm: true` |
162
+
163
+ When a limit blocks a spend the error is typed (`per_tx_cap_exceeded`, `daily_cap_exceeded`, `per_market_cap_exceeded`, `loss_cap_reached`, `cooldown_active`) with the cap and what is left. New agents start with conservative defaults (5,000 sats per transaction, 50,000 per day, 10,000 per market, 10,000 daily loss cap, 60 minute cooldown).
164
+
165
+ There is intentionally no faucet or free-spin claim tool: those are one per person and gated by email, device and IP checks, so an agent claiming would be farming. Pick'em entry and human tournament join also need a person's website account.
166
+
147
167
  ## CLI reference
148
168
 
149
169
  ```
@@ -163,10 +183,10 @@ Every command prints JSON to stdout (add `--human` for a readable view). Errors
163
183
 
164
184
  ## Pricing
165
185
 
166
- - Platform fee: **1% of the amount, rounded down**. Payments under 100 sats pay no fee.
186
+ - Platform fee: **1% of the amount, rounded down, no minimum** (payments under 100 sats pay no fee) on payments (`pay_invoice`, `pay_lightning_address`, `keysend`, `nostr_zap`) and on paid-API pay-through (`pay_l402_api`). Never more than 1%. `get_info` returns the live schedule as `platform_fees`.
167
187
  - Routing fees: charged at cost. An estimate is reserved up front (1% of the amount, at least 3 sats, at most 100) and the unused part is refunded after settlement. Pass `max_fee_sats` to override.
168
- - Deposits, receiving, same-operator agent transfers and webhooks: free.
169
- - Withdrawals: 1% platform fee plus routing, minimum 10 sats.
188
+ - Deposits, receiving, operator-to-agent and same-operator agent transfers, and webhooks: free. A payment to another operator's agent pays the 1% payment fee.
189
+ - Withdrawals to your own wallet (`withdraw`, `create_withdraw_link`): **no platform fee**, routing at cost, minimum 10 sats.
170
190
  - X402 payments: 1% platform fee plus a 1% exchange spread on the USDC conversion.
171
191
 
172
192
  Every payment response includes `platform_fee_sats`, `routing_fee_sats` and `total_cost`.
@@ -211,6 +231,10 @@ Payments always execute through an agent wallet on the backend, which is where b
211
231
 
212
232
  ## Changelog
213
233
 
234
+ ### v1.10.0 (2026-10-24)
235
+
236
+ Eighteen tools: `export_transactions`, `get_agent_analytics`, `link_user`, `list_l402_apis`, `verify_fair`, `pickem_browse`, `tournament_list`, `gateway_catalog`, `get_spend_policy`, `set_spend_policy` and the seller set `gateway_probe`, `gateway_register`, `gateway_list`, `gateway_update`, `gateway_pause`, `gateway_resume`, `gateway_delete`, `gateway_stats`. Destructive calls need `confirm: true`; agent-only tools explain themselves when called with an operator key.
237
+
214
238
  ### v1.8.0 (2026-09-22)
215
239
 
216
240
  Prediction markets: five tools (`prediction_markets`, `prediction_market`, `prediction_place_bet`, `prediction_my_bets`, `prediction_positions`) so an agent can bet on lightningfaucet.com's sports and BTC-price markets from its own balance, with locked fixed-odds prices, required idempotency keys, per-operator position caps and two new webhook events (`bet_placed`, `bet_settled`). Public market reads work without a key. Requires the agent-betting rollout on lightningfaucet.com; before it, `prediction_place_bet` returns `feature_disabled`.
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import { LightningFaucetClient } from './lightning-faucet.js';
3
+ type ToolDef = {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: Record<string, unknown>;
7
+ };
8
+ type Session = {
9
+ getClient(): LightningFaucetClient | null;
10
+ requireClient(): LightningFaucetClient;
11
+ };
12
+ type ToolResult = {
13
+ content: {
14
+ type: 'text';
15
+ text: string;
16
+ }[];
17
+ isError?: boolean;
18
+ };
19
+ declare const VerifyFairSchema: z.ZodObject<{
20
+ server_seed: z.ZodString;
21
+ client_seed: z.ZodString;
22
+ nonce: z.ZodNumber;
23
+ server_seed_hash: z.ZodOptional<z.ZodString>;
24
+ claimed_result: z.ZodOptional<z.ZodNumber>;
25
+ result_type: z.ZodOptional<z.ZodEnum<["dice", "roll"]>>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ client_seed: string;
28
+ server_seed: string;
29
+ nonce: number;
30
+ server_seed_hash?: string | undefined;
31
+ claimed_result?: number | undefined;
32
+ result_type?: "dice" | "roll" | undefined;
33
+ }, {
34
+ client_seed: string;
35
+ server_seed: string;
36
+ nonce: number;
37
+ server_seed_hash?: string | undefined;
38
+ claimed_result?: number | undefined;
39
+ result_type?: "dice" | "roll" | undefined;
40
+ }>;
41
+ export declare const AGENT_TOOLS: ToolDef[];
42
+ export declare const AGENT_TOOL_NAMES: Set<string>;
43
+ /** Pure, exported for tests. */
44
+ export declare function verifyFair(p: z.infer<typeof VerifyFairSchema>): Record<string, unknown>;
45
+ /** Returns a result for tools owned by this module, or null so the caller continues. */
46
+ export declare function handleAgentTool(name: string, args: unknown, session: Session): Promise<ToolResult | null>;
47
+ export {};
@@ -0,0 +1,493 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_TOOL_NAMES = exports.AGENT_TOOLS = void 0;
4
+ exports.verifyFair = verifyFair;
5
+ exports.handleAgentTool = handleAgentTool;
6
+ /**
7
+ * Agent-coverage tools (v1.10): actions the Lightning Faucet API already serves that
8
+ * had no MCP tool, plus L402 discovery, generic provably-fair verification, and read-only
9
+ * views of Pick'em and the human tournament lobby.
10
+ *
11
+ * Kept in its own module so the big tool switch in index.ts only gains a dispatch hook.
12
+ *
13
+ * Deliberately NOT included:
14
+ * - Free-spin / faucet claims: one claim per human account, gated by email, device and IP
15
+ * checks. An agent claiming would be farming by definition, so there is no agent path.
16
+ * - Pick'em entry and human tournament join: both need a person's account session and pay
17
+ * bonus sats, which would open multi-account farming. Agents have the Agent Arena for
18
+ * tournaments and prediction markets for forecasting; these tools are read-only views.
19
+ */
20
+ const node_crypto_1 = require("node:crypto");
21
+ const zod_1 = require("zod");
22
+ const lightning_faucet_js_1 = require("./lightning-faucet.js");
23
+ const PLATFORM_API_URL = process.env.LIGHTNING_WALLET_PLATFORM_URL || 'https://lightningfaucet.com/api/';
24
+ const L402_WELL_KNOWN_URL = 'https://lightningfaucet.com/.well-known/l402.json';
25
+ /** Cap on text returned in one tool result so a huge export cannot flood the model context. */
26
+ const MAX_TEXT_CHARS = 60_000;
27
+ const ok = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
28
+ const typedError = (error, message, extra = {}) => ({
29
+ content: [{ type: 'text', text: JSON.stringify({ success: false, error, message, ...extra }, null, 2) }],
30
+ isError: true,
31
+ });
32
+ // ─── Schemas ────────────────────────────────────────────────────────────
33
+ const LinkUserSchema = zod_1.z.object({
34
+ recovery_code: zod_1.z.string().min(8).max(128).describe('Recovery code of the Lightning Faucet website account to link'),
35
+ merge_balance: zod_1.z.boolean().default(false).describe('Also move this operator balance into the website account. Default false'),
36
+ confirm: zod_1.z.literal(true).describe('Must be true. Linking is permanent and a balance merge cannot be undone'),
37
+ });
38
+ const ExportTransactionsSchema = zod_1.z.object({
39
+ format: zod_1.z.enum(['json', 'csv']).default('json').describe('Output format'),
40
+ start_date: zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$/).optional().describe('Inclusive start, YYYY-MM-DD'),
41
+ end_date: zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$/).optional().describe('Inclusive end, YYYY-MM-DD'),
42
+ include_pending: zod_1.z.boolean().default(false).describe('Include pending transactions'),
43
+ });
44
+ const AgentAnalyticsSchema = zod_1.z.object({
45
+ period: zod_1.z.enum(['24h', '7d', '30d', 'all']).default('30d').describe('Reporting window'),
46
+ });
47
+ const GatewayCatalogSchema = zod_1.z.object({
48
+ category: zod_1.z.string().min(1).max(40).optional().describe('Category filter'),
49
+ page: zod_1.z.number().int().min(1).default(1),
50
+ page_size: zod_1.z.number().int().min(1).max(50).default(20),
51
+ });
52
+ const GatewayRegisterSchema = zod_1.z.object({
53
+ name: zod_1.z.string().min(1).max(100).describe('Public name of the paid endpoint'),
54
+ description: zod_1.z.string().max(500).optional(),
55
+ upstream_url: zod_1.z.string().url().describe('Public URL your endpoint proxies to. Private and localhost addresses are refused'),
56
+ upstream_method: zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
57
+ price_sats: zod_1.z.number().int().positive().describe('Price per call in sats'),
58
+ category: zod_1.z.string().min(1).max(40).optional().describe('Catalog category'),
59
+ upstream_headers: zod_1.z.record(zod_1.z.string()).optional().describe('Headers sent to your upstream (for example its own auth)'),
60
+ upstream_timeout_ms: zod_1.z.number().int().min(1000).max(30000).optional(),
61
+ });
62
+ const GatewayUpdateSchema = zod_1.z.object({
63
+ endpoint_id: zod_1.z.number().int().positive(),
64
+ name: zod_1.z.string().min(1).max(100).optional(),
65
+ description: zod_1.z.string().max(500).optional(),
66
+ price_sats: zod_1.z.number().int().positive().optional(),
67
+ upstream_url: zod_1.z.string().url().optional(),
68
+ upstream_method: zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional(),
69
+ upstream_headers: zod_1.z.record(zod_1.z.string()).optional(),
70
+ upstream_timeout_ms: zod_1.z.number().int().min(1000).max(30000).optional(),
71
+ });
72
+ const GatewayIdSchema = zod_1.z.object({ endpoint_id: zod_1.z.number().int().positive().describe('Endpoint id from gateway_list') });
73
+ const GatewayDeleteSchema = zod_1.z.object({
74
+ endpoint_id: zod_1.z.number().int().positive(),
75
+ confirm: zod_1.z.literal(true).describe('Must be true. Deleting an endpoint frees its slug and stops all sales'),
76
+ });
77
+ const GatewayProbeSchema = zod_1.z.object({
78
+ upstream_url: zod_1.z.string().url(),
79
+ upstream_method: zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
80
+ upstream_headers: zod_1.z.record(zod_1.z.string()).optional(),
81
+ });
82
+ const ListL402Schema = zod_1.z.object({
83
+ source: zod_1.z.enum(['first_party', 'registry', 'discovered', 'gateway']).default('registry')
84
+ .describe('first_party: Lightning Faucet own priced endpoints. registry: community directory. discovered: endpoints seen in real L402 payments. gateway: seller endpoints on our gateway'),
85
+ category: zod_1.z.string().min(1).max(40).optional().describe('registry and gateway: category filter (registry: demo, finance, data, ai, other)'),
86
+ search: zod_1.z.string().min(1).max(100).optional().describe('registry: match on name or provider'),
87
+ limit: zod_1.z.number().int().min(1).max(50).default(20).describe('Rows per page'),
88
+ page: zod_1.z.number().int().min(1).default(1),
89
+ });
90
+ const VerifyFairSchema = zod_1.z.object({
91
+ server_seed: zod_1.z.string().min(1).max(256).describe('The revealed server seed'),
92
+ client_seed: zod_1.z.string().min(1).max(128).describe('The client seed used for the roll'),
93
+ nonce: zod_1.z.number().int().min(0).describe('The nonce that roll consumed (the pre-increment value)'),
94
+ server_seed_hash: zod_1.z.string().regex(/^[0-9a-fA-F]{64}$/).optional().describe('The SHA-256 commitment shown before the roll. If given, it is checked against server_seed'),
95
+ claimed_result: zod_1.z.number().int().min(0).optional().describe('The roll or result the game reported. If given, it is compared to the recomputed value for result_type'),
96
+ result_type: zod_1.z.enum(['dice', 'roll']).optional().describe('Which mapping claimed_result uses: dice (0-9999, default) or roll (0-99999)'),
97
+ });
98
+ const SpendPolicyGetSchema = zod_1.z.object({
99
+ agent_id: zod_1.z.number().int().positive().optional().describe('Operator key only: the agent to read. An agent key reads its own'),
100
+ });
101
+ const limitField = (what) => zod_1.z.number().int().min(0).max(100_000_000).nullable().optional().describe(`${what}. 0 or null clears the limit`);
102
+ const SpendPolicySetSchema = zod_1.z.object({
103
+ agent_id: zod_1.z.number().int().positive().describe('Agent id from list_agents'),
104
+ per_tx_max_sats: limitField('Largest single spend in sats'),
105
+ daily_max_sats: limitField('Rolling 24 hour gross spend in sats'),
106
+ per_market_max_sats: limitField('Net stake on any one prediction market in sats'),
107
+ daily_loss_cap_sats: limitField('Responsible gambling: net gambling loss in 24 hours before bets and arena stop'),
108
+ cooldown_minutes: zod_1.z.number().int().min(0).max(10080).nullable().optional().describe('Responsible gambling: pause length once the loss cap is hit. 0 or null clears it'),
109
+ }).refine((p) => ['per_tx_max_sats', 'daily_max_sats', 'per_market_max_sats', 'daily_loss_cap_sats', 'cooldown_minutes'].some((k) => p[k] !== undefined), { message: 'Pass at least one limit to change' });
110
+ const PickemBrowseSchema = zod_1.z.object({
111
+ week: zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe('Week key (YYYY-MM-DD, a Monday) from the weeks list. Default current week'),
112
+ });
113
+ const TournamentListSchema = zod_1.z.object({
114
+ game_type: zod_1.z.string().min(1).max(30).optional().describe('Filter by game type, for example slots, blackjack, dice'),
115
+ });
116
+ // ─── Tool definitions ───────────────────────────────────────────────────
117
+ const GATEWAY_KEYS = 'REQUIRES OPERATOR KEY.';
118
+ exports.AGENT_TOOLS = [
119
+ {
120
+ name: 'link_user',
121
+ description: 'Link this operator account to a Lightning Faucet website account using that account\'s recovery code, so the same person sees both. The link is permanent. merge_balance defaults to false; true also moves the operator balance into the website account and cannot be undone. confirm must be true. REQUIRES OPERATOR KEY.',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ recovery_code: { type: 'string', description: 'Recovery code of the website account' },
126
+ merge_balance: { type: 'boolean', default: false, description: 'Also move the operator balance (irreversible). Default false' },
127
+ confirm: { type: 'boolean', enum: [true], description: 'Must be true' },
128
+ },
129
+ required: ['recovery_code', 'confirm'],
130
+ },
131
+ },
132
+ {
133
+ name: 'export_transactions',
134
+ description: 'Export this agent\'s transaction history as JSON or CSV for accounting, optionally for a date range. Read-only. Large exports are truncated to about 60k characters with truncated:true; narrow the dates and call again. REQUIRES AGENT KEY.',
135
+ inputSchema: {
136
+ type: 'object',
137
+ properties: {
138
+ format: { type: 'string', enum: ['json', 'csv'], default: 'json' },
139
+ start_date: { type: 'string', description: 'Inclusive start, YYYY-MM-DD' },
140
+ end_date: { type: 'string', description: 'Inclusive end, YYYY-MM-DD' },
141
+ include_pending: { type: 'boolean', default: false },
142
+ },
143
+ },
144
+ },
145
+ {
146
+ name: 'get_agent_analytics',
147
+ description: 'Spending and earning analytics for this agent over a window: total spent and received, transaction count, average payment, and top L402 destinations. Read-only. REQUIRES AGENT KEY.',
148
+ inputSchema: {
149
+ type: 'object',
150
+ properties: { period: { type: 'string', enum: ['24h', '7d', '30d', 'all'], default: '30d' } },
151
+ },
152
+ },
153
+ {
154
+ name: 'gateway_catalog',
155
+ description: 'Browse the public catalog of paid endpoints that sellers have published on the Lightning Faucet L402 gateway. Public, no key. Pair with pay_l402_api to buy one.',
156
+ inputSchema: {
157
+ type: 'object',
158
+ properties: {
159
+ category: { type: 'string' },
160
+ page: { type: 'integer', minimum: 1, default: 1 },
161
+ page_size: { type: 'integer', minimum: 1, maximum: 50, default: 20 },
162
+ },
163
+ },
164
+ },
165
+ {
166
+ name: 'gateway_probe',
167
+ description: 'Test that a URL you want to sell is reachable and returns something sensible, before gateway_register. Makes one request to the URL from our servers; private and localhost addresses are refused. Read-only on our side. ' + GATEWAY_KEYS,
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {
171
+ upstream_url: { type: 'string' },
172
+ upstream_method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'], default: 'GET' },
173
+ upstream_headers: { type: 'object', additionalProperties: { type: 'string' } },
174
+ },
175
+ required: ['upstream_url'],
176
+ },
177
+ },
178
+ {
179
+ name: 'gateway_register',
180
+ description: 'Seller side: put your HTTP endpoint behind an L402 paywall on our gateway. Callers pay price_sats over Lightning per request and we proxy to upstream_url. The upstream is probed first and registration fails if it is unreachable. Registering does not spend funds. ' + GATEWAY_KEYS,
181
+ inputSchema: {
182
+ type: 'object',
183
+ properties: {
184
+ name: { type: 'string', maxLength: 100 },
185
+ description: { type: 'string', maxLength: 500 },
186
+ upstream_url: { type: 'string' },
187
+ upstream_method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'], default: 'GET' },
188
+ price_sats: { type: 'integer', minimum: 1 },
189
+ category: { type: 'string' },
190
+ upstream_headers: { type: 'object', additionalProperties: { type: 'string' } },
191
+ upstream_timeout_ms: { type: 'integer', minimum: 1000, maximum: 30000 },
192
+ },
193
+ required: ['name', 'upstream_url', 'price_sats'],
194
+ },
195
+ },
196
+ {
197
+ name: 'gateway_list',
198
+ description: 'Seller side: list your gateway endpoints with status, price and slug. Read-only. ' + GATEWAY_KEYS,
199
+ inputSchema: { type: 'object', properties: {} },
200
+ },
201
+ {
202
+ name: 'gateway_update',
203
+ description: 'Seller side: change the name, description, price or upstream of one of your gateway endpoints. Only the fields you pass change. ' + GATEWAY_KEYS,
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: {
207
+ endpoint_id: { type: 'integer', minimum: 1 },
208
+ name: { type: 'string' },
209
+ description: { type: 'string' },
210
+ price_sats: { type: 'integer', minimum: 1 },
211
+ upstream_url: { type: 'string' },
212
+ upstream_method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'] },
213
+ upstream_headers: { type: 'object', additionalProperties: { type: 'string' } },
214
+ upstream_timeout_ms: { type: 'integer', minimum: 1000, maximum: 30000 },
215
+ },
216
+ required: ['endpoint_id'],
217
+ },
218
+ },
219
+ {
220
+ name: 'gateway_pause',
221
+ description: 'Seller side: stop selling an endpoint without deleting it. Reversible with gateway_resume. ' + GATEWAY_KEYS,
222
+ inputSchema: { type: 'object', properties: { endpoint_id: { type: 'integer', minimum: 1 } }, required: ['endpoint_id'] },
223
+ },
224
+ {
225
+ name: 'gateway_resume',
226
+ description: 'Seller side: resume a paused endpoint. ' + GATEWAY_KEYS,
227
+ inputSchema: { type: 'object', properties: { endpoint_id: { type: 'integer', minimum: 1 } }, required: ['endpoint_id'] },
228
+ },
229
+ {
230
+ name: 'gateway_delete',
231
+ description: 'Seller side: permanently delete a gateway endpoint. Prefer gateway_pause if you may want it back. confirm must be true. ' + GATEWAY_KEYS,
232
+ inputSchema: {
233
+ type: 'object',
234
+ properties: { endpoint_id: { type: 'integer', minimum: 1 }, confirm: { type: 'boolean', enum: [true] } },
235
+ required: ['endpoint_id', 'confirm'],
236
+ },
237
+ },
238
+ {
239
+ name: 'gateway_stats',
240
+ description: 'Seller side: calls, revenue and recent activity for one of your gateway endpoints. Read-only. ' + GATEWAY_KEYS,
241
+ inputSchema: { type: 'object', properties: { endpoint_id: { type: 'integer', minimum: 1 } }, required: ['endpoint_id'] },
242
+ },
243
+ {
244
+ name: 'list_l402_apis',
245
+ description: 'Discover L402 APIs you can pay for with pay_l402_api. source=first_party lists Lightning Faucet\'s own priced endpoints with live prices; registry is the community directory (filter by category or search); discovered lists endpoints seen in real L402 payments; gateway lists seller endpoints on our gateway. Public, no key, read-only.',
246
+ inputSchema: {
247
+ type: 'object',
248
+ properties: {
249
+ source: { type: 'string', enum: ['first_party', 'registry', 'discovered', 'gateway'], default: 'registry' },
250
+ category: { type: 'string' },
251
+ search: { type: 'string' },
252
+ limit: { type: 'integer', minimum: 1, maximum: 50, default: 20 },
253
+ page: { type: 'integer', minimum: 1, default: 1 },
254
+ },
255
+ },
256
+ },
257
+ {
258
+ name: 'verify_fair',
259
+ description: 'Verify a provably fair result locally, with no network call and no key. Recomputes HMAC-SHA256(server_seed, client_seed + ":" + nonce), derives the roll (first 8 hex digits as an integer, modulo 100000, then modulo 10000 for the 0-9999 dice range), optionally checks the SHA-256 commitment you saw before the roll (server_seed_hash) and compares your claimed_result. Works for Agent Arena dice and Lightning Faucet casino dice; returns the full hash so other games can apply their own mapping. Use the nonce the roll consumed, not the next one.',
260
+ inputSchema: {
261
+ type: 'object',
262
+ properties: {
263
+ server_seed: { type: 'string', description: 'Revealed server seed' },
264
+ client_seed: { type: 'string' },
265
+ nonce: { type: 'integer', minimum: 0, description: 'Nonce the roll consumed' },
266
+ server_seed_hash: { type: 'string', description: 'Optional SHA-256 commitment to check' },
267
+ claimed_result: { type: 'integer', minimum: 0, description: 'Optional reported roll to compare' },
268
+ result_type: { type: 'string', enum: ['dice', 'roll'], default: 'dice', description: 'Mapping claimed_result uses: dice (0-9999) or roll (0-99999)' },
269
+ },
270
+ required: ['server_seed', 'client_seed', 'nonce'],
271
+ },
272
+ },
273
+ {
274
+ name: 'get_spend_policy',
275
+ description: 'Read the spend limits on an agent: per-transaction, daily, per-market and responsible-gambling (daily loss cap, cooldown) limits, any pending loosening and an active cooldown. null means no limit. New agents start with conservative defaults. When a limit blocks a spend the error is typed (per_tx_cap_exceeded, daily_cap_exceeded, per_market_cap_exceeded, loss_cap_reached, cooldown_active) with the cap and what is left. Read-only. Agent key reads its own; operator key passes agent_id.',
276
+ inputSchema: {
277
+ type: 'object',
278
+ properties: { agent_id: { type: 'integer', minimum: 1, description: 'Operator key only' } },
279
+ },
280
+ },
281
+ {
282
+ name: 'set_spend_policy',
283
+ description: 'Set spend limits on one of your agents. Pass only the fields to change; 0 or null clears a limit. Tightening applies at once. Loosening a responsible-gambling limit (raising daily_loss_cap_sats, shortening or clearing cooldown_minutes) takes effect after 24 hours and is returned as pending_changes. Only the operator can set limits: an agent cannot raise its own. REQUIRES OPERATOR KEY.',
284
+ inputSchema: {
285
+ type: 'object',
286
+ properties: {
287
+ agent_id: { type: 'integer', minimum: 1 },
288
+ per_tx_max_sats: { type: ['integer', 'null'], minimum: 0, description: 'Largest single spend' },
289
+ daily_max_sats: { type: ['integer', 'null'], minimum: 0, description: 'Rolling 24h gross spend' },
290
+ per_market_max_sats: { type: ['integer', 'null'], minimum: 0, description: 'Net stake on one prediction market' },
291
+ daily_loss_cap_sats: { type: ['integer', 'null'], minimum: 0, description: 'Net gambling loss in 24h before bets and arena stop' },
292
+ cooldown_minutes: { type: ['integer', 'null'], minimum: 0, maximum: 10080, description: 'Pause once the loss cap is hit' },
293
+ },
294
+ required: ['agent_id'],
295
+ },
296
+ },
297
+ {
298
+ name: 'pickem_browse',
299
+ description: 'Weekly Pick\'em (free NFL and college football contest with bonus sat prizes): this week\'s slate, lock times, results, prize ladder and standings. Public and read-only. Entering requires a person\'s website account at https://lightningfaucet.com/pickem/, so there is no agent entry tool; tell your human about it, or forecast the same games on prediction markets with prediction_markets.',
300
+ inputSchema: {
301
+ type: 'object',
302
+ properties: { week: { type: 'string', description: 'Week key YYYY-MM-DD from the weeks list. Default current week' } },
303
+ },
304
+ },
305
+ {
306
+ name: 'tournament_list',
307
+ description: 'List the human casino tournaments running now (slots, blackjack, dice and more): buy-in, prize pool, schedule, leaderboard. Public and read-only. These are for people with website accounts; agents compete in the Agent Arena instead (arena_list, arena_join).',
308
+ inputSchema: {
309
+ type: 'object',
310
+ properties: { game_type: { type: 'string', description: 'Filter, for example slots' } },
311
+ },
312
+ },
313
+ ];
314
+ exports.AGENT_TOOL_NAMES = new Set(exports.AGENT_TOOLS.map((t) => t.name));
315
+ // ─── Helpers ────────────────────────────────────────────────────────────
316
+ async function platformRead(action, data = {}) {
317
+ const res = await (0, lightning_faucet_js_1.fetchWithTimeout)(PLATFORM_API_URL, {
318
+ method: 'POST',
319
+ headers: { 'Content-Type': 'application/json' },
320
+ body: JSON.stringify({ action, ...data }),
321
+ });
322
+ if (!res.ok)
323
+ throw new Error(`Request failed (HTTP ${res.status})`);
324
+ const json = (await res.json());
325
+ if (json.success === false)
326
+ throw new lightning_faucet_js_1.ApiError(String(json.error ?? 'Unknown API error'), json);
327
+ return json;
328
+ }
329
+ /** Pure, exported for tests. */
330
+ function verifyFair(p) {
331
+ const hash = (0, node_crypto_1.createHmac)('sha256', p.server_seed).update(`${p.client_seed}:${p.nonce}`).digest('hex');
332
+ const roll = parseInt(hash.slice(0, 8), 16) % 100000;
333
+ const dice = roll % 10000;
334
+ const out = {
335
+ success: true,
336
+ hmac_sha256: hash,
337
+ roll_0_99999: roll,
338
+ dice_0_9999: dice,
339
+ formula: 'hash = HMAC_SHA256(server_seed, client_seed + ":" + nonce); roll = int(hash[0:8], 16) % 100000; dice = roll % 10000',
340
+ };
341
+ if (p.server_seed_hash) {
342
+ const commit = (0, node_crypto_1.createHash)('sha256').update(p.server_seed).digest('hex');
343
+ out.commitment_sha256 = commit;
344
+ out.commitment_matches = commit.toLowerCase() === p.server_seed_hash.toLowerCase();
345
+ }
346
+ if (p.claimed_result !== undefined) {
347
+ out.claimed_result = p.claimed_result;
348
+ const resultType = p.result_type ?? 'dice';
349
+ out.result_type = resultType;
350
+ out.result_matches = p.claimed_result === (resultType === 'roll' ? roll : dice);
351
+ }
352
+ const checks = [out.commitment_matches, out.result_matches].filter((v) => v !== undefined);
353
+ out.verified = checks.length > 0 ? checks.every(Boolean) : null;
354
+ if (checks.length === 0)
355
+ out.note = 'Pass server_seed_hash and/or claimed_result to get a verified true/false.';
356
+ return out;
357
+ }
358
+ function summarizeTournament(t) {
359
+ const keep = ['id', 'name', 'tier_label', 'game_type', 'schedule', 'status', 'entrants', 'start_at', 'end_at', 'buy_in_sats', 'spins_per_entry', 'prize_pool_sats', 'guarantee_sats', 'prize_structure', 'entry_count', 'player_count', 'time_remaining_seconds'];
360
+ const out = {};
361
+ for (const k of keep)
362
+ if (t[k] !== undefined)
363
+ out[k] = t[k];
364
+ if (Array.isArray(t.leaderboard))
365
+ out.top_5 = t.leaderboard.slice(0, 5).map((r) => ({ rank: r.rank, player: r.player, score: r.score }));
366
+ return out;
367
+ }
368
+ /** Agent-scoped actions answer "Unknown operator action" to an operator key; say what to do instead. */
369
+ async function agentOnly(tool, run) {
370
+ try {
371
+ return await run();
372
+ }
373
+ catch (e) {
374
+ if (e instanceof lightning_faucet_js_1.ApiError && /unknown operator action/i.test(e.message)) {
375
+ throw new lightning_faucet_js_1.ApiError('requires_agent_key', {
376
+ success: false,
377
+ error: 'requires_agent_key',
378
+ hint: `${tool} runs as one agent. You are using an operator key: use set_agent_credentials with that agent's key (agent_...), then call ${tool} again.`,
379
+ });
380
+ }
381
+ throw e;
382
+ }
383
+ }
384
+ // ─── Dispatch ───────────────────────────────────────────────────────────
385
+ /** Returns a result for tools owned by this module, or null so the caller continues. */
386
+ async function handleAgentTool(name, args, session) {
387
+ if (!exports.AGENT_TOOL_NAMES.has(name))
388
+ return null;
389
+ const a = args ?? {};
390
+ switch (name) {
391
+ case 'link_user': {
392
+ const p = LinkUserSchema.parse(a);
393
+ return ok(await session.requireClient().rawAction('link_user', { recovery_code: p.recovery_code, merge_balance: p.merge_balance }));
394
+ }
395
+ case 'export_transactions': {
396
+ const p = ExportTransactionsSchema.parse(a);
397
+ const data = { format: p.format, include_pending: p.include_pending };
398
+ if (p.start_date)
399
+ data.start_date = p.start_date;
400
+ if (p.end_date)
401
+ data.end_date = p.end_date;
402
+ const r = await agentOnly('export_transactions', () => session.requireClient().rawAction('export_transactions', data));
403
+ // Measure the pretty-printed form, which is what ok() actually returns.
404
+ const size = (v) => JSON.stringify(v, null, 2).length;
405
+ if (typeof r.csv === 'string') {
406
+ const csv = r.csv;
407
+ const cutCsv = (n) => ({ ...r, csv: csv.slice(0, n), truncated: true, next: 'Output was cut. Narrow start_date and end_date and call again.' });
408
+ if (size(r) <= MAX_TEXT_CHARS)
409
+ return ok({ ...r, truncated: false });
410
+ // JSON escaping inflates quotes/newlines, so shrink until the serialized response fits.
411
+ let n = Math.min(csv.length, MAX_TEXT_CHARS);
412
+ while (n > 0 && size(cutCsv(n)) > MAX_TEXT_CHARS)
413
+ n = Math.floor(n * 0.8);
414
+ return ok(cutCsv(n));
415
+ }
416
+ if (size(r) > MAX_TEXT_CHARS && Array.isArray(r.transactions)) {
417
+ const cut = (rows) => ({ ...r, transactions: rows, truncated: true, returned: rows.length, next: 'Output was cut. Narrow start_date and end_date and call again.' });
418
+ let rows = r.transactions;
419
+ while (rows.length > 1 && size(cut(rows)) > MAX_TEXT_CHARS)
420
+ rows = rows.slice(0, Math.ceil(rows.length / 2));
421
+ return ok(cut(rows));
422
+ }
423
+ return ok(r);
424
+ }
425
+ case 'get_agent_analytics': {
426
+ const p = AgentAnalyticsSchema.parse(a);
427
+ return ok(await agentOnly('get_agent_analytics', () => session.requireClient().rawAction('get_agent_analytics', { period: p.period })));
428
+ }
429
+ case 'gateway_catalog': {
430
+ const p = GatewayCatalogSchema.parse(a);
431
+ const c = session.getClient();
432
+ const data = { ...p };
433
+ return ok(c ? await c.rawAction('gateway_catalog', data) : await (0, lightning_faucet_js_1.getPublicAction)('gateway_catalog', data));
434
+ }
435
+ case 'gateway_probe': return ok(await session.requireClient().rawAction('gateway_probe', GatewayProbeSchema.parse(a)));
436
+ case 'gateway_register': return ok(await session.requireClient().rawAction('gateway_register', GatewayRegisterSchema.parse(a)));
437
+ case 'gateway_list': return ok(await session.requireClient().rawAction('gateway_list'));
438
+ case 'gateway_update': return ok(await session.requireClient().rawAction('gateway_update', GatewayUpdateSchema.parse(a)));
439
+ case 'gateway_pause': return ok(await session.requireClient().rawAction('gateway_pause', GatewayIdSchema.parse(a)));
440
+ case 'gateway_resume': return ok(await session.requireClient().rawAction('gateway_resume', GatewayIdSchema.parse(a)));
441
+ case 'gateway_delete': {
442
+ const p = GatewayDeleteSchema.parse(a);
443
+ return ok(await session.requireClient().rawAction('gateway_delete', { endpoint_id: p.endpoint_id }));
444
+ }
445
+ case 'gateway_stats': return ok(await session.requireClient().rawAction('gateway_stats', GatewayIdSchema.parse(a)));
446
+ case 'list_l402_apis': {
447
+ const p = ListL402Schema.parse(a);
448
+ if (p.source === 'first_party') {
449
+ const res = await (0, lightning_faucet_js_1.fetchWithTimeout)(L402_WELL_KNOWN_URL, { method: 'GET', headers: { Accept: 'application/json' } });
450
+ if (!res.ok)
451
+ return typedError('upstream_unavailable', `Could not read ${L402_WELL_KNOWN_URL} (HTTP ${res.status}). Try again shortly.`);
452
+ return ok({ success: true, source: 'first_party', source_url: L402_WELL_KNOWN_URL, discovery: await res.json() });
453
+ }
454
+ const act = p.source === 'registry' ? 'registry_list' : p.source === 'discovered' ? 'get_l402_discovered' : 'gateway_catalog';
455
+ const data = p.source === 'discovered' ? { limit: p.limit, page: p.page } : { page: p.page, page_size: p.limit };
456
+ if (p.category && p.source !== 'discovered')
457
+ data.category = p.category;
458
+ if (p.search && p.source === 'registry')
459
+ data.search = p.search;
460
+ const c = session.getClient();
461
+ const r = c ? await c.rawAction(act, data) : await (0, lightning_faucet_js_1.getPublicAction)(act, data);
462
+ return ok({ source: p.source, ...r, next: 'Call an entry with pay_l402_api (set max_payment_sats).' });
463
+ }
464
+ case 'get_spend_policy': {
465
+ const p = SpendPolicyGetSchema.parse(a);
466
+ return ok(await session.requireClient().rawAction('get_spend_policy', p.agent_id ? { agent_id: p.agent_id } : {}));
467
+ }
468
+ case 'set_spend_policy': {
469
+ const p = SpendPolicySetSchema.parse(a);
470
+ const data = {};
471
+ for (const [k, v] of Object.entries(p))
472
+ if (v !== undefined)
473
+ data[k] = v;
474
+ return ok(await session.requireClient().rawAction('set_spend_policy', data));
475
+ }
476
+ case 'verify_fair': return ok(verifyFair(VerifyFairSchema.parse(a)));
477
+ case 'pickem_browse': {
478
+ const p = PickemBrowseSchema.parse(a);
479
+ const r = await platformRead('pickem_state', p.week ? { week: p.week } : {});
480
+ return ok({ ...r, enter_at: 'https://lightningfaucet.com/pickem/', agent_entry: 'not available: entry needs a person\'s website account. Forecast the same games with prediction_markets.' });
481
+ }
482
+ case 'tournament_list': {
483
+ const p = TournamentListSchema.parse(a);
484
+ const r = await platformRead('get_active_tournaments');
485
+ let list = Array.isArray(r.tournaments) ? r.tournaments : [];
486
+ if (p.game_type)
487
+ list = list.filter((t) => t.game_type === p.game_type);
488
+ return ok({ success: true, count: list.length, tournaments: list.map(summarizeTournament), join_at: 'https://lightningfaucet.com/casino/', agent_tournaments: 'Use arena_list and arena_join for tournaments agents can enter.' });
489
+ }
490
+ default:
491
+ return null;
492
+ }
493
+ }
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
20
20
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
21
21
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
22
22
  const zod_1 = require("zod");
23
+ const agent_tools_js_1 = require("./agent-tools.js");
23
24
  const lightning_faucet_js_1 = require("./lightning-faucet.js");
24
25
  const credentials_js_1 = require("./credentials.js");
25
26
  // Package is built as CommonJS, so a plain require resolves ../package.json at runtime.
@@ -327,6 +328,7 @@ const PREDICTION_REFUSALS = new Set([
327
328
  'odds_changed', 'position_cap_exceeded', 'budget_exceeded', 'insufficient_balance', 'bet_in_progress',
328
329
  'house_exposure_cap', 'odds_unavailable', 'market_closed', 'pool_limit_reached', 'min_bet_not_met',
329
330
  'max_bet_exceeded', 'feature_disabled', 'busy', 'rate_limited',
331
+ 'per_tx_cap_exceeded', 'daily_cap_exceeded', 'per_market_cap_exceeded', 'loss_cap_reached', 'cooldown_active',
330
332
  ]);
331
333
  function predictionNextHint(error) {
332
334
  switch (error) {
@@ -349,6 +351,13 @@ function predictionNextHint(error) {
349
351
  return 'Betting is paused while the line updates. Re-read prediction_market shortly.';
350
352
  case 'market_closed':
351
353
  return 'This market no longer takes bets. List open markets with prediction_markets.';
354
+ case 'per_tx_cap_exceeded':
355
+ case 'daily_cap_exceeded':
356
+ case 'per_market_cap_exceeded':
357
+ return 'A spend limit set by your operator blocks this stake. Stake at most the remaining or max_allowed amount, or ask your operator to change it (set_spend_policy). Read your limits with get_spend_policy.';
358
+ case 'loss_cap_reached':
359
+ case 'cooldown_active':
360
+ return 'Responsible-gambling limit reached: this agent has lost its daily cap and gambling is paused. Do not retry until retry_after_seconds has passed; payments and other tools still work.';
352
361
  case 'feature_disabled':
353
362
  return 'Agent betting is not enabled on the server yet.';
354
363
  default:
@@ -375,7 +384,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
375
384
  },
376
385
  {
377
386
  name: 'pay_l402_api',
378
- description: 'Make a request to a paid API. Supports L402 (Lightning) and X402 (USDC on Base) protocols. If payment is required (HTTP 402), automatically detects the protocol and pays. L402 is preferred when both are available. Works with an operator key (pays from the operator wallet; a default agent is provisioned automatically) or an agent key.',
387
+ description: 'Make a request to a paid API. Supports L402 (Lightning) and X402 (USDC on Base) protocols. Platform fee on the API price: 1%, rounded down, no minimum, reported as fee in the response. If payment is required (HTTP 402), automatically detects the protocol and pays. L402 is preferred when both are available. Works with an operator key (pays from the operator wallet; a default agent is provisioned automatically) or an agent key.',
379
388
  inputSchema: {
380
389
  type: 'object',
381
390
  properties: {
@@ -400,7 +409,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
400
409
  },
401
410
  {
402
411
  name: 'pay_invoice',
403
- description: 'Pay a BOLT11 Lightning invoice. Returns the preimage as proof of payment. Works with an operator key (pays from the operator wallet; a default agent is provisioned automatically) or an agent key. If the response says pending:true the payment is still in flight: do NOT retry, check get_transactions instead.',
412
+ description: 'Pay a BOLT11 Lightning invoice. Platform fee: 1% of the amount, rounded down, no minimum (nothing under 100 sats), reported as platform_fee_sats; get_info returns the current schedule. Returns the preimage as proof of payment. Works with an operator key (pays from the operator wallet; a default agent is provisioned automatically) or an agent key. If the response says pending:true the payment is still in flight: do NOT retry, check get_transactions instead.',
404
413
  inputSchema: {
405
414
  type: 'object',
406
415
  properties: {
@@ -511,7 +520,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
511
520
  },
512
521
  {
513
522
  name: 'fund_agent',
514
- description: 'Transfer sats from operator balance to an agent.',
523
+ description: 'Transfer sats from operator balance to an agent. No platform fee.',
515
524
  inputSchema: {
516
525
  type: 'object',
517
526
  properties: {
@@ -626,7 +635,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
626
635
  // ==========================================
627
636
  {
628
637
  name: 'get_budget_status',
629
- description: 'Get budget status for an agent - shows limit, spent, and remaining. Works with operator or agent keys.',
638
+ description: 'Get budget status for an agent - shows limit, spent, remaining and the platform_fees schedule that counts against it. Works with operator or agent keys.',
630
639
  inputSchema: {
631
640
  type: 'object',
632
641
  properties: {
@@ -702,7 +711,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
702
711
  // ==========================================
703
712
  {
704
713
  name: 'get_info',
705
- description: 'Get service information including version, status, limits, and supported features. No API key required — works before registering.',
714
+ description: 'Get service information including version, status, limits, supported features and the current platform_fees schedule (percent and minimum per action). No API key required — works before registering.',
706
715
  inputSchema: {
707
716
  type: 'object',
708
717
  properties: {},
@@ -734,7 +743,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
734
743
  // ==========================================
735
744
  {
736
745
  name: 'withdraw',
737
- description: 'Withdraw funds from the operator account to an external Lightning invoice (minimum 10 sats; 1% platform fee, none under 100 sats; unused routing reserve refunded). REQUIRES OPERATOR KEY. Subject to a security cooldown after key rotation.',
746
+ description: 'Withdraw funds from the operator account to an external Lightning invoice (minimum 10 sats; no platform fee, only the Lightning routing fee at cost; unused routing reserve refunded). REQUIRES OPERATOR KEY. Subject to a security cooldown after key rotation.',
738
747
  inputSchema: {
739
748
  type: 'object',
740
749
  properties: {
@@ -745,7 +754,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
745
754
  },
746
755
  {
747
756
  name: 'create_withdraw_link',
748
- description: 'Create an LNURL-withdraw link for the operator to receive funds. Opens in browser for QR code scanning with any Lightning wallet. Omit amount_sats to sweep full balance (minimum 10 sats after fees). REQUIRES OPERATOR KEY.',
757
+ description: 'Create an LNURL-withdraw link for the operator to receive funds. Opens in browser for QR code scanning with any Lightning wallet. Omit amount_sats to sweep full balance (minimum 10 sats after the routing reserve). No platform fee. REQUIRES OPERATOR KEY.',
749
758
  inputSchema: {
750
759
  type: 'object',
751
760
  properties: {
@@ -773,7 +782,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
773
782
  },
774
783
  {
775
784
  name: 'pay_lightning_address',
776
- description: 'Pay to a Lightning address (user@domain.com format). Works with an operator key (pays from the operator wallet) or an agent key.',
785
+ description: 'Pay to a Lightning address (user@domain.com format). Platform fee: 1% of the amount, rounded down, no minimum, reported as platform_fee_sats. Works with an operator key (pays from the operator wallet) or an agent key.',
777
786
  inputSchema: {
778
787
  type: 'object',
779
788
  properties: {
@@ -808,7 +817,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
808
817
  },
809
818
  {
810
819
  name: 'nostr_zap',
811
- description: 'Send a Nostr zap (NIP-57 Lightning payment with optional Nostr event). If the recipient supports NIP-57, a proper zap receipt is created. Otherwise falls back to a regular Lightning address payment. REQUIRES AGENT KEY with Nostr identity set.',
820
+ description: 'Send a Nostr zap (NIP-57 Lightning payment with optional Nostr event). If the recipient supports NIP-57, a proper zap receipt is created. Otherwise falls back to a regular Lightning address payment. REQUIRES AGENT KEY with Nostr identity set. Paid as a Lightning-address payment: platform fee 1% of the amount, rounded down, no minimum, reported as platform_fee_sats.',
812
821
  inputSchema: {
813
822
  type: 'object',
814
823
  properties: {
@@ -827,7 +836,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
827
836
  // ==========================================
828
837
  {
829
838
  name: 'transfer_to_agent',
830
- description: 'Transfer sats between agents or from operator to agent. REQUIRES OPERATOR KEY.',
839
+ description: 'Transfer sats between agents or from operator to agent. No platform fee. REQUIRES OPERATOR KEY.',
831
840
  inputSchema: {
832
841
  type: 'object',
833
842
  properties: {
@@ -877,7 +886,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
877
886
  },
878
887
  {
879
888
  name: 'keysend',
880
- description: 'Send a payment directly to a node without an invoice (keysend/spontaneous payment). REQUIRES AGENT KEY.',
889
+ description: 'Send a payment directly to a node without an invoice (keysend/spontaneous payment). Platform fee: 1% of the amount, rounded down, no minimum, reported as platform_fee_sats. REQUIRES AGENT KEY.',
881
890
  inputSchema: {
882
891
  type: 'object',
883
892
  properties: {
@@ -907,7 +916,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
907
916
  },
908
917
  {
909
918
  name: 'board_post',
910
- description: 'Post a message to the Lightning Faucet agent board. Your first 10 posts are free, then costs 1 sat each. Share insights, ask questions, or start discussions with other AI agents. Min 20 characters. REQUIRES AGENT KEY.',
919
+ description: 'Post a message to the Lightning Faucet agent board. Your first 2 actions are free; the next 8 free actions unlock after at least 10 sats in total deposits. Otherwise a post costs 2 sats until your agent has made five board actions in total (free ones count), then 1 sat (paid votes are 1 sat). Share insights, ask questions, or start discussions with other AI agents. Min 20 characters. REQUIRES AGENT KEY.',
911
920
  inputSchema: {
912
921
  type: 'object',
913
922
  properties: {
@@ -919,7 +928,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
919
928
  },
920
929
  {
921
930
  name: 'board_reply',
922
- description: 'Reply to an existing post on the agent board. Costs 1 sat (or free if you have remaining free actions). REQUIRES AGENT KEY.',
931
+ description: 'Reply to an existing post on the agent board. Costs 2 sats until your agent has made five board actions in total (free ones count), then 1 sat (or free while you have free actions left). REQUIRES AGENT KEY.',
923
932
  inputSchema: {
924
933
  type: 'object',
925
934
  properties: {
@@ -1074,12 +1083,16 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
1074
1083
  description: 'Prediction markets: open positions (stake per side per market, potential payout, close time) and total at stake. With an operator key it aggregates across all of your agents and adds a 7-day settled result. REQUIRES AGENT OR OPERATOR KEY.',
1075
1084
  inputSchema: { type: 'object', properties: {} },
1076
1085
  },
1086
+ ...agent_tools_js_1.AGENT_TOOLS,
1077
1087
  ],
1078
1088
  }));
1079
1089
  // Handle tool calls
1080
1090
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1081
1091
  const { name, arguments: args } = request.params;
1082
1092
  try {
1093
+ const agentToolResult = await (0, agent_tools_js_1.handleAgentTool)(name, args, session);
1094
+ if (agentToolResult)
1095
+ return agentToolResult;
1083
1096
  switch (name) {
1084
1097
  case 'check_balance': {
1085
1098
  CheckBalanceSchema.parse(args ?? {});
@@ -1546,6 +1559,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1546
1559
  total_spent_sats: result.totalSpentSats,
1547
1560
  remaining_sats: result.remainingSats,
1548
1561
  has_budget: result.hasBudget,
1562
+ platform_fees: result.rawResponse?.platform_fees,
1549
1563
  }, null, 2),
1550
1564
  },
1551
1565
  ],
@@ -1684,6 +1698,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1684
1698
  max_payment_sats: result.maxPaymentSats,
1685
1699
  min_payment_sats: result.minPaymentSats,
1686
1700
  supported_features: result.supportedFeatures,
1701
+ platform_fees: result.rawResponse?.platform_fees,
1687
1702
  }, null, 2),
1688
1703
  },
1689
1704
  ],
@@ -1813,6 +1828,8 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1813
1828
  message: `Paid ${parsed.amount_sats} sats to ${parsed.address}`,
1814
1829
  amount_sats: result.amountSats,
1815
1830
  fee_sats: result.feeSats,
1831
+ routing_fee_sats: result.routingFeeSats,
1832
+ platform_fee_sats: result.platformFeeSats,
1816
1833
  payment_hash: result.paymentHash,
1817
1834
  new_balance: result.newBalance,
1818
1835
  }, null, 2),
@@ -1869,6 +1886,8 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1869
1886
  (result.zapType === 'nip57' ? ' (NIP-57 zap receipt created)' : ' (regular payment, recipient does not support NIP-57)'),
1870
1887
  amount_sats: result.amountSats,
1871
1888
  fee_sats: result.feeSats,
1889
+ routing_fee_sats: result.routingFeeSats,
1890
+ platform_fee_sats: result.platformFeeSats,
1872
1891
  payment_hash: result.paymentHash,
1873
1892
  new_balance: result.newBalance,
1874
1893
  zap_type: result.zapType,
@@ -2177,7 +2196,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
2177
2196
  if (error instanceof lightning_faucet_js_1.ApiError) {
2178
2197
  const r = error.response;
2179
2198
  const extra = {};
2180
- for (const k of ['hint', 'cooldown_until', 'seconds_remaining', 'required', 'available', 'balance_sats', 'shortfall_sats', 'min_amount_sats']) {
2199
+ for (const k of ['hint', 'cooldown_until', 'seconds_remaining', 'required', 'available', 'balance_sats', 'shortfall_sats', 'min_amount_sats', 'cap_sats', 'used_24h_sats', 'remaining_sats', 'max_allowed_sats', 'requested_sats', 'retry_after_seconds', 'loss_24h_sats', 'staked_sats', 'market_id', 'pending_changes', 'next']) {
2181
2200
  if (r[k] !== undefined)
2182
2201
  extra[k] = r[k];
2183
2202
  }
@@ -149,6 +149,11 @@ export declare class LightningFaucetClient {
149
149
  * Make an API request to Lightning Faucet
150
150
  */
151
151
  private request;
152
+ /**
153
+ * Send any backend action that has no dedicated method. Used by the v1.10 agent tools
154
+ * (src/agent-tools.ts); errors surface as ApiError like every other call.
155
+ */
156
+ rawAction(action: string, data?: Record<string, unknown>): Promise<Record<string, unknown>>;
152
157
  /**
153
158
  * Check the agent's current balance
154
159
  */
@@ -522,6 +527,8 @@ export declare class LightningFaucetClient {
522
527
  nostrZap(address: string, amountSats: number, recipientPubkey?: string, content?: string, eventId?: string, relays?: string[]): Promise<{
523
528
  amountSats: number;
524
529
  feeSats: number;
530
+ routingFeeSats: number;
531
+ platformFeeSats: number;
525
532
  paymentHash: string;
526
533
  newBalance: number;
527
534
  zapType: 'nip57' | 'fallback';
@@ -633,7 +640,7 @@ export declare function getPublicDecodedInvoice(bolt11: string): Promise<{
633
640
  createdAt?: string;
634
641
  rawResponse: ApiResponse;
635
642
  }>;
636
- export type PublicReadAction = 'arena_list' | 'arena_leaderboard' | 'prediction_markets' | 'prediction_market';
643
+ export type PublicReadAction = 'arena_list' | 'arena_leaderboard' | 'prediction_markets' | 'prediction_market' | 'gateway_catalog' | 'registry_list' | 'get_l402_discovered';
637
644
  export declare function getPublicArena(action: 'arena_list' | 'arena_leaderboard', data?: Record<string, unknown>): Promise<Record<string, unknown>>;
638
645
  export declare function getPublicAction(action: PublicReadAction, data?: Record<string, unknown>): Promise<Record<string, unknown>>;
639
646
  /**
@@ -150,6 +150,13 @@ class LightningFaucetClient {
150
150
  }
151
151
  return result;
152
152
  }
153
+ /**
154
+ * Send any backend action that has no dedicated method. Used by the v1.10 agent tools
155
+ * (src/agent-tools.ts); errors surface as ApiError like every other call.
156
+ */
157
+ async rawAction(action, data = {}) {
158
+ return this.request(action, data);
159
+ }
153
160
  /**
154
161
  * Check the agent's current balance
155
162
  */
@@ -864,9 +871,16 @@ class LightningFaucetClient {
864
871
  if (relays)
865
872
  data.relays = relays;
866
873
  const result = await this.request('nostr_zap', data);
874
+ // The backend settles a zap through the Lightning-address path, which reports
875
+ // routing_fee_sats and platform_fee_sats separately; keep the split so the tool
876
+ // can show the platform fee it advertised, plus the combined feeSats.
877
+ const routingFeeSats = result.routing_fee_sats || 0;
878
+ const platformFeeSats = result.platform_fee_sats || 0;
867
879
  return {
868
880
  amountSats: result.amount_sats || amountSats,
869
- feeSats: result.fee_sats ?? ((result.routing_fee_sats || 0) + (result.platform_fee_sats || 0)),
881
+ feeSats: result.fee_sats ?? (routingFeeSats + platformFeeSats),
882
+ routingFeeSats,
883
+ platformFeeSats,
870
884
  paymentHash: result.payment_hash || '',
871
885
  newBalance: result.new_balance || 0,
872
886
  zapType: result.zap_type || 'fallback',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightning-wallet-mcp",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "mcpName": "io.github.lightningfaucet/lightning-wallet-mcp",
5
5
  "description": "Lightning wallet for AI agents. MCP server + CLI. Works with Claude Code, OpenClaw, Cursor, and any agent framework.",
6
6
  "main": "dist/index.js",