clanker-wallet 0.7.0 → 0.9.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
@@ -88,6 +88,8 @@ clanker-wallet send --xch testnet11 --to txch1qqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs
88
88
  # approved → {"tx_hash":"<bitcoin txid or chia transaction id>","status":"approved"}
89
89
  ```
90
90
 
91
+ A chia approval may have no `tx_hash` (`{"status":"approved"}`): the Sage wallet sends without reporting a transaction id, so check the recipient's balance.
92
+
91
93
  | Flag | Required | Default | Description |
92
94
  |------|----------|---------|-------------|
93
95
  | `--btc <network>` | one of `--btc`/`--xch` | — | `mainnet`, `testnet` or `signet` |
@@ -177,11 +179,11 @@ Claude Desktop (`claude_desktop_config.json`), Cursor (`~/.cursor/mcp.json`), an
177
179
  | Tool | Arguments | Returns |
178
180
  |------|-----------|---------|
179
181
  | `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
- | `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
- | `list_pairings` | none | `agent_id`, `fingerprint`, and the paired families (`evm`, `bitcoin`) with network, wallet and mode |
182
+ | `request_transfer` | `family` (`bitcoin` or `chia`), `network` (bitcoin: `mainnet`, `testnet`, `signet`; chia: `mainnet`, `testnet11`), `to`, `amount` (sats, integer string) or mojos for chia, `reason`, optional `fee` (chia only: mojos, default `"0"`) / `urgency` / `expected_outcome` / `from` (the paired wallet to send from, needed when several wallets of the same family are paired) | as above |
183
+ | `get_request` | `request_id`, optional `wait_seconds` (at most 50) | `status`: `pending`, `approved` (with `tx_hash`; a chia send may have none), `rejected`, `failed` (with `error`), `unknown` (with `error`) or `expired` |
184
+ | `list_pairings` | none | `agent_id`, `fingerprint`, and the paired families (`evm`, `bitcoin`, `chia`) with network, wallet and mode |
183
185
 
184
- Requests are asynchronous: a request tool returns once the relay has the request, then the assistant calls `get_request` (with `wait_seconds` to wait for an answer) until the status is no longer `pending`. Invalid input or a missing pairing is a tool error, and nothing is sent. Pending requests live in the server process: after 1 h the server stops waiting and reports `expired`, and a restarted server no longer knows them (`get_request` then reports an unknown `request_id`). In both cases the human may still approve the request in their wallet, so ask them before sending it again. `unknown` means the request was delivered but the server lost its relay connection: the human may still approve it, so ask them before sending it again. Chia is not offered over MCP yet.
186
+ Requests are asynchronous: a request tool returns once the relay has the request, then the assistant calls `get_request` (with `wait_seconds` to wait for an answer) until the status is no longer `pending`. Invalid input or a missing pairing is a tool error, and nothing is sent. Pending requests live in the server process: after 1 h the server stops waiting and reports `expired`, and a restarted server no longer knows them (`get_request` then reports an unknown `request_id`). In both cases the human may still approve the request in their wallet, so ask them before sending it again. `unknown` means the request was delivered but the server lost its relay connection: the human may still approve it, so ask them before sending it again. A chia approval may have no `tx_hash`: the Sage wallet sends without reporting a transaction id, so check the recipient's balance.
185
187
 
186
188
  ---
187
189
 
@@ -220,7 +222,7 @@ const txId = await wallet.requestTransfer({
220
222
  })
221
223
  ```
222
224
 
223
- It throws `InvalidTxError` before sending for an evm pairing, an unknown network, a recipient from another network, a non-integer amount, or a bitcoin `fee` (the wallet sets bitcoin fees). Chia `fee` defaults to `"0"`.
225
+ It throws `InvalidTxError` before sending for an evm pairing, an unknown network, a recipient from another network, a non-integer amount, or a bitcoin `fee` (the wallet sets bitcoin fees). Chia `fee` defaults to `"0"`. It returns `undefined` for a chia send the wallet reported no id for (Sage); check the recipient's balance.
224
226
 
