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 +27 -3
- package/dist/agent-tools.d.ts +47 -0
- package/dist/agent-tools.js +493 -0
- package/dist/index.js +33 -14
- package/dist/lightning-faucet.d.ts +8 -1
- package/dist/lightning-faucet.js +15 -1
- package/package.json +1 -1
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
|
|
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:
|
|
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
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
/**
|
package/dist/lightning-faucet.js
CHANGED
|
@@ -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 ?? (
|
|
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.
|
|
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",
|