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 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, unreachable relay, join timeout, cancelled)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clanker-wallet",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",