openclaw-overlay-plugin 0.7.22

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.
Files changed (221) hide show
  1. package/README.md +406 -0
  2. package/SKILL.md +78 -0
  3. package/clawdbot.plugin.json +106 -0
  4. package/dist/cli-main.d.ts +7 -0
  5. package/dist/cli-main.js +192 -0
  6. package/dist/cli.d.ts +8 -0
  7. package/dist/cli.js +14 -0
  8. package/dist/core/config.d.ts +11 -0
  9. package/dist/core/config.js +13 -0
  10. package/dist/core/index.d.ts +25 -0
  11. package/dist/core/index.js +26 -0
  12. package/dist/core/payment.d.ts +16 -0
  13. package/dist/core/payment.js +94 -0
  14. package/dist/core/types.d.ts +94 -0
  15. package/dist/core/types.js +4 -0
  16. package/dist/core/verify.d.ts +28 -0
  17. package/dist/core/verify.js +104 -0
  18. package/dist/core/wallet.d.ts +99 -0
  19. package/dist/core/wallet.js +219 -0
  20. package/dist/scripts/baemail/commands.d.ts +64 -0
  21. package/dist/scripts/baemail/commands.js +258 -0
  22. package/dist/scripts/baemail/handler.d.ts +36 -0
  23. package/dist/scripts/baemail/handler.js +284 -0
  24. package/dist/scripts/baemail/index.d.ts +5 -0
  25. package/dist/scripts/baemail/index.js +5 -0
  26. package/dist/scripts/config.d.ts +48 -0
  27. package/dist/scripts/config.js +68 -0
  28. package/dist/scripts/index.d.ts +7 -0
  29. package/dist/scripts/index.js +7 -0
  30. package/dist/scripts/messaging/connect.d.ts +8 -0
  31. package/dist/scripts/messaging/connect.js +114 -0
  32. package/dist/scripts/messaging/handlers.d.ts +21 -0
  33. package/dist/scripts/messaging/handlers.js +334 -0
  34. package/dist/scripts/messaging/inbox.d.ts +11 -0
  35. package/dist/scripts/messaging/inbox.js +51 -0
  36. package/dist/scripts/messaging/index.d.ts +8 -0
  37. package/dist/scripts/messaging/index.js +8 -0
  38. package/dist/scripts/messaging/poll.d.ts +7 -0
  39. package/dist/scripts/messaging/poll.js +52 -0
  40. package/dist/scripts/messaging/send.d.ts +7 -0
  41. package/dist/scripts/messaging/send.js +43 -0
  42. package/dist/scripts/output.d.ts +12 -0
  43. package/dist/scripts/output.js +19 -0
  44. package/dist/scripts/overlay/discover.d.ts +7 -0
  45. package/dist/scripts/overlay/discover.js +72 -0
  46. package/dist/scripts/overlay/index.d.ts +7 -0
  47. package/dist/scripts/overlay/index.js +7 -0
  48. package/dist/scripts/overlay/registration.d.ts +19 -0
  49. package/dist/scripts/overlay/registration.js +176 -0
  50. package/dist/scripts/overlay/services.d.ts +29 -0
  51. package/dist/scripts/overlay/services.js +167 -0
  52. package/dist/scripts/overlay/transaction.d.ts +42 -0
  53. package/dist/scripts/overlay/transaction.js +103 -0
  54. package/dist/scripts/payment/build.d.ts +24 -0
  55. package/dist/scripts/payment/build.js +54 -0
  56. package/dist/scripts/payment/commands.d.ts +15 -0
  57. package/dist/scripts/payment/commands.js +73 -0
  58. package/dist/scripts/payment/index.d.ts +6 -0
  59. package/dist/scripts/payment/index.js +6 -0
  60. package/dist/scripts/payment/types.d.ts +56 -0
  61. package/dist/scripts/payment/types.js +4 -0
  62. package/dist/scripts/services/index.d.ts +6 -0
  63. package/dist/scripts/services/index.js +6 -0
  64. package/dist/scripts/services/queue.d.ts +11 -0
  65. package/dist/scripts/services/queue.js +28 -0
  66. package/dist/scripts/services/request.d.ts +7 -0
  67. package/dist/scripts/services/request.js +82 -0
  68. package/dist/scripts/services/respond.d.ts +11 -0
  69. package/dist/scripts/services/respond.js +132 -0
  70. package/dist/scripts/types.d.ts +107 -0
  71. package/dist/scripts/types.js +4 -0
  72. package/dist/scripts/utils/index.d.ts +6 -0
  73. package/dist/scripts/utils/index.js +6 -0
  74. package/dist/scripts/utils/merkle.d.ts +12 -0
  75. package/dist/scripts/utils/merkle.js +47 -0
  76. package/dist/scripts/utils/storage.d.ts +66 -0
  77. package/dist/scripts/utils/storage.js +211 -0
  78. package/dist/scripts/utils/woc.d.ts +26 -0
  79. package/dist/scripts/utils/woc.js +91 -0
  80. package/dist/scripts/wallet/balance.d.ts +22 -0
  81. package/dist/scripts/wallet/balance.js +240 -0
  82. package/dist/scripts/wallet/identity.d.ts +70 -0
  83. package/dist/scripts/wallet/identity.js +151 -0
  84. package/dist/scripts/wallet/index.d.ts +6 -0
  85. package/dist/scripts/wallet/index.js +6 -0
  86. package/dist/scripts/wallet/setup.d.ts +15 -0
  87. package/dist/scripts/wallet/setup.js +105 -0
  88. package/dist/scripts/x-verification/commands.d.ts +27 -0
  89. package/dist/scripts/x-verification/commands.js +222 -0
  90. package/dist/scripts/x-verification/index.d.ts +4 -0
  91. package/dist/scripts/x-verification/index.js +4 -0
  92. package/dist/services/built-in/api-proxy/index.d.ts +6 -0
  93. package/dist/services/built-in/api-proxy/index.js +23 -0
  94. package/dist/services/built-in/code-develop/index.d.ts +6 -0
  95. package/dist/services/built-in/code-develop/index.js +23 -0
  96. package/dist/services/built-in/code-review/index.d.ts +10 -0
  97. package/dist/services/built-in/code-review/index.js +51 -0
  98. package/dist/services/built-in/image-analysis/index.d.ts +6 -0
  99. package/dist/services/built-in/image-analysis/index.js +33 -0
  100. package/dist/services/built-in/memory-store/index.d.ts +6 -0
  101. package/dist/services/built-in/memory-store/index.js +22 -0
  102. package/dist/services/built-in/roulette/index.d.ts +6 -0
  103. package/dist/services/built-in/roulette/index.js +27 -0
  104. package/dist/services/built-in/summarize/index.d.ts +6 -0
  105. package/dist/services/built-in/summarize/index.js +21 -0
  106. package/dist/services/built-in/tell-joke/handler.d.ts +7 -0
  107. package/dist/services/built-in/tell-joke/handler.js +122 -0
  108. package/dist/services/built-in/tell-joke/index.d.ts +9 -0
  109. package/dist/services/built-in/tell-joke/index.js +31 -0
  110. package/dist/services/built-in/translate/index.d.ts +6 -0
  111. package/dist/services/built-in/translate/index.js +21 -0
  112. package/dist/services/built-in/web-research/index.d.ts +9 -0
  113. package/dist/services/built-in/web-research/index.js +51 -0
  114. package/dist/services/index.d.ts +13 -0
  115. package/dist/services/index.js +14 -0
  116. package/dist/services/loader.d.ts +77 -0
  117. package/dist/services/loader.js +292 -0
  118. package/dist/services/manager.d.ts +86 -0
  119. package/dist/services/manager.js +255 -0
  120. package/dist/services/registry.d.ts +98 -0
  121. package/dist/services/registry.js +204 -0
  122. package/dist/services/types.d.ts +230 -0
  123. package/dist/services/types.js +30 -0
  124. package/dist/test/cli.test.d.ts +7 -0
  125. package/dist/test/cli.test.js +329 -0
  126. package/dist/test/comprehensive-overlay.test.d.ts +13 -0
  127. package/dist/test/comprehensive-overlay.test.js +593 -0
  128. package/dist/test/key-derivation.test.d.ts +12 -0
  129. package/dist/test/key-derivation.test.js +86 -0
  130. package/dist/test/overlay-submit.test.d.ts +10 -0
  131. package/dist/test/overlay-submit.test.js +460 -0
  132. package/dist/test/request-response-flow.test.d.ts +5 -0
  133. package/dist/test/request-response-flow.test.js +209 -0
  134. package/dist/test/service-system.test.d.ts +5 -0
  135. package/dist/test/service-system.test.js +190 -0
  136. package/dist/test/utils/server-logic.d.ts +98 -0
  137. package/dist/test/utils/server-logic.js +286 -0
  138. package/dist/test/wallet.test.d.ts +7 -0
  139. package/dist/test/wallet.test.js +146 -0
  140. package/index.ts +1965 -0
  141. package/openclaw.plugin.json +106 -0
  142. package/package.json +73 -0
  143. package/src/cli-main.ts +230 -0
  144. package/src/cli.ts +16 -0
  145. package/src/core/README.md +246 -0
  146. package/src/core/config.ts +21 -0
  147. package/src/core/index.ts +42 -0
  148. package/src/core/payment.ts +111 -0
  149. package/src/core/types.ts +102 -0
  150. package/src/core/verify.ts +119 -0
  151. package/src/core/wallet.ts +282 -0
  152. package/src/scripts/baemail/commands.ts +326 -0
  153. package/src/scripts/baemail/handler.ts +338 -0
  154. package/src/scripts/baemail/index.ts +6 -0
  155. package/src/scripts/config.ts +81 -0
  156. package/src/scripts/index.ts +8 -0
  157. package/src/scripts/messaging/connect.ts +121 -0
  158. package/src/scripts/messaging/handlers.ts +394 -0
  159. package/src/scripts/messaging/inbox.ts +64 -0
  160. package/src/scripts/messaging/index.ts +9 -0
  161. package/src/scripts/messaging/poll.ts +59 -0
  162. package/src/scripts/messaging/send.ts +54 -0
  163. package/src/scripts/output.ts +21 -0
  164. package/src/scripts/overlay/discover.ts +81 -0
  165. package/src/scripts/overlay/index.ts +8 -0
  166. package/src/scripts/overlay/registration.ts +199 -0
  167. package/src/scripts/overlay/services.ts +199 -0
  168. package/src/scripts/overlay/transaction.ts +124 -0
  169. package/src/scripts/payment/build.ts +65 -0
  170. package/src/scripts/payment/commands.ts +92 -0
  171. package/src/scripts/payment/index.ts +7 -0
  172. package/src/scripts/payment/types.ts +62 -0
  173. package/src/scripts/services/index.ts +7 -0
  174. package/src/scripts/services/queue.ts +35 -0
  175. package/src/scripts/services/request.ts +98 -0
  176. package/src/scripts/services/respond.ts +149 -0
  177. package/src/scripts/types.ts +121 -0
  178. package/src/scripts/utils/index.ts +7 -0
  179. package/src/scripts/utils/merkle.ts +57 -0
  180. package/src/scripts/utils/storage.ts +231 -0
  181. package/src/scripts/utils/woc.ts +106 -0
  182. package/src/scripts/wallet/balance.ts +277 -0
  183. package/src/scripts/wallet/identity.ts +203 -0
  184. package/src/scripts/wallet/index.ts +7 -0
  185. package/src/scripts/wallet/setup.ts +121 -0
  186. package/src/scripts/x-verification/commands.ts +256 -0
  187. package/src/scripts/x-verification/index.ts +5 -0
  188. package/src/services/built-in/api-proxy/index.ts +26 -0
  189. package/src/services/built-in/api-proxy/prompt.md +26 -0
  190. package/src/services/built-in/code-develop/index.ts +26 -0
  191. package/src/services/built-in/code-develop/prompt.md +35 -0
  192. package/src/services/built-in/code-review/index.ts +54 -0
  193. package/src/services/built-in/code-review/prompt.md +105 -0
  194. package/src/services/built-in/image-analysis/index.ts +36 -0
  195. package/src/services/built-in/image-analysis/prompt.md +42 -0
  196. package/src/services/built-in/memory-store/index.ts +25 -0
  197. package/src/services/built-in/memory-store/prompt.md +45 -0
  198. package/src/services/built-in/roulette/index.ts +30 -0
  199. package/src/services/built-in/roulette/prompt.md +35 -0
  200. package/src/services/built-in/summarize/index.ts +24 -0
  201. package/src/services/built-in/summarize/prompt.md +27 -0
  202. package/src/services/built-in/tell-joke/handler.ts +134 -0
  203. package/src/services/built-in/tell-joke/index.ts +34 -0
  204. package/src/services/built-in/tell-joke/prompt.md +59 -0
  205. package/src/services/built-in/translate/index.ts +24 -0
  206. package/src/services/built-in/translate/prompt.md +23 -0
  207. package/src/services/built-in/web-research/index.ts +54 -0
  208. package/src/services/built-in/web-research/prompt.md +110 -0
  209. package/src/services/index.ts +16 -0
  210. package/src/services/loader.ts +344 -0
  211. package/src/services/manager.ts +304 -0
  212. package/src/services/registry.ts +246 -0
  213. package/src/services/types.ts +259 -0
  214. package/src/test/cli.test.ts +352 -0
  215. package/src/test/comprehensive-overlay.test.ts +729 -0
  216. package/src/test/key-derivation.test.ts +102 -0
  217. package/src/test/overlay-submit.test.ts +570 -0
  218. package/src/test/request-response-flow.test.ts +252 -0
  219. package/src/test/service-system.test.ts +241 -0
  220. package/src/test/utils/server-logic.ts +368 -0
  221. package/src/test/wallet.test.ts +166 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Wallet balance commands: balance, import, refund.
