clanker-wallet 0.4.2 → 0.6.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 +84 -9
- package/SKILL.md +54 -4
- package/bin/cli.js +208 -47
- package/dist/bech32.d.ts +27 -0
- package/dist/bech32.js +111 -0
- package/dist/client.d.ts +14 -1
- package/dist/client.js +90 -20
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +156 -0
- package/dist/mcp.js +370 -0
- package/dist/types.d.ts +52 -0
- package/dist/validate.d.ts +13 -2
- package/dist/validate.js +63 -0
- package/package.json +2 -1
package/dist/bech32.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bech32 / Bech32m decoding — a port of the BIP-173 / BIP-350 reference
|
|
3
|
+
* implementation (github.com/sipa/bech32 ref/python/segwit_addr.py).
|
|
4
|
+
* Used to check transfer recipients: bitcoin segwit addresses and chia
|
|
5
|
+
* puzzle-hash addresses (bech32m, hrp xch / txch).
|
|
6
|
+
*/
|
|
7
|
+
export type Bech32Encoding = 'bech32' | 'bech32m';
|
|
8
|
+
/** validate a bech32/bech32m string; returns its hrp, data (checksum stripped) and encoding */
|
|
9
|
+
export declare function bech32Decode(bech: string): {
|
|
10
|
+
hrp: string;
|
|
11
|
+
data: number[];
|
|
12
|
+
encoding: Bech32Encoding;
|
|
13
|
+
} | null;
|
|
14
|
+
/** general power-of-2 base conversion */
|
|
15
|
+
export declare function convertBits(data: number[], fromBits: number, toBits: number, pad: boolean): number[] | null;
|
|
16
|
+
/** decode a segwit address for the given hrp ('bc' mainnet, 'tb' testnet/signet) */
|
|
17
|
+
export declare function decodeSegwitAddress(hrp: string, addr: string): {
|
|
18
|
+
version: number;
|
|
19
|
+
program: number[];
|
|
20
|
+
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* a segwit address a wallet can pay today: v0 p2wpkh/p2wsh (20/32 bytes) or v1 p2tr (32 bytes).
|
|
23
|
+
* other versions and v1 lengths decode but are unspendable or anyone-can-spend, so transfers refuse them
|
|
24
|
+
*/
|
|
25
|
+
export declare function isStandardSegwitAddress(hrp: string, addr: string): boolean;
|
|
26
|
+
/** decode a chia address (bech32m of a 32-byte puzzle hash) for the given hrp ('xch' / 'txch') */
|
|
27
|
+
export declare function decodeChiaAddress(hrp: string, addr: string): number[] | null;
|
package/dist/bech32.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bech32 / Bech32m decoding — a port of the BIP-173 / BIP-350 reference
|
|
3
|
+
* implementation (github.com/sipa/bech32 ref/python/segwit_addr.py).
|
|
4
|
+
* Used to check transfer recipients: bitcoin segwit addresses and chia
|
|
5
|
+
* puzzle-hash addresses (bech32m, hrp xch / txch).
|
|
6
|
+
*/
|
|
7
|
+
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
|
8
|
+
const BECH32M_CONST = 0x2bc830a3;
|
|
9
|
+
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
|
|
10
|
+
function polymod(values) {
|
|
11
|
+
let chk = 1;
|
|
12
|
+
for (const value of values) {
|
|
13
|
+
const top = chk >>> 25;
|
|
14
|
+
chk = (((chk & 0x1ffffff) << 5) ^ value) >>> 0;
|
|
15
|
+
for (let i = 0; i < 5; i++) {
|
|
16
|
+
if ((top >>> i) & 1)
|
|
17
|
+
chk = (chk ^ GENERATOR[i]) >>> 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return chk;
|
|
21
|
+
}
|
|
22
|
+
function hrpExpand(hrp) {
|
|
23
|
+
const codes = Array.from(hrp, (c) => c.charCodeAt(0));
|
|
24
|
+
return [...codes.map((c) => c >> 5), 0, ...codes.map((c) => c & 31)];
|
|
25
|
+
}
|
|
26
|
+
/** validate a bech32/bech32m string; returns its hrp, data (checksum stripped) and encoding */
|
|
27
|
+
export function bech32Decode(bech) {
|
|
28
|
+
for (const c of bech) {
|
|
29
|
+
const code = c.charCodeAt(0);
|
|
30
|
+
if (code < 33 || code > 126)
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (bech.toLowerCase() !== bech && bech.toUpperCase() !== bech)
|
|
34
|
+
return null;
|
|
35
|
+
const lower = bech.toLowerCase();
|
|
36
|
+
const pos = lower.lastIndexOf('1');
|
|
37
|
+
if (pos < 1 || pos + 7 > lower.length || lower.length > 90)
|
|
38
|
+
return null;
|
|
39
|
+
const data = [];
|
|
40
|
+
for (const c of lower.slice(pos + 1)) {
|
|
41
|
+
const d = CHARSET.indexOf(c);
|
|
42
|
+
if (d === -1)
|
|
43
|
+
return null;
|
|
44
|
+
data.push(d);
|
|
45
|
+
}
|
|
46
|
+
const hrp = lower.slice(0, pos);
|
|
47
|
+
const check = polymod([...hrpExpand(hrp), ...data]);
|
|
48
|
+
const encoding = check === 1 ? 'bech32' : check === BECH32M_CONST ? 'bech32m' : null;
|
|
49
|
+
if (!encoding)
|
|
50
|
+
return null;
|
|
51
|
+
return { hrp, data: data.slice(0, -6), encoding };
|
|
52
|
+
}
|
|
53
|
+
/** general power-of-2 base conversion */
|
|
54
|
+
export function convertBits(data, fromBits, toBits, pad) {
|
|
55
|
+
let acc = 0;
|
|
56
|
+
let bits = 0;
|
|
57
|
+
const ret = [];
|
|
58
|
+
const maxv = (1 << toBits) - 1;
|
|
59
|
+
const maxAcc = (1 << (fromBits + toBits - 1)) - 1;
|
|
60
|
+
for (const value of data) {
|
|
61
|
+
if (value < 0 || value >> fromBits)
|
|
62
|
+
return null;
|
|
63
|
+
acc = ((acc << fromBits) | value) & maxAcc;
|
|
64
|
+
bits += fromBits;
|
|
65
|
+
while (bits >= toBits) {
|
|
66
|
+
bits -= toBits;
|
|
67
|
+
ret.push((acc >> bits) & maxv);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (pad) {
|
|
71
|
+
if (bits)
|
|
72
|
+
ret.push((acc << (toBits - bits)) & maxv);
|
|
73
|
+
}
|
|
74
|
+
else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return ret;
|
|
78
|
+
}
|
|
79
|
+
/** decode a segwit address for the given hrp ('bc' mainnet, 'tb' testnet/signet) */
|
|
80
|
+
export function decodeSegwitAddress(hrp, addr) {
|
|
81
|
+
const decoded = bech32Decode(addr);
|
|
82
|
+
if (!decoded || decoded.hrp !== hrp || decoded.data.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
const [version, ...rest] = decoded.data;
|
|
85
|
+
const program = convertBits(rest, 5, 8, false);
|
|
86
|
+
if (!program || program.length < 2 || program.length > 40)
|
|
87
|
+
return null;
|
|
88
|
+
if (version > 16)
|
|
89
|
+
return null;
|
|
90
|
+
if (version === 0 && program.length !== 20 && program.length !== 32)
|
|
91
|
+
return null;
|
|
92
|
+
if ((version === 0) !== (decoded.encoding === 'bech32'))
|
|
93
|
+
return null;
|
|
94
|
+
return { version, program };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* a segwit address a wallet can pay today: v0 p2wpkh/p2wsh (20/32 bytes) or v1 p2tr (32 bytes).
|
|
98
|
+
* other versions and v1 lengths decode but are unspendable or anyone-can-spend, so transfers refuse them
|
|
99
|
+
*/
|
|
100
|
+
export function isStandardSegwitAddress(hrp, addr) {
|
|
101
|
+
const decoded = decodeSegwitAddress(hrp, addr);
|
|
102
|
+
return !!decoded && (decoded.version === 0 || (decoded.version === 1 && decoded.program.length === 32));
|
|
103
|
+
}
|
|
104
|
+
/** decode a chia address (bech32m of a 32-byte puzzle hash) for the given hrp ('xch' / 'txch') */
|
|
105
|
+
export function decodeChiaAddress(hrp, addr) {
|
|
106
|
+
const decoded = bech32Decode(addr);
|
|
107
|
+
if (!decoded || decoded.hrp !== hrp || decoded.encoding !== 'bech32m')
|
|
108
|
+
return null;
|
|
109
|
+
const puzzleHash = convertBits(decoded.data, 5, 8, false);
|
|
110
|
+
return puzzleHash && puzzleHash.length === 32 ? puzzleHash : null;
|
|
111
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { TypedEmitter } from './events.js';
|
|
2
|
-
import type { PairingData, RequestTxOptions } from './types.js';
|
|
2
|
+
import type { PairingData, RequestTransferOptions, RequestTxOptions } from './types.js';
|
|
3
|
+
/** the relay answered with an error frame (rate limited, message too large, ...): it refused the request instead of passing it on */
|
|
4
|
+
export declare class RelayError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
3
7
|
export interface ClankerWalletOptions {
|
|
4
8
|
/** agent display name (informational) */
|
|
5
9
|
agentName?: string;
|
|
@@ -27,4 +31,13 @@ export declare class ClankerWallet extends TypedEmitter {
|
|
|
27
31
|
* throws on rejection, timeout, or connection error.
|
|
28
32
|
*/
|
|
29
33
|
requestTx(options: RequestTxOptions): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* send a bitcoin / chia transfer to the paired human and wait for their response.
|
|
36
|
+
* the family comes from the pairing; the human's wallet builds, signs and broadcasts
|
|
37
|
+
* the send. resolves with the txid on approval.
|
|
38
|
+
* throws InvalidTxError (before anything is sent), or on rejection, timeout, or connection error.
|
|
39
|
+
*/
|
|
40
|
+
requestTransfer(options: RequestTransferOptions): Promise<string>;
|
|
41
|
+
/** encrypt a signed request to the paired human, send it through the relay, and wait for the answer */
|
|
42
|
+
private send;
|
|
30
43
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decryptMessage, encryptMessage, fromHex, generateKeypair, keypairFromSecret, signMessage, signingKeypairFromSeed, toHex } from './crypto.js';
|
|
2
2
|
import { TypedEmitter } from './events.js';
|
|
3
|
-
import { validateTx } from './validate.js';
|
|
3
|
+
import { InvalidTxError, validateTransfer, validateTx } from './validate.js';
|
|
4
4
|
const DEFAULT_TIMEOUT_MS = 3_600_000; // 1 hour
|
|
5
5
|
const UNREACHABLE_HINT = 'if you run in a sandbox or behind a firewall, allow this host; otherwise the relay may be down or rate limiting, retry in a few seconds';
|
|
6
6
|
/** node's WebSocket ignores HTTPS_PROXY unless NODE_USE_ENV_PROXY is set at startup, and sandboxes (claude code) only let traffic out through that proxy */
|
|
@@ -12,6 +12,13 @@ function proxyHint() {
|
|
|
12
12
|
? '; HTTPS_PROXY is set but node ignores it, rerun with NODE_USE_ENV_PROXY=1 (node 24+)'
|
|
13
13
|
: '';
|
|
14
14
|
}
|
|
15
|
+
/** the relay answered with an error frame (rate limited, message too large, ...): it refused the request instead of passing it on */
|
|
16
|
+
export class RelayError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'RelayError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
15
22
|
export class ClankerWallet extends TypedEmitter {
|
|
16
23
|
publicKey;
|
|
17
24
|
secretKey;
|
|
@@ -55,6 +62,8 @@ export class ClankerWallet extends TypedEmitter {
|
|
|
55
62
|
relayUrl: data.relay,
|
|
56
63
|
humanWallet: data.wallet || null,
|
|
57
64
|
mode: data.mode || 'encrypted',
|
|
65
|
+
chainFamily: data.chain_family || null,
|
|
66
|
+
chainNetwork: data.chain_network || null,
|
|
58
67
|
};
|
|
59
68
|
this.emit('paired', { relay: data.relay, wallet: data.wallet || '' });
|
|
60
69
|
return data;
|
|
@@ -68,13 +77,16 @@ export class ClankerWallet extends TypedEmitter {
|
|
|
68
77
|
if (!this.pairing) {
|
|
69
78
|
throw new Error('not paired — call pair() first');
|
|
70
79
|
}
|
|
80
|
+
// a bitcoin or chia pairing takes transfers only: its wallet can't sign an evm tx
|
|
81
|
+
const { chainFamily } = this.pairing;
|
|
82
|
+
if (chainFamily && chainFamily !== 'evm') {
|
|
83
|
+
throw new InvalidTxError('chain_family', `this pairing is for ${chainFamily}: use requestTransfer for bitcoin or chia`);
|
|
84
|
+
}
|
|
71
85
|
// reject malformed requests before anything reaches the relay
|
|
72
86
|
const { chain_id, transaction } = validateTx(options.chain_id, options.transaction);
|
|
73
|
-
const {
|
|
74
|
-
const timeoutMs = options.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
75
|
-
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
87
|
+
const { humanWallet, mode } = this.pairing;
|
|
76
88
|
const txRequest = {
|
|
77
|
-
request_id:
|
|
89
|
+
request_id: newRequestId(),
|
|
78
90
|
agent_id: this.agentId,
|
|
79
91
|
timestamp: new Date().toISOString(),
|
|
80
92
|
chain_id,
|
|
@@ -83,28 +95,90 @@ export class ClankerWallet extends TypedEmitter {
|
|
|
83
95
|
signature: '', // set below after signing
|
|
84
96
|
};
|
|
85
97
|
txRequest.signature = signMessage(JSON.stringify(txRequest), this.signingSecretKey);
|
|
98
|
+
// managed mode: relay gets plaintext tx params so it can simulate
|
|
99
|
+
const simulate = mode === 'managed'
|
|
100
|
+
? { from: humanWallet || '', to: transaction.to, value: transaction.value, data: transaction.data, chainId: chain_id }
|
|
101
|
+
: undefined;
|
|
102
|
+
return this.send(txRequest, options, simulate);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* send a bitcoin / chia transfer to the paired human and wait for their response.
|
|
106
|
+
* the family comes from the pairing; the human's wallet builds, signs and broadcasts
|
|
107
|
+
* the send. resolves with the txid on approval.
|
|
108
|
+
* throws InvalidTxError (before anything is sent), or on rejection, timeout, or connection error.
|
|
109
|
+
*/
|
|
110
|
+
async requestTransfer(options) {
|
|
111
|
+
if (!this.pairing) {
|
|
112
|
+
throw new Error('not paired — call pair() first');
|
|
113
|
+
}
|
|
114
|
+
const { chainFamily, chainNetwork } = this.pairing;
|
|
115
|
+
// reject malformed requests before anything reaches the relay
|
|
116
|
+
const { chain_family, chain_network, transfer } = validateTransfer(chainFamily, options.chain_network, options.transfer);
|
|
117
|
+
if (chainNetwork && chainNetwork !== chain_network) {
|
|
118
|
+
throw new InvalidTxError('chain_network', `this pairing is for ${chain_family} ${chainNetwork}, got "${chain_network}"`);
|
|
119
|
+
}
|
|
120
|
+
const request = {
|
|
121
|
+
request_id: newRequestId(),
|
|
122
|
+
agent_id: this.agentId,
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
chain_family,
|
|
125
|
+
chain_network,
|
|
126
|
+
transfer,
|
|
127
|
+
context: options.context,
|
|
128
|
+
signature: '', // set below after signing
|
|
129
|
+
};
|
|
130
|
+
request.signature = signMessage(JSON.stringify(request), this.signingSecretKey);
|
|
131
|
+
// the relay simulator is evm-only: no simulate object for transfers, even on a managed pairing
|
|
132
|
+
return this.send(request, options);
|
|
133
|
+
}
|
|
134
|
+
/** encrypt a signed request to the paired human, send it through the relay, and wait for the answer */
|
|
135
|
+
send(request, options, simulate) {
|
|
136
|
+
const { humanPubkey, humanPubkeyHex, relayUrl } = this.pairing;
|
|
137
|
+
const requestId = request.request_id;
|
|
138
|
+
const timeoutMs = options.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
139
|
+
const { join_timeout_ms: joinTimeoutMs, signal } = options;
|
|
86
140
|
return new Promise((resolve, reject) => {
|
|
87
141
|
let ws;
|
|
88
142
|
let timer;
|
|
143
|
+
let joinTimer;
|
|
89
144
|
let settled = false;
|
|
145
|
+
const onAbort = () => {
|
|
146
|
+
if (!settled)
|
|
147
|
+
fail(`request ${requestId} cancelled`);
|
|
148
|
+
};
|
|
90
149
|
const cleanup = () => {
|
|
91
150
|
settled = true;
|
|
92
151
|
clearTimeout(timer);
|
|
152
|
+
clearTimeout(joinTimer);
|
|
153
|
+
signal?.removeEventListener('abort', onAbort);
|
|
93
154
|
if (ws && ws.readyState <= WebSocket.OPEN) {
|
|
94
155
|
ws.close();
|
|
95
156
|
}
|
|
96
157
|
};
|
|
97
|
-
const fail = (
|
|
158
|
+
const fail = (error) => {
|
|
159
|
+
const err = typeof error === 'string' ? new Error(error) : error;
|
|
98
160
|
cleanup();
|
|
99
|
-
this.emit('error', { message });
|
|
100
|
-
reject(
|
|
161
|
+
this.emit('error', { message: err.message });
|
|
162
|
+
reject(err);
|
|
101
163
|
};
|
|
164
|
+
if (signal?.aborted) {
|
|
165
|
+
onAbort();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
signal?.addEventListener('abort', onAbort);
|
|
102
169
|
timer = setTimeout(() => {
|
|
103
170
|
if (!settled) {
|
|
104
171
|
fail(`request ${requestId} timed out after ${timeoutMs}ms`);
|
|
105
172
|
}
|
|
106
173
|
}, timeoutMs);
|
|
107
174
|
this.emit('connecting', { relay: relayUrl });
|
|
175
|
+
// the request goes out only once joined, so failing before then guarantees nothing was sent
|
|
176
|
+
if (joinTimeoutMs !== undefined) {
|
|
177
|
+
joinTimer = setTimeout(() => {
|
|
178
|
+
if (!settled)
|
|
179
|
+
fail(`relay ${relayUrl} did not answer join within ${joinTimeoutMs}ms; request ${requestId} was not sent`);
|
|
180
|
+
}, joinTimeoutMs);
|
|
181
|
+
}
|
|
108
182
|
try {
|
|
109
183
|
ws = new WebSocket(relayUrl);
|
|
110
184
|
}
|
|
@@ -138,8 +212,9 @@ export class ClankerWallet extends TypedEmitter {
|
|
|
138
212
|
return;
|
|
139
213
|
}
|
|
140
214
|
if (msg.type === 'joined') {
|
|
215
|
+
clearTimeout(joinTimer);
|
|
141
216
|
this.emit('connected', { room: humanPubkeyHex });
|
|
142
|
-
const ciphertext = encryptMessage(JSON.stringify(
|
|
217
|
+
const ciphertext = encryptMessage(JSON.stringify(request), humanPubkey, this.secretKey);
|
|
143
218
|
const envelope = {
|
|
144
219
|
sender_pubkey: this.agentPubkeyHex,
|
|
145
220
|
ciphertext,
|
|
@@ -150,16 +225,8 @@ export class ClankerWallet extends TypedEmitter {
|
|
|
150
225
|
room: humanPubkeyHex,
|
|
151
226
|
payload: JSON.stringify(envelope),
|
|
152
227
|
};
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
wsMsg.simulate = {
|
|
156
|
-
from: humanWallet || '',
|
|
157
|
-
to: transaction.to,
|
|
158
|
-
value: transaction.value,
|
|
159
|
-
data: transaction.data,
|
|
160
|
-
chainId: chain_id,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
228
|
+
if (simulate)
|
|
229
|
+
wsMsg.simulate = simulate;
|
|
163
230
|
ws.send(JSON.stringify(wsMsg));
|
|
164
231
|
this.emit('request_sent', { request_id: requestId });
|
|
165
232
|
return;
|
|
@@ -201,9 +268,12 @@ export class ClankerWallet extends TypedEmitter {
|
|
|
201
268
|
return;
|
|
202
269
|
}
|
|
203
270
|
if (msg.type === 'error') {
|
|
204
|
-
fail(`relay error: ${msg.message}`);
|
|
271
|
+
fail(new RelayError(`relay error: ${msg.message}`));
|
|
205
272
|
}
|
|
206
273
|
});
|
|
207
274
|
});
|
|
208
275
|
}
|
|
209
276
|
}
|
|
277
|
+
function newRequestId() {
|
|
278
|
+
return `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
279
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { ClankerWallet } from './client.js';
|
|
1
|
+
export { ClankerWallet, RelayError } from './client.js';
|
|
2
2
|
export type { ClankerWalletOptions } from './client.js';
|
|
3
3
|
export { base64ToBytes, bytesToBase64, decryptMessage, encryptMessage, fromHex, generateKeypair, keypairFromSecret, signMessage, signingKeypairFromSeed, toHex, verifySignature } from './crypto.js';
|
|
4
4
|
export { TypedEmitter } from './events.js';
|
|
5
5
|
export type { EventMap } from './events.js';
|
|
6
|
-
export { InvalidTxError, isChecksumAddress, validateTx } from './validate.js';
|
|
6
|
+
export { InvalidTxError, isChecksumAddress, validateTransfer, validateTx } from './validate.js';
|
|
7
7
|
export type { TxField } from './validate.js';
|
|
8
|
-
export type { AgentEnvelope, PairingData, RequestTxOptions, Transaction, TxContext, TxInput, TxRequest, TxResponse, } from './types.js';
|
|
8
|
+
export type { AgentEnvelope, PairingData, RequestTransferOptions, RequestTxOptions, Transaction, Transfer, TransferFamily, TransferInput, TransferRequest, TxContext, TxInput, TxRequest, TxResponse, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ClankerWallet } from './client.js';
|
|
1
|
+
export { ClankerWallet, RelayError } from './client.js';
|
|
2
2
|
export { base64ToBytes, bytesToBase64, decryptMessage, encryptMessage, fromHex, generateKeypair, keypairFromSecret, signMessage, signingKeypairFromSeed, toHex, verifySignature } from './crypto.js';
|
|
3
3
|
export { TypedEmitter } from './events.js';
|
|
4
|
-
export { InvalidTxError, isChecksumAddress, validateTx } from './validate.js';
|
|
4
|
+
export { InvalidTxError, isChecksumAddress, validateTransfer, validateTx } from './validate.js';
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { type ClankerWallet } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* MCP server for `clanker-wallet mcp`: newline-delimited JSON-RPC 2.0 over stdio, hand-written so
|
|
4
|
+
* tweetnacl stays the only runtime dependency. the cli owns stdin/stdout and the identity and pairing
|
|
5
|
+
* files; this module answers one line at a time and keeps each request's state in memory.
|
|
6
|
+
*/
|
|
7
|
+
/** newest first; a client asking for another version gets the newest. not 2025-03-26: it requires receiving batches, which we refuse */
|
|
8
|
+
export declare const SUPPORTED_PROTOCOL_VERSIONS: string[];
|
|
9
|
+
/** get_request waits at most this long per call; assistants time tool calls out after about a minute */
|
|
10
|
+
export declare const MAX_WAIT_SECONDS = 50;
|
|
11
|
+
/** a pairing as the cli saves it in ~/.clanker-wallet/pairings/<family>.json */
|
|
12
|
+
export interface SavedPairing {
|
|
13
|
+
pubkey: string;
|
|
14
|
+
relay: string;
|
|
15
|
+
wallet: string | null;
|
|
16
|
+
mode: 'encrypted' | 'managed' | null;
|
|
17
|
+
chain_family?: string;
|
|
18
|
+
chain_network?: string;
|
|
19
|
+
}
|
|
20
|
+
/** the families the tools request on; chia waits until the web app shows it again */
|
|
21
|
+
type RequestFamily = 'evm' | 'bitcoin';
|
|
22
|
+
export interface McpServerOptions {
|
|
23
|
+
/** the package version, reported in initialize */
|
|
24
|
+
version: string;
|
|
25
|
+
identity: {
|
|
26
|
+
agent_id: string;
|
|
27
|
+
fingerprint: string;
|
|
28
|
+
};
|
|
29
|
+
/** the saved pairing for a family, or null when that family isn't paired */
|
|
30
|
+
loadPairing: (family: RequestFamily) => SavedPairing | null;
|
|
31
|
+
/** a new ClankerWallet with the agent identity, paired from `pairing` */
|
|
32
|
+
wallet: (pairing: SavedPairing) => ClankerWallet;
|
|
33
|
+
/** how long a request waits for the human (default 1 h, the relay's buffer) */
|
|
34
|
+
requestTtlMs?: number;
|
|
35
|
+
/** how long a request waits for the relay to confirm the join before failing unsent (default 20 s) */
|
|
36
|
+
joinTimeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
export declare const MCP_TOOLS: ({
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: string;
|
|
43
|
+
properties: {
|
|
44
|
+
reason: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
urgency: {
|
|
49
|
+
type: string;
|
|
50
|
+
enum: string[];
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
expected_outcome: {
|
|
54
|
+
type: string;
|
|
55
|
+
description: string;
|
|
56
|
+
};
|
|
57
|
+
chain_id: {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
};
|
|
61
|
+
to: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
value: {
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
data: {
|
|
70
|
+
type: string;
|
|
71
|
+
description: string;
|
|
72
|
+
};
|
|
73
|
+
request_id?: undefined;
|
|
74
|
+
wait_seconds?: undefined;
|
|
75
|
+
};
|
|
76
|
+
required: string[];
|
|
77
|
+
};
|
|
78
|
+
} | {
|
|
79
|
+
name: string;
|
|
80
|
+
description: string;
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: string;
|
|
83
|
+
properties: {
|
|
84
|
+
reason: {
|
|
85
|
+
type: string;
|
|
86
|
+
description: string;
|
|
87
|
+
};
|
|
88
|
+
urgency: {
|
|
89
|
+
type: string;
|
|
90
|
+
enum: string[];
|
|
91
|
+
description: string;
|
|
92
|
+
};
|
|
93
|
+
expected_outcome: {
|
|
94
|
+
type: string;
|
|
95
|
+
description: string;
|
|
96
|
+
};
|
|
97
|
+
family: {
|
|
98
|
+
type: string;
|
|
99
|
+
enum: string[];
|
|
100
|
+
description: string;
|
|
101
|
+
};
|
|
102
|
+
network: {
|
|
103
|
+
type: string;
|
|
104
|
+
enum: string[];
|
|
105
|
+
description: string;
|
|
106
|
+
};
|
|
107
|
+
to: {
|
|
108
|
+
type: string;
|
|
109
|
+
description: string;
|
|
110
|
+
};
|
|
111
|
+
amount: {
|
|
112
|
+
type: string;
|
|
113
|
+
description: string;
|
|
114
|
+
};
|
|
115
|
+
request_id?: undefined;
|
|
116
|
+
wait_seconds?: undefined;
|
|
117
|
+
};
|
|
118
|
+
required: string[];
|
|
119
|
+
};
|
|
120
|
+
} | {
|
|
121
|
+
name: string;
|
|
122
|
+
description: string;
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: string;
|
|
125
|
+
properties: {
|
|
126
|
+
request_id: {
|
|
127
|
+
type: string;
|
|
128
|
+
description: string;
|
|
129
|
+
};
|
|
130
|
+
wait_seconds: {
|
|
131
|
+
type: string;
|
|
132
|
+
minimum: number;
|
|
133
|
+
maximum: number;
|
|
134
|
+
description: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
required: string[];
|
|
138
|
+
};
|
|
139
|
+
} | {
|
|
140
|
+
name: string;
|
|
141
|
+
description: string;
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: string;
|
|
144
|
+
properties: {
|
|
145
|
+
request_id?: undefined;
|
|
146
|
+
wait_seconds?: undefined;
|
|
147
|
+
};
|
|
148
|
+
required?: undefined;
|
|
149
|
+
};
|
|
150
|
+
})[];
|
|
151
|
+
/** what get_request reports, in the order the docs list them */
|
|
152
|
+
export declare const REQUEST_STATUSES: readonly ["pending", "approved", "rejected", "failed", "unknown", "expired"];
|
|
153
|
+
export declare function createMcpServer(options: McpServerOptions): {
|
|
154
|
+
handleLine: (line: string) => Promise<string | null>;
|
|
155
|
+
};
|
|
156
|
+
export {};
|