225
227
  See full API: [`src/`](./src/)
226
228
 
package/SKILL.md CHANGED
@@ -132,7 +132,7 @@ clanker-wallet send --xch testnet11 --to txch1qqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs
132
132
  - **bitcoin recipients are segwit addresses only** (`bc1q...`, `bc1p...`, `tb1...`); legacy `1...`/`3...` addresses are refused.
133
133
  - **amounts are whole base units**: `--sats` (1 BTC = `100000000`), `--mojos` (1 XCH = `1000000000000`). No decimals.
134
134
  - **fees:** bitcoin fees are set in the human's wallet. Chia takes `--fee-mojos` (default `0`), which the human sees before approving.
135
- - `--reason`, `--urgency`, `--expected-outcome` and `--timeout` work as for `tx`, and so do the output and exit codes. On approval `tx_hash` is the bitcoin txid or the chia transaction id. Exit code 4 means that chain family isn't paired.
135
+ - `--reason`, `--urgency`, `--expected-outcome` and `--timeout` work as for `tx`, and so do the output and exit codes. On approval `tx_hash` is the bitcoin txid or the chia transaction id. A chia approval may have no `tx_hash` (`{"status":"approved"}`): the Sage wallet sends without reporting a transaction id, so check the recipient's balance. Exit code 4 means that chain family isn't paired.
136
136
 
137
137
  ## Common Chain IDs
138
138
 
@@ -216,11 +216,11 @@ Claude Desktop (`claude_desktop_config.json`), Cursor (`~/.cursor/mcp.json`), an
216
216
  | Tool | Arguments | Returns |
217
217
  |------|-----------|---------|
