clanker-wallet 0.6.1 → 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;
@@ -87,6 +91,10 @@ export declare const MCP_TOOLS: ({
87
91
  inputSchema: {
88
92
  type: string;
89
93
  properties: {
94
+ from: {
95
+ type: string;
96
+ description: string;
97
+ };
90
98
  reason: {
91
99
  type: string;
92
100
  description: string;
package/dist/mcp.js CHANGED
@@ -20,6 +20,10 @@ 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
+ };
23
27
  /**
24
28
  * MCP tool annotations: assistants (ChatGPT, dots, Claude) may skip their confirmation prompt for read-only tools.
25
29
  * a request moves nothing until the human approves it on their device, so it is not destructive, but each call
@@ -42,6 +46,7 @@ export const MCP_TOOLS = [
42
46
  value: { type: 'string', description: 'integer string in wei; "0" for a contract call that sends no ETH' },
43
47
  data: { type: 'string', description: 'hex calldata, default "0x" (a plain transfer)' },
44
48
  ...CONTEXT_PROPERTIES,
49
+ ...FROM_PROPERTY,
45
50
  },
46
51
  required: ['chain_id', 'to', 'value', 'reason'],
47
52
  },
@@ -61,6 +66,7 @@ export const MCP_TOOLS = [
61
66
  to: { type: 'string', description: 'segwit address on that network: bc1... on mainnet, tb1... on testnet and signet' },
62
67
  amount: { type: 'string', description: 'integer string in sats' },
63
68
  ...CONTEXT_PROPERTIES,
69
+ ...FROM_PROPERTY,
64
70
  },
65
71
  required: ['family', 'network', 'to', 'amount', 'reason'],
66
72
  },
@@ -134,6 +140,23 @@ function contextFrom(args) {
134
140
  }
135
141
  return context;
136
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
+ }
137
160
  const NOT_PAIRED = {
138
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>\'',
139
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>\'',
@@ -146,12 +169,16 @@ export function createMcpServer(options) {
146
169
  const inFlight = new Map();
147
170
  /**
148
171
  * start a request on the family's pairing and answer once the relay has it: { request_id } after
149
- * 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)
150
174
  */
151
- function submit(family, cancelled, send) {
175
+ function submit(family, from, cancelled, send) {
152
176
  const pairing = options.loadPairing(family);
153
177
  if (!pairing)
154
178
  return Promise.resolve(toolError(NOT_PAIRED[family]));
179
+ const mismatch = fromMismatch(family, from, pairing);
180
+ if (mismatch)
181
+ return Promise.resolve(mismatch);
155
182
  const wallet = options.wallet(pairing);
156
183
  const expiresAt = Date.now() + ttl;
157
184
  // a cancel stops the wallet only before it sends: once delivered, the human can still approve, so keep waiting
@@ -194,13 +221,15 @@ export function createMcpServer(options) {
194
221
  }
195
222
  async function requestEvmTx(args, cancelled) {
196
223
  const context = contextFrom(args);
224
+ const from = fromArg(args);
197
225
  const { chain_id, transaction } = validateTx(args.chain_id, { to: args.to, value: args.value, data: args.data ?? '0x' });
198
- 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 }));
199
227
  }
200
228
  async function requestTransfer(args, cancelled) {
201
229
  if (args.family !== 'bitcoin')
202
230
  throw new ToolInputError(`invalid family: must be "bitcoin", got ${JSON.stringify(args.family)}`);
203
231
  const context = contextFrom(args);
232
+ const from = fromArg(args);
204
233
  let request;
205
234
  try {
206
235
  request = validateTransfer('bitcoin', args.network, { asset: 'btc', to: args.to, amount: args.amount });
@@ -212,7 +241,7 @@ export function createMcpServer(options) {
212
241
  throw err;
213
242
  }
214
243
  const { chain_network, transfer } = request;
215
- return submit('bitcoin', cancelled, async (wallet, limits) => {
244
+ return submit('bitcoin', from, cancelled, async (wallet, limits) => {
216
245
  try {
217
246
  return await wallet.requestTransfer({ chain_network, transfer, context, ...limits });
218
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clanker-wallet",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",