clanker-wallet 0.6.0 → 0.7.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 +2 -2
- package/SKILL.md +2 -2
- package/dist/mcp.d.ts +28 -0
- package/dist/mcp.js +44 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,8 +176,8 @@ Claude Desktop (`claude_desktop_config.json`), Cursor (`~/.cursor/mcp.json`), an
|
|
|
176
176
|
|
|
177
177
|
| Tool | Arguments | Returns |
|
|
178
178
|
|------|-----------|---------|
|
|
179
|
-
| `request_evm_tx` | `chain_id`, `to`, `value` (wei, integer string), `data` (default `0x`), `reason`, optional `urgency` / `expected_outcome` | `{"request_id":"req_...","status":"pending","expires_at":"..."}` |
|
|
180
|
-
| `request_transfer` | `family` (`bitcoin`), `network` (`mainnet`, `testnet`, `signet`), `to`, `amount` (sats, integer string), `reason`, optional `urgency` / `expected_outcome` | as above |
|
|
179
|
+
| `request_evm_tx` | `chain_id`, `to`, `value` (wei, integer string), `data` (default `0x`), `reason`, optional `urgency` / `expected_outcome` / `from` (the paired wallet to send from, needed when several wallets of the same family are paired) | `{"request_id":"req_...","status":"pending","expires_at":"..."}` |
|
|
180
|
+
| `request_transfer` | `family` (`bitcoin`), `network` (`mainnet`, `testnet`, `signet`), `to`, `amount` (sats, integer string), `reason`, optional `urgency` / `expected_outcome` / `from` (the paired wallet to send from, needed when several wallets of the same family are paired) | as above |
|
|
181
181
|
| `get_request` | `request_id`, optional `wait_seconds` (at most 50) | `status`: `pending`, `approved` (with `tx_hash`), `rejected`, `failed` (with `error`), `unknown` (with `error`) or `expired` |
|
|
182
182
|
| `list_pairings` | none | `agent_id`, `fingerprint`, and the paired families (`evm`, `bitcoin`) with network, wallet and mode |
|
|
183
183
|
|
package/SKILL.md
CHANGED
|
@@ -215,8 +215,8 @@ Claude Desktop (`claude_desktop_config.json`), Cursor (`~/.cursor/mcp.json`), an
|
|
|
215
215
|
|
|
216
216
|
| Tool | Arguments | Returns |
|
|
217
217
|
|------|-----------|---------|
|
|
218
|
-
| `request_evm_tx` | `chain_id`, `to`, `value` (wei, integer string), `data` (default `0x`), `reason`, optional `urgency` / `expected_outcome` | `{"request_id":"req_...","status":"pending","expires_at":"..."}` |
|
|
219
|
-
| `request_transfer` | `family` (`bitcoin`), `network` (`mainnet`, `testnet`, `signet`), `to`, `amount` (sats, integer string), `reason`, optional `urgency` / `expected_outcome` | as above |
|
|
218
|
+
| `request_evm_tx` | `chain_id`, `to`, `value` (wei, integer string), `data` (default `0x`), `reason`, optional `urgency` / `expected_outcome` / `from` (the paired wallet to send from, needed when several wallets of the same family are paired) | `{"request_id":"req_...","status":"pending","expires_at":"..."}` |
|
|
219
|
+
| `request_transfer` | `family` (`bitcoin`), `network` (`mainnet`, `testnet`, `signet`), `to`, `amount` (sats, integer string), `reason`, optional `urgency` / `expected_outcome` / `from` (the paired wallet to send from, needed when several wallets of the same family are paired) | as above |
|
|
220
220
|
| `get_request` | `request_id`, optional `wait_seconds` (at most 50) | `status`: `pending`, `approved` (with `tx_hash`), `rejected`, `failed` (with `error`), `unknown` (with `error`) or `expired` |
|
|
221
221
|
| `list_pairings` | none | `agent_id`, `fingerprint`, and the paired families (`evm`, `bitcoin`) with network, wallet and mode |
|
|
222
222
|
|
package/dist/mcp.d.ts
CHANGED
|
@@ -41,6 +41,10 @@ export declare const MCP_TOOLS: ({
|
|
|
41
41
|
inputSchema: {
|
|
42
42
|
type: string;
|
|
43
43
|
properties: {
|
|
44
|
+
from: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
44
48
|
reason: {
|
|
45
49
|
type: string;
|
|
46
50
|
description: string;
|
|
@@ -75,12 +79,22 @@ export declare const MCP_TOOLS: ({
|
|
|
75
79
|
};
|
|
76
80
|
required: string[];
|
|
77
81
|
};
|
|
82
|
+
annotations: {
|
|
83
|
+
readOnlyHint: boolean;
|
|
84
|
+
destructiveHint: boolean;
|
|
85
|
+
idempotentHint: boolean;
|
|
86
|
+
openWorldHint: boolean;
|
|
87
|
+
};
|
|
78
88
|
} | {
|
|
79
89
|
name: string;
|
|
80
90
|
description: string;
|
|
81
91
|
inputSchema: {
|
|
82
92
|
type: string;
|
|
83
93
|
properties: {
|
|
94
|
+
from: {
|
|
95
|
+
type: string;
|
|
96
|
+
description: string;
|
|
97
|
+
};
|
|
84
98
|
reason: {
|
|
85
99
|
type: string;
|
|
86
100
|
description: string;
|
|
@@ -117,6 +131,12 @@ export declare const MCP_TOOLS: ({
|
|
|
117
131
|
};
|
|
118
132
|
required: string[];
|
|
119
133
|
};
|
|
134
|
+
annotations: {
|
|
135
|
+
readOnlyHint: boolean;
|
|
136
|
+
destructiveHint: boolean;
|
|
137
|
+
idempotentHint: boolean;
|
|
138
|
+
openWorldHint: boolean;
|
|
139
|
+
};
|
|
120
140
|
} | {
|
|
121
141
|
name: string;
|
|
122
142
|
description: string;
|
|
@@ -136,6 +156,10 @@ export declare const MCP_TOOLS: ({
|
|
|
136
156
|
};
|
|
137
157
|
required: string[];
|
|
138
158
|
};
|
|
159
|
+
annotations: {
|
|
160
|
+
readOnlyHint: boolean;
|
|
161
|
+
openWorldHint: boolean;
|
|
162
|
+
};
|
|
139
163
|
} | {
|
|
140
164
|
name: string;
|
|
141
165
|
description: string;
|
|
@@ -147,6 +171,10 @@ export declare const MCP_TOOLS: ({
|
|
|
147
171
|
};
|
|
148
172
|
required?: undefined;
|
|
149
173
|
};
|
|
174
|
+
annotations: {
|
|
175
|
+
readOnlyHint: boolean;
|
|
176
|
+
openWorldHint: boolean;
|
|
177
|
+
};
|
|
150
178
|
})[];
|
|
151
179
|
/** what get_request reports, in the order the docs list them */
|
|
152
180
|
export declare const REQUEST_STATUSES: readonly ["pending", "approved", "rejected", "failed", "unknown", "expired"];
|
package/dist/mcp.js
CHANGED
|
@@ -20,6 +20,17 @@ const CONTEXT_PROPERTIES = {
|
|
|
20
20
|
urgency: { type: 'string', enum: URGENCIES, description: 'priority hint shown to the human' },
|
|
21
21
|
expected_outcome: { type: 'string', description: 'what will happen once it is sent' },
|
|
22
22
|
};
|
|
23
|
+
/** the same on both request tools; the hosted connector copies these definitions verbatim */
|
|
24
|
+
const FROM_PROPERTY = {
|
|
25
|
+
from: { type: 'string', description: 'the paired wallet address to send from, as list_pairings shows it; needed only when several wallets of the same family (evm or bitcoin) are paired' },
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* MCP tool annotations: assistants (ChatGPT, dots, Claude) may skip their confirmation prompt for read-only tools.
|
|
29
|
+
* a request moves nothing until the human approves it on their device, so it is not destructive, but each call
|
|
30
|
+
* sends a new request to the human's wallet.
|
|
31
|
+
*/
|
|
32
|
+
const REQUEST_HINTS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
33
|
+
const READ_HINTS = { readOnlyHint: true, openWorldHint: false };
|
|
23
34
|
export const MCP_TOOLS = [
|
|
24
35
|
{
|
|
25
36
|
name: 'request_evm_tx',
|
|
@@ -35,9 +46,11 @@ export const MCP_TOOLS = [
|
|
|
35
46
|
value: { type: 'string', description: 'integer string in wei; "0" for a contract call that sends no ETH' },
|
|
36
47
|
data: { type: 'string', description: 'hex calldata, default "0x" (a plain transfer)' },
|
|
37
48
|
...CONTEXT_PROPERTIES,
|
|
49
|
+
...FROM_PROPERTY,
|
|
38
50
|
},
|
|
39
51
|
required: ['chain_id', 'to', 'value', 'reason'],
|
|
40
52
|
},
|
|
53
|
+
annotations: REQUEST_HINTS,
|
|
41
54
|
},
|
|
42
55
|
{
|
|
43
56
|
name: 'request_transfer',
|
|
@@ -53,9 +66,11 @@ export const MCP_TOOLS = [
|
|
|
53
66
|
to: { type: 'string', description: 'segwit address on that network: bc1... on mainnet, tb1... on testnet and signet' },
|
|
54
67
|
amount: { type: 'string', description: 'integer string in sats' },
|
|
55
68
|
...CONTEXT_PROPERTIES,
|
|
69
|
+
...FROM_PROPERTY,
|
|
56
70
|
},
|
|
57
71
|
required: ['family', 'network', 'to', 'amount', 'reason'],
|
|
58
72
|
},
|
|
73
|
+
annotations: REQUEST_HINTS,
|
|
59
74
|
},
|
|
60
75
|
{
|
|
61
76
|
name: 'get_request',
|
|
@@ -72,12 +87,14 @@ export const MCP_TOOLS = [
|
|
|
72
87
|
},
|
|
73
88
|
required: ['request_id'],
|
|
74
89
|
},
|
|
90
|
+
annotations: READ_HINTS,
|
|
75
91
|
},
|
|
76
92
|
{
|
|
77
93
|
name: 'list_pairings',
|
|
78
94
|
description: 'List the wallets you can request on (chain family, network, wallet address, mode) and your agent fingerprint, ' +
|
|
79
95
|
'which the human compares in the web app before trusting you.',
|
|
80
96
|
inputSchema: { type: 'object', properties: {} },
|
|
97
|
+
annotations: READ_HINTS,
|
|
81
98
|
},
|
|
82
99
|
];
|
|
83
100
|
const INSTRUCTIONS = 'clanker-wallet sends transaction requests to a human, who approves or rejects each one in their own wallet; ' +
|
|
@@ -123,6 +140,23 @@ function contextFrom(args) {
|
|
|
123
140
|
}
|
|
124
141
|
return context;
|
|
125
142
|
}
|
|
143
|
+
/** the optional wallet address to send from; undefined means the saved pairing */
|
|
144
|
+
function fromArg(args) {
|
|
145
|
+
const { from } = args;
|
|
146
|
+
if (from !== undefined && typeof from !== 'string')
|
|
147
|
+
throw new ToolInputError('invalid from: must be the wallet address as list_pairings shows it');
|
|
148
|
+
return from;
|
|
149
|
+
}
|
|
150
|
+
/** a tool error when `from` names a wallet other than the saved pairing's (case-insensitive: evm hex and bech32 both are) */
|
|
151
|
+
function fromMismatch(family, from, pairing) {
|
|
152
|
+
if (from === undefined || from.toLowerCase() === pairing.wallet?.toLowerCase())
|
|
153
|
+
return null;
|
|
154
|
+
// == null: a cli-written pairing stores null, a hand-written file may leave the key out
|
|
155
|
+
const paired = pairing.wallet == null
|
|
156
|
+
? `the ${family} pairing records no wallet address to check it against`
|
|
157
|
+
: `the paired ${family} wallet is ${pairing.wallet}, the only one this server sends from`;
|
|
158
|
+
return toolError(`from ${JSON.stringify(from)} is not the paired wallet: ${paired}; nothing was sent`);
|
|
159
|
+
}
|
|
126
160
|
const NOT_PAIRED = {
|
|
127
161
|
evm: 'not paired with an evm wallet: ask the human to open https://clanker-wallet.xyz, copy the pairing JSON, and run: clanker-wallet pair \'<JSON>\'',
|
|
128
162
|
bitcoin: 'not paired with a bitcoin wallet: ask the human to open the bitcoin tab at https://clanker-wallet.xyz, copy its pairing JSON, and run: clanker-wallet pair \'<JSON>\'',
|
|
@@ -135,12 +169,16 @@ export function createMcpServer(options) {
|
|
|
135
169
|
const inFlight = new Map();
|
|
136
170
|
/**
|
|
137
171
|
* start a request on the family's pairing and answer once the relay has it: { request_id } after
|
|
138
|
-
* the wallet sends it, or a tool error when it fails first (bad input,
|
|
172
|
+
* the wallet sends it, or a tool error when it fails first (bad input, `from` not the paired wallet, unreachable relay,
|
|
173
|
+
* join timeout, cancelled)
|
|
139
174
|
*/
|
|
140
|
-
function submit(family, cancelled, send) {
|
|
175
|
+
function submit(family, from, cancelled, send) {
|
|
141
176
|
const pairing = options.loadPairing(family);
|
|
142
177
|
if (!pairing)
|
|
143
178
|
return Promise.resolve(toolError(NOT_PAIRED[family]));
|
|
179
|
+
const mismatch = fromMismatch(family, from, pairing);
|
|
180
|
+
if (mismatch)
|
|
181
|
+
return Promise.resolve(mismatch);
|
|
144
182
|
const wallet = options.wallet(pairing);
|
|
145
183
|
const expiresAt = Date.now() + ttl;
|
|
146
184
|
// a cancel stops the wallet only before it sends: once delivered, the human can still approve, so keep waiting
|
|
@@ -183,13 +221,15 @@ export function createMcpServer(options) {
|
|
|
183
221
|
}
|
|
184
222
|
async function requestEvmTx(args, cancelled) {
|
|
185
223
|
const context = contextFrom(args);
|
|
224
|
+
const from = fromArg(args);
|
|
186
225
|
const { chain_id, transaction } = validateTx(args.chain_id, { to: args.to, value: args.value, data: args.data ?? '0x' });
|
|
187
|
-
return submit('evm', cancelled, (wallet, limits) => wallet.requestTx({ chain_id, transaction, context, ...limits }));
|
|
226
|
+
return submit('evm', from, cancelled, (wallet, limits) => wallet.requestTx({ chain_id, transaction, context, ...limits }));
|
|
188
227
|
}
|
|
189
228
|
async function requestTransfer(args, cancelled) {
|
|
190
229
|
if (args.family !== 'bitcoin')
|
|
191
230
|
throw new ToolInputError(`invalid family: must be "bitcoin", got ${JSON.stringify(args.family)}`);
|
|
192
231
|
const context = contextFrom(args);
|
|
232
|
+
const from = fromArg(args);
|
|
193
233
|
let request;
|
|
194
234
|
try {
|
|
195
235
|
request = validateTransfer('bitcoin', args.network, { asset: 'btc', to: args.to, amount: args.amount });
|
|
@@ -201,7 +241,7 @@ export function createMcpServer(options) {
|
|
|
201
241
|
throw err;
|
|
202
242
|
}
|
|
203
243
|
const { chain_network, transfer } = request;
|
|
204
|
-
return submit('bitcoin', cancelled, async (wallet, limits) => {
|
|
244
|
+
return submit('bitcoin', from, cancelled, async (wallet, limits) => {
|
|
205
245
|
try {
|
|
206
246
|
return await wallet.requestTransfer({ chain_network, transfer, context, ...limits });
|
|
207
247
|
}
|