3
+ */
4
+
5
+ import fs from 'node:fs';
6
+ import { NETWORK, WALLET_DIR, PATHS } from '../config.js';
7
+ import { ok, fail } from '../output.js';
8
+ import { loadWalletIdentity } from './identity.js';
9
+ import { wocFetch, fetchBeefFromWoC, getExplorerBaseUrl } from '../utils/woc.js';
10
+ import { buildMerklePathFromTSC } from '../utils/merkle.js';
11
+ import { loadStoredChange, deleteStoredChange } from '../utils/storage.js';
12
+
13
+ import { BSVAgentWallet } from '../../core/index.js';
14
+
15
+ async function getBSVAgentWallet(): Promise<typeof BSVAgentWallet> {
16
+ return BSVAgentWallet;
17
+ }
18
+
19
+ // Dynamic import for @bsv/sdk
20
+ let _sdk: any = null;
21
+
22
+ async function getSdk(): Promise<any> {
23
+ if (_sdk) return _sdk;
24
+
25
+ try {
26
+ _sdk = await import('@bsv/sdk');
27
+ return _sdk;
28
+ } catch {
29
+ const { fileURLToPath } = await import('node:url');
30
+ const path = await import('node:path');
31
+ const os = await import('node:os');
32
+
33
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
34
+ const candidates = [
35
+ path.resolve(__dirname, '..', '..', '..', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
36
+ path.resolve(__dirname, '..', '..', '..', '..', '..', 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
37
+ path.resolve(os.homedir(), 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
38
+ ];
39
+
40
+ for (const p of candidates) {
41
+ try {
42
+ _sdk = await import(p);
43
+ return _sdk;
44
+ } catch {
45
+ // Try next
46
+ }
47
+ }
48
+ throw new Error('Cannot find @bsv/sdk. Run setup.sh first.');
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Sleep helper for polling
54
+ */
55
+ function sleep(ms: number): Promise<void> {
56
+ return new Promise(resolve => setTimeout(resolve, ms));
57
+ }
58
+
59
+ /**
60
+ * Balance command: show wallet balance.
61
+ */
62
+ export async function cmdBalance(): Promise<never> {
63
+ const BSVAgentWallet = await getBSVAgentWallet();
64
+ const sdk = await getSdk();
65
+
66
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
67
+ const total = await wallet.getBalance();
68
+ await wallet.destroy();
69
+
70
+ return ok({ walletBalance: total });
71
+ }
72
+
73
+ /**
74
+ * Import command: import external UTXO with merkle proof.
75
+ *
76
+ * This function handles both confirmed and unconfirmed transactions.
77
+ * For unconfirmed transactions, it uses BEEF from WoC which includes
78
+ * the source chain back to confirmed ancestors (SPV-compliant).
79
+ *
80
+ * If the transaction isn't yet on WoC (just broadcast), it will poll
81
+ * with exponential backoff for up to 60 seconds.
82
+ */
83
+ export async function cmdImport(txidArg: string | undefined, voutStr?: string): Promise<never> {
84
+ if (!txidArg) {
85
+ return fail('Usage: import <txid> [vout]');
86
+ }
87
+
88
+ const vout = parseInt(voutStr || '0', 10);
89
+ const txid = txidArg.toLowerCase();
90
+
91
+ if (!/^[0-9a-f]{64}$/.test(txid)) {
92
+ return fail('Invalid txid — must be 64 hex characters');
93
+ }
94
+
95
+ const sdk = await getSdk();
96
+ const BSVAgentWallet = await getBSVAgentWallet();
97
+
98
+ // Poll for transaction on WoC with exponential backoff
99
+ // This handles the case where user just broadcast and WoC hasn't indexed yet
100
+ let txInfo: any = null;
101
+ const maxWaitMs = 60000; // 60 seconds max
102
+ const startTime = Date.now();
103
+ let attempt = 0;
104
+
105
+ while (Date.now() - startTime < maxWaitMs) {
106
+ const txInfoResp = await wocFetch(`/tx/${txid}`, {}, 1, 10000); // Single retry, 10s timeout
107
+
108
+ if (txInfoResp.ok) {
109
+ txInfo = await txInfoResp.json();
110
+ break;
111
+ } else if (txInfoResp.status === 404) {
112
+ // Transaction not found yet - wait and retry
113
+ attempt++;
114
+ const delayMs = Math.min(1000 * Math.pow(1.5, attempt), 10000); // 1s, 1.5s, 2.25s, ... max 10s
115
+ console.error(`[import] Transaction not on WoC yet, waiting ${Math.round(delayMs/1000)}s... (attempt ${attempt})`);
116
+ await sleep(delayMs);
117
+ continue;
118
+ } else {
119
+ return fail(`Failed to fetch tx info: ${txInfoResp.status}`);
120
+ }
121
+ }
122
+
123
+ if (!txInfo) {
124
+ return fail(`Transaction ${txid} not found on WhatsOnChain after ${Math.round((Date.now() - startTime) / 1000)}s. The transaction may not have been broadcast yet, or the txid may be incorrect.`);
125
+ }
126
+
127
+ const isConfirmed = txInfo.confirmations && txInfo.confirmations >= 1;
128
+ const blockHeight = txInfo.blockheight;
129
+
130
+ // Validate output exists
131
+ if (!txInfo.vout || !txInfo.vout[vout]) {
132
+ return fail(`Output index ${vout} not found in transaction (has ${txInfo.vout?.length || 0} outputs)`);
133
+ }
134
+
135
+ let atomicBeefBytes: Uint8Array | undefined;
136
+
137
+ // Try WoC BEEF first - works for both confirmed and unconfirmed transactions
138
+ // WoC provides BEEF with full source chain back to confirmed ancestors
139
+ const wocBeefBytes = await fetchBeefFromWoC(txid);
140
+
141
+ if (wocBeefBytes) {
142
+ try {
143
+ const wocBeef = sdk.Beef.fromBinary(Array.from(wocBeefBytes));
144
+ const foundTx = wocBeef.findTxid(txid);
145
+
146
+ if (foundTx) {
147
+ // Verify the output exists in the parsed tx
148
+ const txObj = foundTx.tx || foundTx._tx;
149
+ if (txObj) {
150
+ const output = txObj.outputs[vout];
151
+ if (!output) {
152
+ return fail(`Output index ${vout} not found in BEEF transaction (has ${txObj.outputs.length} outputs)`);
153
+ }
154
+ }
155
+ atomicBeefBytes = wocBeef.toBinaryAtomic(txid);
156
+ }
157
+ } catch (beefErr: any) {
158
+ console.error(`[import] WoC BEEF parse failed: ${beefErr.message}`);
159
+ // Fall through to manual construction
160
+ }
161
+ }
162
+
163
+ // Fallback for confirmed txs: construct BEEF manually using TSC merkle proof
164
+ if (!atomicBeefBytes && isConfirmed) {
165
+ try {
166
+ const rawTxResp = await wocFetch(`/tx/${txid}/hex`);
167
+ if (!rawTxResp.ok) {
168
+ return fail(`Failed to fetch raw transaction: ${rawTxResp.status}`);
169
+ }
170
+ const rawTxHex = await rawTxResp.text();
171
+ const sourceTx = sdk.Transaction.fromHex(rawTxHex.trim());
172
+
173
+ const proofResp = await wocFetch(`/tx/${txid}/proof/tsc`);
174
+ if (!proofResp.ok) {
175
+ return fail(`Failed to fetch merkle proof: ${proofResp.status}`);
176
+ }
177
+ const proofData = await proofResp.json();
178
+
179
+ if (!Array.isArray(proofData) || proofData.length === 0) {
180
+ return fail('Merkle proof not available from WoC');
181
+ }
182
+
183
+ const proof = proofData[0];
184
+ const merklePath = await buildMerklePathFromTSC(txid, proof.index, proof.nodes, blockHeight);
185
+ sourceTx.merklePath = merklePath;
186
+
187
+ const beef = new sdk.Beef();
188
+ beef.mergeTransaction(sourceTx);
189
+ atomicBeefBytes = beef.toBinaryAtomic(txid);
190
+ } catch (manualErr: any) {
191
+ return fail(`Failed to construct BEEF manually: ${manualErr.message}`);
192
+ }
193
+ }
194
+
195
+ // If still no BEEF, we can't import
196
+ if (!atomicBeefBytes) {
197
+ if (isConfirmed) {
198
+ return fail(`Transaction ${txid} is confirmed but BEEF construction failed. This is unexpected — please report this issue.`);
199
+ } else {
200
+ // Unconfirmed and no BEEF available
201
+ // This can happen if the funding tx itself spends unconfirmed inputs
202
+ return fail(
203
+ `Transaction ${txid} is unconfirmed (${txInfo.confirmations || 0} confirmations) and BEEF is not available.\n\n` +
204
+ `This usually means the funding transaction spends from other unconfirmed transactions, creating a chain.\n` +
205
+ `Wait for 1 block confirmation (~10 minutes) and try again, or use a fresh UTXO as the funding source.`
206
+ );
207
+ }
208
+ }
209
+
210
+ // Get output satoshis for reporting
211
+ const outputSatoshis = txInfo.vout[vout].value != null
212
+ ? Math.round(txInfo.vout[vout].value * 1e8)
213
+ : undefined;
214
+
215
+ // Import into wallet
216
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
217
+ const identityKey = await wallet.getIdentityKey();
218
+
219
+ try {
220
+ await wallet._setup.wallet.storage.internalizeAction({
221
+ tx: Array.from(atomicBeefBytes),
222
+ outputs: [{
223
+ outputIndex: vout,
224
+ protocol: 'wallet payment',
225
+ paymentRemittance: {
226
+ derivationPrefix: sdk.Utils.toBase64(sdk.Utils.toArray('import', 'utf8')),
227
+ derivationSuffix: sdk.Utils.toBase64(sdk.Utils.toArray('now', 'utf8')),
228
+ senderIdentityKey: identityKey,
229
+ },
230
+ }],
231
+ description: 'External funding import',
232
+ });
233
+
234
+ const balance = await wallet.getBalance();
235
+ await wallet.destroy();
236
+
237
+ const explorerBase = getExplorerBaseUrl();
238
+ return ok({
239
+ txid,
240
+ vout,
241
+ satoshis: outputSatoshis,
242
+ blockHeight: blockHeight || null,
243
+ confirmations: txInfo.confirmations || 0,
244
+ imported: true,
245
+ unconfirmed: !isConfirmed,
246
+ balance,
247
+ explorer: `${explorerBase}/tx/${txid}`,
248
+ });
249
+ } catch (err: any) {
250
+ await wallet.destroy();
251
+
252
+ // Provide helpful error messages for common issues
253
+ if (err.message?.includes('already') || err.message?.includes('duplicate')) {
254
+ return fail(`UTXO ${txid}:${vout} appears to already be imported.`);
255
+ }
256
+ if (err.message?.includes('script') || err.message?.includes('locking')) {
257
+ return fail(`UTXO ${txid}:${vout} does not belong to this wallet's address. Make sure you sent to the correct address.`);
258
+ }
259
+
260
+ return fail(`Failed to import UTXO: ${err.message}`);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Refund command: sweep wallet to an address.
266
+ */
267
+ export async function cmdRefund(targetAddress: string | undefined): Promise<void> {
268
+ if (!targetAddress) {
269
+ return fail('Usage: refund <address>');
270
+ }
271
+
272
+ if (!fs.existsSync(PATHS.walletIdentity)) {
273
+ return fail('Wallet not initialized. Run: setup');
274
+ }
275
+
276
+ // TODO IMPLEMENT THIS
277
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Wallet identity helpers.
3
+ */
4
+
5
+ import fs from 'node:fs';
6
+ import { PATHS } from '../config.js';
7
+ import type { WalletIdentity } from '../types.js';
8
+ import { CachedKeyDeriver, Utils } from '@bsv/sdk';
9
+ import { brc29ProtocolID } from '@bsv/wallet-toolbox';
10
+
11
+ // Dynamic import for @bsv/sdk
12
+ let _sdk: any = null;
13
+
14
+ async function getSdk(): Promise<any> {
15
+ if (_sdk) return _sdk;
16
+
17
+ try {
18
+ _sdk = await import('@bsv/sdk');
19
+ return _sdk;
20
+ } catch {
21
+ const { fileURLToPath } = await import('node:url');
22
+ const path = await import('node:path');
23
+ const os = await import('node:os');
24
+
25
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
+ const candidates = [
27
+ path.resolve(__dirname, '..', '..', '..', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
28
+ path.resolve(__dirname, '..', '..', '..', '..', '..', 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
29
+ path.resolve(os.homedir(), 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
30
+ ];
31
+
32
+ for (const p of candidates) {
33
+ try {
34
+ _sdk = await import(p);
35
+ return _sdk;
36
+ } catch {
37
+ // Try next
38
+ }
39
+ }
40
+ throw new Error('Cannot find @bsv/sdk. Run setup.sh first.');
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Load wallet identity from disk.
46
+ * @returns Identity object with rootKeyHex and identityKey
47
+ * @throws Error if wallet not initialized
48
+ */
49
+ export function loadWalletIdentity(): WalletIdentity {
50
+ if (!fs.existsSync(PATHS.walletIdentity)) {
51
+ throw new Error('Wallet not initialized. Run: cli setup');
52
+ }
53
+
54
+ // Security warning for overly permissive file mode
55
+ try {
56
+ const fileMode = fs.statSync(PATHS.walletIdentity).mode & 0o777;
57
+ if (fileMode & 0o044) { // world or group readable
58
+ console.error(`[security] WARNING: ${PATHS.walletIdentity} has permissive mode 0${fileMode.toString(8)}. Run: chmod 600 ${PATHS.walletIdentity}`);
59
+ }
60
+ } catch {
61
+ // Ignore stat errors
62
+ }
63
+
64
+ return JSON.parse(fs.readFileSync(PATHS.walletIdentity, 'utf-8'));
65
+ }
66
+
67
+ /**
68
+ * Load identity and private key for relay message signing.
69
+ * @returns Object with identityKey and privKey
70
+ */
71
+ export async function loadIdentity(): Promise<{ identityKey: string; privKey: any }> {
72
+ const identity = loadWalletIdentity();
73
+ const sdk = await getSdk();
74
+ const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
75
+ return { identityKey: identity.identityKey, privKey };
76
+ }
77
+
78
+ /**
79
+ * Sign a relay message using ECDSA.
80
+ * @param privKey - Private key for signing
81
+ * @param to - Recipient's identity key
82
+ * @param type - Message type
83
+ * @param payload - Message payload
84
+ * @returns Hex-encoded DER signature
85
+ */
86
+ export async function signRelayMessage(
87
+ privKey: any,
88
+ to: string,
89
+ type: string,
90
+ payload: unknown
91
+ ): Promise<string> {
92
+ const sdk = await getSdk();
93
+ const preimage = to + type + JSON.stringify(payload);
94
+ const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
95
+ const sig = privKey.sign(msgHash);
96
+ return (Array.from(sig.toDER()) as number[]).map((b) => b.toString(16).padStart(2, '0')).join('');
97
+ }
98
+
99
+ /**
100
+ * Verify a relay message signature.
101
+ * @param fromKey - Sender's public key
102
+ * @param to - Recipient's identity key
103
+ * @param type - Message type
104
+ * @param payload - Message payload
105
+ * @param signatureHex - Hex-encoded DER signature
106
+ * @returns Verification result
107
+ */
108
+ export async function verifyRelaySignature(
109
+ fromKey: string,
110
+ to: string,
111
+ type: string,
112
+ payload: unknown,
113
+ signatureHex: string | undefined
114
+ ): Promise<{ valid: boolean; reason?: string }> {
115
+ if (!signatureHex) return { valid: false, reason: 'no signature' };
116
+
117
+ try {
118
+ const sdk = await getSdk();
119
+ const preimage = to + type + JSON.stringify(payload);
120
+ const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
121
+ const sigBytes: number[] = [];
122
+ for (let i = 0; i < signatureHex.length; i += 2) {
123
+ sigBytes.push(parseInt(signatureHex.substring(i, i + 2), 16));
124
+ }
125
+ const sig = sdk.Signature.fromDER(sigBytes);
126
+ const pubKey = sdk.PublicKey.fromString(fromKey);
127
+ return { valid: pubKey.verify(msgHash, sig) };
128
+ } catch (err) {
129
+ return { valid: false, reason: String(err) };
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Derive wallet address components from a private key.
135
+ *
136
+ * IMPORTANT: This uses BRC-29 key derivation to create a child key.
137
+ * Any transactions spending to this address MUST use the matching
138
+ * child private key for signing, NOT the root key.
139
+ *
140
+ * Use deriveWalletKeys() to get both the address and signing key.
141
+ */
142
+ export async function deriveWalletAddress(privKey: any): Promise<{
143
+ address: string;
144
+ hash160: Uint8Array;
145
+ pubKey: any;
146
+ }> {
147
+
148
+ const keyDeriver = new CachedKeyDeriver(privKey);
149
+ const pubKey = keyDeriver.derivePublicKey(
150
+ brc29ProtocolID,
151
+ Utils.toBase64(Utils.toArray('import')) + ' ' + Utils.toBase64(Utils.toArray('now')),
152
+ 'self',
153
+ true
154
+ );
155
+
156
+ const address = pubKey.toAddress();
157
+ const hash160 = Buffer.from(pubKey.toHash());
158
+
159
+ return { address, hash160, pubKey };
160
+ }
161
+
162
+ /**
163
+ * Derive wallet keys for both address AND transaction signing.
164
+ *
165
+ * CRITICAL: Use this function to get the child private key for signing
166
+ * transactions that spend from the derived address. Do NOT use the
167
+ * root private key - it will cause signature verification failures!
168
+ *
169
+ * @param rootPrivKey - Root private key from wallet identity
170
+ * @returns Object with address, hash160, and CHILD private key for signing
171
+ */
172
+ export async function deriveWalletKeys(rootPrivKey: any): Promise<{
173
+ address: string;
174
+ hash160: Uint8Array;
175
+ pubKey: any;
176
+ childPrivKey: any;
177
+ }> {
178
+ const keyDeriver = new CachedKeyDeriver(rootPrivKey);
179
+
180
+ const derivationPrefix = Utils.toBase64(Utils.toArray('import'));
181
+ const derivationSuffix = Utils.toBase64(Utils.toArray('now'));
182
+ const keyString = `${derivationPrefix} ${derivationSuffix}`;
183
+
184
+ // Derive child private key (for signing)
185
+ const childPrivKey = keyDeriver.derivePrivateKey(
186
+ brc29ProtocolID,
187
+ keyString,
188
+ 'self'
189
+ );
190
+
191
+ // Derive child public key (for address)
192
+ const pubKey = keyDeriver.derivePublicKey(
193
+ brc29ProtocolID,
194
+ keyString,
195
+ 'self',
196
+ true
197
+ );
198
+
199
+ const address = pubKey.toAddress();
200
+ const hash160 = Buffer.from(pubKey.toHash());
201
+
202
+ return { address, hash160, pubKey, childPrivKey };
203
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Wallet module exports.
3
+ */
4
+
5
+ export * from './identity.js';
6
+ export * from './setup.js';
7
+ export * from './balance.js';
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Wallet setup commands: setup, identity, address.
3
+ */
4
+
5
+ import fs from 'node:fs';
6
+ import { NETWORK, WALLET_DIR, OVERLAY_URL, PATHS } from '../config.js';
7
+ import { ok, fail } from '../output.js';
8
+ import { loadWalletIdentity, deriveWalletAddress } from './identity.js';
9
+
10
+ import { BSVAgentWallet } from '../../core/index.js';
11
+
12
+ async function getBSVAgentWallet(): Promise<typeof BSVAgentWallet> {
13
+ return BSVAgentWallet;
14
+ }
15
+
16
+ // Dynamic import for @bsv/sdk
17
+ let _sdk: any = null;
18
+
19
+ async function getSdk(): Promise<any> {
20
+ if (_sdk) return _sdk;
21
+
22
+ try {
23
+ _sdk = await import('@bsv/sdk');
24
+ return _sdk;
25
+ } catch {
26
+ const { fileURLToPath } = await import('node:url');
27
+ const path = await import('node:path');
28
+ const os = await import('node:os');
29
+
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
+ const candidates = [
32
+ path.resolve(__dirname, '..', '..', '..', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
33
+ path.resolve(__dirname, '..', '..', '..', '..', '..', 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
34
+ path.resolve(os.homedir(), 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
35
+ ];
36
+
37
+ for (const p of candidates) {
38
+ try {
39
+ _sdk = await import(p);
40
+ return _sdk;
41
+ } catch {
42
+ // Try next
43
+ }
44
+ }
45
+ throw new Error('Cannot find @bsv/sdk. Run setup.sh first.');
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Setup command: create wallet and show identity.
51
+ */
52
+ export async function cmdSetup(): Promise<never> {
53
+ const BSVAgentWallet = await getBSVAgentWallet();
54
+
55
+ if (fs.existsSync(PATHS.walletIdentity)) {
56
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
57
+ const identityKey = await wallet.getIdentityKey();
58
+ await wallet.destroy();
59
+
60
+ return ok({
61
+ identityKey,
62
+ walletDir: WALLET_DIR,
63
+ network: NETWORK,
64
+ overlayUrl: OVERLAY_URL,
65
+ alreadyExisted: true,
66
+ });
67
+ }
68
+
69
+ fs.mkdirSync(WALLET_DIR, { recursive: true });
70
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
71
+ const identityKey = await wallet.getIdentityKey();
72
+ await wallet.destroy();
73
+
74
+ // Restrict permissions on wallet-identity.json (contains private key)
75
+ if (fs.existsSync(PATHS.walletIdentity)) {
76
+ fs.chmodSync(PATHS.walletIdentity, 0o600);
77
+ }
78
+
79
+ return ok({
80
+ identityKey,
81
+ walletDir: WALLET_DIR,
82
+ network: NETWORK,
83
+ overlayUrl: OVERLAY_URL,
84
+ alreadyExisted: false,
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Identity command: show identity public key.
90
+ */
91
+ export async function cmdIdentity(): Promise<never> {
92
+ const BSVAgentWallet = await getBSVAgentWallet();
93
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
94
+ const identityKey = await wallet.getIdentityKey();
95
+ await wallet.destroy();
96
+
97
+ return ok({ identityKey });
98
+ }
99
+
100
+ /**
101
+ * Address command: show P2PKH receive address.
102
+ */
103
+ export async function cmdAddress(): Promise<never> {
104
+ if (!fs.existsSync(PATHS.walletIdentity)) {
105
+ return fail('Wallet not initialized. Run: setup');
106
+ }
107
+
108
+ const sdk = await getSdk();
109
+ const identity = loadWalletIdentity();
110
+ const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
111
+ const { address } = await deriveWalletAddress(privKey);
112
+
113
+ return ok({
114
+ address,
115
+ network: NETWORK,
116
+ identityKey: identity.identityKey,
117
+ note: NETWORK === 'mainnet'
118
+ ? `Fund this address at an exchange — Explorer: https://whatsonchain.com/address/${address}`
119
+ : `Fund via faucet: https://witnessonchain.com/faucet/tbsv — Explorer: https://test.whatsonchain.com/address/${address}`,
120
+ });
121
+ }