218
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
- | `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
- | `list_pairings` | none | `agent_id`, `fingerprint`, and the paired families (`evm`, `bitcoin`) with network, wallet and mode |
219
+ | `request_transfer` | `family` (`bitcoin` or `chia`), `network` (bitcoin: `mainnet`, `testnet`, `signet`; chia: `mainnet`, `testnet11`), `to`, `amount` (sats, integer string) or mojos for chia, `reason`, optional `fee` (chia only: mojos, default `"0"`) / `urgency` / `expected_outcome` / `from` (the paired wallet to send from, needed when several wallets of the same family are paired) | as above |
220
+ | `get_request` | `request_id`, optional `wait_seconds` (at most 50) | `status`: `pending`, `approved` (with `tx_hash`; a chia send may have none), `rejected`, `failed` (with `error`), `unknown` (with `error`) or `expired` |
221
+ | `list_pairings` | none | `agent_id`, `fingerprint`, and the paired families (`evm`, `bitcoin`, `chia`) with network, wallet and mode |
222
222
 
223
- Flow: call `list_pairings` to see which chains you can use, call `request_evm_tx` or `request_transfer` (same rules as `tx` / `send`: wei and sats as integer strings, real addresses), tell the human a request is waiting (see "Transaction notifications"), then call `get_request` with `wait_seconds` until the status is no longer `pending`. Invalid input or a missing pairing comes back as a tool error and nothing is sent. After 1 h the server stops waiting and reports `expired`, and a restarted server no longer knows its earlier requests (`get_request` then reports an unknown `request_id`). In both cases the human may still approve the request in their wallet, so ask them before sending it again. `unknown` means the request was delivered but the server lost its relay connection: the human may still approve it, so ask them before sending it again. Chia is not offered over MCP yet.
223
+ Flow: call `list_pairings` to see which chains you can use, call `request_evm_tx` or `request_transfer` (same rules as `tx` / `send`: wei, sats and mojos as integer strings, real addresses), tell the human a request is waiting (see "Transaction notifications"), then call `get_request` with `wait_seconds` until the status is no longer `pending`. Invalid input or a missing pairing comes back as a tool error and nothing is sent. After 1 h the server stops waiting and reports `expired`, and a restarted server no longer knows its earlier requests (`get_request` then reports an unknown `request_id`). In both cases the human may still approve the request in their wallet, so ask them before sending it again. `unknown` means the request was delivered but the server lost its relay connection: the human may still approve it, so ask them before sending it again. A chia approval may have no `tx_hash`: the Sage wallet sends without reporting a transaction id, so check the recipient's balance.
224
224
 
225
225
  ## Troubleshooting
226
226
 
package/dist/client.d.ts CHANGED
@@ -34,10 +34,11 @@ export declare class ClankerWallet extends TypedEmitter {
34
34
  /**
35
35
  * send a bitcoin / chia transfer to the paired human and wait for their response.
36
36
  * the family comes from the pairing; the human's wallet builds, signs and broadcasts
37
- * the send. resolves with the txid on approval.
37
+ * the send. resolves with the txid on approval, or with undefined for a chia send the wallet
38
+ * reported no id for (Sage's chia_send): it was sent, and the recipient's balance shows it.
38
39
  * throws InvalidTxError (before anything is sent), or on rejection, timeout, or connection error.
39
40
  */
40
- requestTransfer(options: RequestTransferOptions): Promise<string>;
41
+ requestTransfer(options: RequestTransferOptions): Promise<string | undefined>;
41
42
  /** encrypt a signed request to the paired human, send it through the relay, and wait for the answer */
42
43
  private send;
43
44
  }
package/dist/client.js CHANGED
@@ -99,12 +99,17 @@ export class ClankerWallet extends TypedEmitter {
99
99
  const simulate = mode === 'managed'
100
100
  ? { from: humanWallet || '', to: transaction.to, value: transaction.value, data: transaction.data, chainId: chain_id }
101
101
  : undefined;
102
- return this.send(txRequest, options, simulate);
102
+ const txHash = await this.send(txRequest, options, simulate);
103
+ // unreachable: send() lets only a chia approval through without a hash
104
+ if (txHash === undefined)
105
+ throw new Error(`request ${txRequest.request_id}: approved without a tx hash`);
106
+ return txHash;
103
107
  }
104
108
  /**
105
109
  * send a bitcoin / chia transfer to the paired human and wait for their response.
106
110
  * the family comes from the pairing; the human's wallet builds, signs and broadcasts
107
- * the send. resolves with the txid on approval.
111
+ * the send. resolves with the txid on approval, or with undefined for a chia send the wallet
112
+ * reported no id for (Sage's chia_send): it was sent, and the recipient's balance shows it.
108
113
  * throws InvalidTxError (before anything is sent), or on rejection, timeout, or connection error.
109
114
  */
110
115
  async requestTransfer(options) {
@@ -137,6 +142,8 @@ export class ClankerWallet extends TypedEmitter {
137
142
  const requestId = request.request_id;
138
143
  const timeoutMs = options.timeout_ms ?? DEFAULT_TIMEOUT_MS;
139
144
  const { join_timeout_ms: joinTimeoutMs, signal } = options;
145
+ // a chia wallet can send without reporting an id (Sage's chia_send answers {}); every other approval carries one
146
+ const hashOptional = 'chain_family' in request && request.chain_family === 'chia';
140
147
  return new Promise((resolve, reject) => {
141
148
  let ws;
142
149
  let timer;
@@ -249,8 +256,8 @@ export class ClankerWallet extends TypedEmitter {
249
256
  tx_hash: response.tx_hash,
250
257
  });
251
258
  cleanup();
252
- if (response.status === 'approved' && response.tx_hash) {
253
- resolve(response.tx_hash);
259
+ if (response.status === 'approved' && (response.tx_hash || hashOptional)) {
260
+ resolve(response.tx_hash || undefined);
254
261
  }
255
262
  else if (response.status === 'rejected') {
256
263
  reject(new Error(`request ${requestId} rejected by human`));
package/dist/mcp.d.ts CHANGED
@@ -17,8 +17,8 @@ export interface SavedPairing {
17
17
  chain_family?: string;
18
18
  chain_network?: string;
19
19
  }
20
- /** the families the tools request on; chia waits until the web app shows it again */
21
- type RequestFamily = 'evm' | 'bitcoin';
20
+ /** the families the tools request on */
21
+ type RequestFamily = 'evm' | 'bitcoin' | 'chia';
22
22
  export interface McpServerOptions {
23
23
  /** the package version, reported in initialize */
24
24
  version: string;
@@ -126,6 +126,10 @@ export declare const MCP_TOOLS: ({
126
126
  type: string;
127
127
  description: string;
128
128
  };
129
+ fee: {
130
+ type: string;
131
+ description: string;
132
+ };
129
133
  request_id?: undefined;
130
134
  wait_seconds?: undefined;
131
135
  };
package/dist/mcp.js CHANGED
@@ -13,7 +13,7 @@ export const MAX_WAIT_SECONDS = 50;
13
13
  const DEFAULT_REQUEST_TTL_MS = 3_600_000;
14
14
  /** a relay that hasn't confirmed the join by then fails the request with nothing sent, inside the hosts' 60 s tool-call timeout */
15
15
  const DEFAULT_JOIN_TIMEOUT_MS = 20_000;
16
- const REQUEST_FAMILIES = ['evm', 'bitcoin'];
16
+ const REQUEST_FAMILIES = ['evm', 'bitcoin', 'chia'];
17
17
  const URGENCIES = ['low', 'medium', 'high'];
18
18
  const CONTEXT_PROPERTIES = {
19
19
  reason: { type: 'string', description: 'why you want this, in plain words; shown to the human on the review card' },
@@ -22,7 +22,7 @@ const CONTEXT_PROPERTIES = {
22
22
  };
23
23
  /** the same on both request tools; the hosted connector copies these definitions verbatim */
24
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' },
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, bitcoin or chia) are paired' },
26
26
  };
27
27
  /**
28
28
  * MCP tool annotations: assistants (ChatGPT, dots, Claude) may skip their confirmation prompt for read-only tools.
@@ -54,17 +54,23 @@ export const MCP_TOOLS = [
54
54
  },
55
55
  {
56
56
  name: 'request_transfer',
57
- description: 'Ask the human to send bitcoin from their own wallet, which builds, signs and broadcasts it. ' +
57
+ description: 'Ask the human to send bitcoin or chia (XCH) from their own wallet, which builds, signs and broadcasts it. ' +
58
58
  'Nothing is signed here: the human approves every request on their own device, or rejects it. ' +
59
59
  'Returns { request_id } once the request is delivered; then call get_request for the outcome. ' +
60
- 'amount is an integer string in sats (1 BTC = "100000000"), no decimals; the wallet sets the fee.',
60
+ 'amount is an integer string, no decimals: sats for bitcoin (1 BTC = "100000000"), where the wallet sets the fee; ' +
61
+ 'mojos for chia (1 XCH = "1000000000000"), with an optional fee in mojos (default "0").',
61
62
  inputSchema: {
62
63
  type: 'object',
63
64
  properties: {
64
- family: { type: 'string', enum: ['bitcoin'], description: 'chain family' },
65
- network: { type: 'string', enum: ['mainnet', 'testnet', 'signet'], description: 'must match the paired wallet (see list_pairings)' },
66
- to: { type: 'string', description: 'segwit address on that network: bc1... on mainnet, tb1... on testnet and signet' },
67
- amount: { type: 'string', description: 'integer string in sats' },
65
+ family: { type: 'string', enum: ['bitcoin', 'chia'], description: 'chain family' },
66
+ network: {
67
+ type: 'string',
68
+ enum: ['mainnet', 'testnet', 'signet', 'testnet11'],
69
+ description: 'bitcoin: mainnet, testnet or signet; chia: mainnet or testnet11. must match the paired wallet (see list_pairings)',
70
+ },
71
+ to: { type: 'string', description: 'address on that network: bitcoin segwit bc1... (mainnet) or tb1... (testnet, signet); chia xch1... (mainnet) or txch1... (testnet11)' },
72
+ amount: { type: 'string', description: 'integer string in sats (bitcoin) or mojos (chia)' },
73
+ fee: { type: 'string', description: 'chia only: integer string in mojos, default "0"; bitcoin takes none (the wallet sets it)' },
68
74
  ...CONTEXT_PROPERTIES,
69
75
  ...FROM_PROPERTY,
70
76
  },
@@ -74,7 +80,7 @@ export const MCP_TOOLS = [
74
80
  },
75
81
  {
76
82
  name: 'get_request',
77
- description: 'Get the outcome of a request: pending, approved (with tx_hash), rejected, failed (with error), unknown (with error) or expired. ' +
83
+ description: 'Get the outcome of a request: pending, approved (with tx_hash; a chia send may have none, so check the recipient\'s balance), rejected, failed (with error), unknown (with error) or expired. ' +
78
84
  `The human may take minutes to answer: pass wait_seconds (at most ${MAX_WAIT_SECONDS}) to wait for it, and call again while it is pending. ` +
79
85
  'unknown means the request was delivered but this server lost its relay connection: the human may still approve it, so ask them before sending it again. ' +
80
86
  'expired means this server stopped waiting, 1 hour after sending; after a restart it no longer knows earlier request ids. ' +
@@ -160,6 +166,7 @@ function fromMismatch(family, from, pairing) {
160
166
  const NOT_PAIRED = {
161
167
  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>\'',
162
168
  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>\'',
169
+ chia: 'not paired with a chia wallet: ask the human to open the XCH tab at https://clanker-wallet.xyz, copy its pairing JSON, and run: clanker-wallet pair \'<JSON>\'',
163
170
  };
164
171
  export function createMcpServer(options) {
165
172
  const ttl = options.requestTtlMs ?? DEFAULT_REQUEST_TTL_MS;
@@ -226,13 +233,15 @@ export function createMcpServer(options) {
226
233
  return submit('evm', from, cancelled, (wallet, limits) => wallet.requestTx({ chain_id, transaction, context, ...limits }));
227
234
  }
228
235
  async function requestTransfer(args, cancelled) {
229
- if (args.family !== 'bitcoin')
230
- throw new ToolInputError(`invalid family: must be "bitcoin", got ${JSON.stringify(args.family)}`);
236
+ const family = args.family;
237
+ if (family !== 'bitcoin' && family !== 'chia')
238
+ throw new ToolInputError(`invalid family: must be "bitcoin" or "chia", got ${JSON.stringify(family)}`);
231
239
  const context = contextFrom(args);
232
240
  const from = fromArg(args);
233
241
  let request;
234
242
  try {
235
- request = validateTransfer('bitcoin', args.network, { asset: 'btc', to: args.to, amount: args.amount });
243
+ const asset = family === 'bitcoin' ? 'btc' : 'xch';
244
+ request = validateTransfer(family, args.network, { asset, to: args.to, amount: args.amount, ...(args.fee !== undefined && { fee: args.fee }) });
236
245
  }
237
246
  catch (err) {
238
247
  // the tool calls the network field `network`
@@ -241,7 +250,7 @@ export function createMcpServer(options) {
241
250
  throw err;
242
251
  }
243
252
  const { chain_network, transfer } = request;
244
- return submit('bitcoin', from, cancelled, async (wallet, limits) => {
253
+ return submit(family, from, cancelled, async (wallet, limits) => {
245
254
  try {
246
255
  return await wallet.requestTransfer({ chain_network, transfer, context, ...limits });
247
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clanker-wallet",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",