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,368 @@
1
+ /**
2
+ * Server validation logic mirrored from openclaw-overlay.
3
+ *
4
+ * This module contains the exact same parsing and validation logic
5
+ * used by the server's topic managers, allowing us to validate
6
+ * client output before submission.
7
+ *
8
+ * Updated to use PushDrop tokens instead of plain OP_RETURN.
9
+ */
10
+
11
+ import { Script, OP, Beef, PushDrop, LockingScript } from '@bsv/sdk';
12
+
13
+ export const PROTOCOL_ID = 'openclaw-overlay-v1';
14
+
15
+ // ============================================================================
16
+ // Type Definitions (mirrored from server)
17
+ // ============================================================================
18
+
19
+ export interface OpenclawIdentityData {
20
+ protocol: string;
21
+ type: 'identity';
22
+ identityKey: string;
23
+ name: string;
24
+ description: string;
25
+ channels: Record<string, string>;
26
+ capabilities: string[];
27
+ timestamp: string;
28
+ }
29
+
30
+ export interface OpenclawIdentityRevocationData {
31
+ protocol: string;
32
+ type: 'identity-revocation';
33
+ identityKey: string;
34
+ reason?: string;
35
+ timestamp: string;
36
+ }
37
+
38
+ export interface OpenclawServiceData {
39
+ protocol: string;
40
+ type: 'service';
41
+ identityKey: string;
42
+ serviceId: string;
43
+ name: string;
44
+ description: string;
45
+ pricing: {
46
+ model: string;
47
+ amountSats: number;
48
+ };
49
+ timestamp: string;
50
+ }
51
+
52
+ export type OpenclawPayload = OpenclawIdentityData | OpenclawIdentityRevocationData | OpenclawServiceData;
53
+
54
+ export interface AdmittanceResult {
55
+ outputsToAdmit: number[];
56
+ coinsToRetain: number[];
57
+ }
58
+
59
+ export interface STEAKResponse {
60
+ [topic: string]: AdmittanceResult;
61
+ }
62
+
63
+ // ============================================================================
64
+ // Script Parsing using PushDrop.decode()
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Extract data fields from a PushDrop script using the SDK's decode method.
69
+ * Returns the fields array or null if not a valid PushDrop script.
70
+ */
71
+ export function extractPushDropFields(script: Script | LockingScript): number[][] | null {
72
+ try {
73
+ const decoded = PushDrop.decode(script as LockingScript);
74
+ return decoded.fields;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Legacy function for backwards compatibility - extracts from OP_RETURN scripts.
82
+ * @deprecated Use extractPushDropFields instead
83
+ */
84
+ export function extractOpReturnPushes(script: Script): Uint8Array[] | null {
85
+ const chunks = script.chunks;
86
+
87
+ // Legacy 4+ chunk format: OP_FALSE OP_RETURN <data> <data> ...
88
+ if (chunks.length >= 4 &&
89
+ chunks[0].op === OP.OP_FALSE &&
90
+ chunks[1].op === OP.OP_RETURN) {
91
+ const pushes: Uint8Array[] = [];
92
+ for (let i = 2; i < chunks.length; i++) {
93
+ if (chunks[i].data) {
94
+ pushes.push(new Uint8Array(chunks[i].data!));
95
+ }
96
+ }
97
+ return pushes;
98
+ }
99
+
100
+ // Collapsed 2-chunk format: OP_FALSE OP_RETURN with data blob
101
+ if (chunks.length === 2 &&
102
+ chunks[0].op === OP.OP_FALSE &&
103
+ chunks[1].op === OP.OP_RETURN &&
104
+ chunks[1].data) {
105
+ const blob = chunks[1].data;
106
+ const pushes: Uint8Array[] = [];
107
+ let pos = 0;
108
+
109
+ while (pos < blob.length) {
110
+ const op = blob[pos++];
111
+
112
+ if (op > 0 && op <= 75) {
113
+ const end = Math.min(pos + op, blob.length);
114
+ pushes.push(new Uint8Array(blob.slice(pos, end)));
115
+ pos = end;
116
+ } else if (op === 0x4c) {
117
+ const len = blob[pos++] ?? 0;
118
+ const end = Math.min(pos + len, blob.length);
119
+ pushes.push(new Uint8Array(blob.slice(pos, end)));
120
+ pos = end;
121
+ } else if (op === 0x4d) {
122
+ const len = (blob[pos] ?? 0) | ((blob[pos + 1] ?? 0) << 8);
123
+ pos += 2;
124
+ const end = Math.min(pos + len, blob.length);
125
+ pushes.push(new Uint8Array(blob.slice(pos, end)));
126
+ pos = end;
127
+ } else if (op === 0x4e) {
128
+ const len = ((blob[pos] ?? 0) |
129
+ ((blob[pos + 1] ?? 0) << 8) |
130
+ ((blob[pos + 2] ?? 0) << 16) |
131
+ ((blob[pos + 3] ?? 0) << 24)) >>> 0;
132
+ pos += 4;
133
+ const end = Math.min(pos + len, blob.length);
134
+ pushes.push(new Uint8Array(blob.slice(pos, end)));
135
+ pos = end;
136
+ } else {
137
+ break;
138
+ }
139
+ }
140
+
141
+ return pushes.length >= 2 ? pushes : null;
142
+ }
143
+
144
+ return null;
145
+ }
146
+
147
+ // ============================================================================
148
+ // Payload Parsing - Updated for PushDrop
149
+ // ============================================================================
150
+
151
+ /**
152
+ * Parse identity payload from a PushDrop script.
153
+ * The first field contains the JSON payload.
154
+ */
155
+ export function parseIdentityOutput(script: Script | LockingScript): OpenclawIdentityData | null {
156
+ const fields = extractPushDropFields(script);
157
+ if (!fields || fields.length < 1) return null;
158
+
159
+ try {
160
+ const payload = JSON.parse(
161
+ new TextDecoder().decode(new Uint8Array(fields[0]))
162
+ ) as OpenclawIdentityData;
163
+
164
+ // Server validation rules
165
+ if (payload.protocol !== PROTOCOL_ID) return null;
166
+ if (payload.type !== 'identity') return null;
167
+ if (typeof payload.identityKey !== 'string' || !/^[0-9a-fA-F]{66}$/.test(payload.identityKey)) return null;
168
+ if (typeof payload.name !== 'string' || payload.name.length === 0) return null;
169
+ if (!Array.isArray(payload.capabilities)) return null;
170
+ if (typeof payload.timestamp !== 'string') return null;
171
+
172
+ return payload;
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Parse identity revocation payload from a PushDrop script.
180
+ */
181
+ export function parseRevocationOutput(script: Script | LockingScript): OpenclawIdentityRevocationData | null {
182
+ const fields = extractPushDropFields(script);
183
+ if (!fields || fields.length < 1) return null;
184
+
185
+ try {
186
+ const payload = JSON.parse(
187
+ new TextDecoder().decode(new Uint8Array(fields[0]))
188
+ ) as OpenclawIdentityRevocationData;
189
+
190
+ if (payload.protocol !== PROTOCOL_ID) return null;
191
+ if (payload.type !== 'identity-revocation') return null;
192
+ if (typeof payload.identityKey !== 'string' || !/^[0-9a-fA-F]{66}$/.test(payload.identityKey)) return null;
193
+ if (typeof payload.timestamp !== 'string') return null;
194
+
195
+ return payload;
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Parse service payload from a PushDrop script.
203
+ */
204
+ export function parseServiceOutput(script: Script | LockingScript): OpenclawServiceData | null {
205
+ const fields = extractPushDropFields(script);
206
+ if (!fields || fields.length < 1) return null;
207
+
208
+ try {
209
+ const payload = JSON.parse(
210
+ new TextDecoder().decode(new Uint8Array(fields[0]))
211
+ ) as OpenclawServiceData;
212
+
213
+ if (payload.protocol !== PROTOCOL_ID) return null;
214
+ if (payload.type !== 'service') return null;
215
+ if (typeof payload.identityKey !== 'string' || !/^[0-9a-fA-F]{66}$/.test(payload.identityKey)) return null;
216
+ if (typeof payload.serviceId !== 'string' || payload.serviceId.length === 0) return null;
217
+ if (typeof payload.name !== 'string' || payload.name.length === 0) return null;
218
+ if (!payload.pricing || typeof payload.pricing.amountSats !== 'number') return null;
219
+ if (typeof payload.timestamp !== 'string') return null;
220
+
221
+ return payload;
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
226
+
227
+ // ============================================================================
228
+ // Topic Manager Simulation
229
+ // ============================================================================
230
+
231
+ /**
232
+ * Simulate the identity topic manager's identifyAdmissibleOutputs.
233
+ */
234
+ export function identifyIdentityOutputs(beef: number[]): AdmittanceResult {
235
+ const parsedBeef = Beef.fromBinary(beef);
236
+ const subjectTx = parsedBeef.txs[0]?._tx;
237
+
238
+ if (!subjectTx) {
239
+ return { outputsToAdmit: [], coinsToRetain: [] };
240
+ }
241
+
242
+ const outputsToAdmit: number[] = [];
243
+
244
+ for (let i = 0; i < subjectTx.outputs.length; i++) {
245
+ const output = subjectTx.outputs[i];
246
+ if (output.lockingScript) {
247
+ // Check identity
248
+ const identity = parseIdentityOutput(output.lockingScript);
249
+ if (identity !== null) {
250
+ outputsToAdmit.push(i);
251
+ continue;
252
+ }
253
+ // Check revocation
254
+ const revocation = parseRevocationOutput(output.lockingScript);
255
+ if (revocation !== null) {
256
+ outputsToAdmit.push(i);
257
+ }
258
+ }
259
+ }
260
+
261
+ return { outputsToAdmit, coinsToRetain: [] };
262
+ }
263
+
264
+ /**
265
+ * Simulate the services topic manager's identifyAdmissibleOutputs.
266
+ */
267
+ export function identifyServiceOutputs(beef: number[]): AdmittanceResult {
268
+ const parsedBeef = Beef.fromBinary(beef);
269
+ const subjectTx = parsedBeef.txs[0]?._tx;
270
+
271
+ if (!subjectTx) {
272
+ return { outputsToAdmit: [], coinsToRetain: [] };
273
+ }
274
+
275
+ const outputsToAdmit: number[] = [];
276
+
277
+ for (let i = 0; i < subjectTx.outputs.length; i++) {
278
+ const output = subjectTx.outputs[i];
279
+ if (output.lockingScript) {
280
+ const service = parseServiceOutput(output.lockingScript);
281
+ if (service !== null) {
282
+ outputsToAdmit.push(i);
283
+ }
284
+ }
285
+ }
286
+
287
+ return { outputsToAdmit, coinsToRetain: [] };
288
+ }
289
+
290
+ // ============================================================================
291
+ // BEEF Validation
292
+ // ============================================================================
293
+
294
+ /**
295
+ * Validate BEEF format and structure.
296
+ */
297
+ export function validateBeef(beef: number[]): {
298
+ valid: boolean;
299
+ error?: string;
300
+ version?: number;
301
+ txCount?: number;
302
+ hasProofs?: boolean;
303
+ } {
304
+ try {
305
+ // Check magic bytes
306
+ if (beef.length < 4) {
307
+ return { valid: false, error: 'BEEF too short' };
308
+ }
309
+
310
+ const magic = beef.slice(0, 4);
311
+ const magicHex = magic.map(b => b.toString(16).padStart(2, '0')).join('');
312
+
313
+ if (magicHex !== '0100beef' && magicHex !== '0200beef') {
314
+ return { valid: false, error: `Invalid magic bytes: ${magicHex}` };
315
+ }
316
+
317
+ const version = magicHex === '0100beef' ? 1 : 2;
318
+
319
+ // Parse BEEF
320
+ const parsed = Beef.fromBinary(beef);
321
+
322
+ if (!parsed.txs || parsed.txs.length === 0) {
323
+ return { valid: false, error: 'BEEF contains no transactions' };
324
+ }
325
+
326
+ // Check for merkle proofs
327
+ const hasProofs = parsed.bumps && parsed.bumps.length > 0;
328
+
329
+ return {
330
+ valid: true,
331
+ version,
332
+ txCount: parsed.txs.length,
333
+ hasProofs,
334
+ };
335
+ } catch (e) {
336
+ return { valid: false, error: e instanceof Error ? e.message : 'Unknown error' };
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Validate BEEF has proper ancestry chain.
342
+ */
343
+ export function validateBeefAncestry(beef: number[]): {
344
+ valid: boolean;
345
+ error?: string;
346
+ chain?: string[];
347
+ } {
348
+ try {
349
+ const parsed = Beef.fromBinary(beef);
350
+ const chain: string[] = [];
351
+
352
+ for (const btx of parsed.txs) {
353
+ const txid = (btx as { txid?: string }).txid || btx._tx?.id('hex');
354
+ if (txid) chain.push(txid);
355
+ }
356
+
357
+ // Use Beef's built-in validation
358
+ const isValid = parsed.isValid(false);
359
+
360
+ if (!isValid) {
361
+ return { valid: false, error: 'BEEF ancestry chain is invalid', chain };
362
+ }
363
+
364
+ return { valid: true, chain };
365
+ } catch (e) {
366
+ return { valid: false, error: e instanceof Error ? e.message : 'Unknown error' };
367
+ }
368
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @a2a-bsv/core — Wallet tests
3
+ *
4
+ * These tests verify that BSVAgentWallet can be instantiated and its basic
5
+ * methods are callable. Full integration tests require a funded testnet wallet.
6
+ */
7
+
8
+ import { BSVAgentWallet } from '../core/index.js';
9
+ import { verifyPayment } from '../core/verify.js';
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import * as os from 'node:os';
13
+
14
+ // Simple test runner (no jest dependency needed)
15
+ let passed = 0;
16
+ let failed = 0;
17
+
18
+ function test(name: string, fn: () => void | Promise<void>) {
19
+ return (async () => {
20
+ try {
21
+ await fn();
22
+ console.log(` ✓ ${name}`);
23
+ passed++;
24
+ } catch (err: unknown) {
25
+ const msg = err instanceof Error ? err.message : String(err);
26
+ console.log(` ✗ ${name}`);
27
+ console.log(` ${msg}`);
28
+ failed++;
29
+ }
30
+ })();
31
+ }
32
+
33
+ function assert(condition: boolean, message: string) {
34
+ if (!condition) throw new Error(`Assertion failed: ${message}`);
35
+ }
36
+
37
+ async function run() {
38
+ console.log('@a2a-bsv/core tests\n');
39
+
40
+ // ── Verify payment (pure, no wallet needed) ──────────────────────────
41
+
42
+ await test('verifyPayment rejects empty beef', async () => {
43
+ const result = await verifyPayment({ beef: '' });
44
+ assert(!result.valid, 'should be invalid');
45
+ assert(result.errors.length > 0, 'should have errors');
46
+ });
47
+
48
+ await test('verifyPayment rejects garbage base64', async () => {
49
+ const result = await verifyPayment({ beef: 'dGhpcyBpcyBub3QgYmVlZg==' });
50
+ assert(!result.valid, 'should be invalid');
51
+ assert(result.errors.length > 0, 'should have errors');
52
+ });
53
+
54
+ await test('verifyPayment validates expectedSender format', async () => {
55
+ const result = await verifyPayment({
56
+ beef: 'dGhpcyBpcyBub3QgYmVlZg==',
57
+ expectedSender: 'not-a-pubkey',
58
+ });
59
+ assert(!result.valid, 'should be invalid');
60
+ assert(result.errors.length >= 2, 'should have both BEEF and sender errors');
61
+ const hasSenderError = result.errors.some(e => e.includes('public key'));
62
+ assert(hasSenderError, 'should flag invalid sender key');
63
+ });
64
+
65
+ // ── Wallet creation ──────────────────────────────────────────────────
66
+
67
+ await test('BSVAgentWallet.load creates a wallet and identity file', async () => {
68
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2a-test-'));
69
+ try {
70
+ const wallet = await BSVAgentWallet.load({
71
+ network: 'testnet',
72
+ storageDir: tmpDir,
73
+ });
74
+
75
+ // Identity file should exist
76
+ const identityPath = path.join(tmpDir, 'wallet-identity.json');
77
+ assert(fs.existsSync(identityPath), 'identity file should exist');
78
+
79
+ // Should have a valid identity key
80
+ const identityKey = await wallet.getIdentityKey();
81
+ assert(
82
+ /^0[23][0-9a-f]{64}$/.test(identityKey),
83
+ `identityKey should be compressed pubkey, got: ${identityKey}`,
84
+ );
85
+
86
+ // Balance should be 0 for a fresh wallet
87
+ const balance = await wallet.getBalance();
88
+ assert(balance === 0, `fresh wallet balance should be 0, got: ${balance}`);
89
+
90
+ await wallet.destroy();
91
+ } finally {
92
+ fs.rmSync(tmpDir, { recursive: true, force: true });
93
+ }
94
+ });
95
+
96
+ await test('BSVAgentWallet.load loads an existing wallet', async () => {
97
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2a-test-'));
98
+ try {
99
+ // Create first
100
+ const wallet1 = await BSVAgentWallet.load({
101
+ network: 'testnet',
102
+ storageDir: tmpDir,
103
+ });
104
+ const key1 = await wallet1.getIdentityKey();
105
+ await wallet1.destroy();
106
+
107
+ // Load
108
+ const wallet2 = await BSVAgentWallet.load({
109
+ network: 'testnet',
110
+ storageDir: tmpDir,
111
+ });
112
+ const key2 = await wallet2.getIdentityKey();
113
+ await wallet2.destroy();
114
+
115
+ assert(key1 === key2, `loaded wallet should have same identity key`);
116
+ } finally {
117
+ fs.rmSync(tmpDir, { recursive: true, force: true });
118
+ }
119
+ });
120
+
121
+ await test('BSVAgentWallet.load throws for missing wallet', async () => {
122
+ const tmpDir = path.join(os.tmpdir(), 'a2a-nonexistent-' + Date.now());
123
+ try {
124
+ await BSVAgentWallet.load({ network: 'testnet', storageDir: tmpDir });
125
+ throw new Error('Should have thrown');
126
+ } catch (err: unknown) {
127
+ const msg = err instanceof Error ? err.message : String(err);
128
+ assert(msg.includes('No wallet found'), `should say no wallet found, got: ${msg}`);
129
+ }
130
+ });
131
+
132
+ await test('createPayment rejects address (requires pubkey)', async () => {
133
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2a-test-'));
134
+ try {
135
+ const wallet = await BSVAgentWallet.load({
136
+ network: 'testnet',
137
+ storageDir: tmpDir,
138
+ });
139
+
140
+ try {
141
+ await wallet.createPayment({
142
+ to: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', // address, not pubkey
143
+ satoshis: 100,
144
+ });
145
+ throw new Error('Should have thrown');
146
+ } catch (err: unknown) {
147
+ const msg = err instanceof Error ? err.message : String(err);
148
+ assert(msg.includes('compressed public key'), `should require pubkey, got: ${msg}`);
149
+ }
150
+
151
+ await wallet.destroy();
152
+ } finally {
153
+ fs.rmSync(tmpDir, { recursive: true, force: true });
154
+ }
155
+ });
156
+
157
+ // ── Summary ──────────────────────────────────────────────────────────
158
+
159
+ console.log(`\n${passed} passed, ${failed} failed`);
160
+ if (failed > 0) process.exit(1);
161
+ }
162
+
163
+ run().catch(err => {
164
+ console.error(err);
165
+ process.exit(1);
166
+ });