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,729 @@
1
+ /**
2
+ * Comprehensive Overlay Submission Tests
3
+ *
4
+ * Tests all aspects of overlay submission:
5
+ * - BEEF format and construction
6
+ * - Payload validation (identity, service, revocation)
7
+ * - PushDrop script format
8
+ * - Transaction chain validation
9
+ * - Server response handling
10
+ *
11
+ * Run with: npx tsx src/test/comprehensive-overlay.test.ts
12
+ */
13
+
14
+ import { Beef, Transaction, PrivateKey, P2PKH, LockingScript, OP } from '@bsv/sdk';
15
+ import {
16
+ PROTOCOL_ID,
17
+ extractPushDropFields,
18
+ parseIdentityOutput,
19
+ parseRevocationOutput,
20
+ parseServiceOutput,
21
+ identifyIdentityOutputs,
22
+ identifyServiceOutputs,
23
+ validateBeef,
24
+ validateBeefAncestry,
25
+ type OpenclawIdentityData,
26
+ type OpenclawServiceData,
27
+ type OpenclawIdentityRevocationData,
28
+ } from './utils/server-logic.js';
29
+
30
+ // ============================================================================
31
+ // Test Infrastructure
32
+ // ============================================================================
33
+
34
+ interface TestResult {
35
+ name: string;
36
+ passed: boolean;
37
+ error?: string;
38
+ }
39
+
40
+ const results: TestResult[] = [];
41
+
42
+ function test(name: string, fn: () => void | Promise<void>): void {
43
+ try {
44
+ const result = fn();
45
+ if (result instanceof Promise) {
46
+ result
47
+ .then(() => {
48
+ results.push({ name, passed: true });
49
+ console.log(`✅ ${name}`);
50
+ })
51
+ .catch((e) => {
52
+ results.push({ name, passed: false, error: e.message });
53
+ console.log(`❌ ${name}: ${e.message}`);
54
+ });
55
+ } else {
56
+ results.push({ name, passed: true });
57
+ console.log(`✅ ${name}`);
58
+ }
59
+ } catch (e: unknown) {
60
+ const errorMessage = e instanceof Error ? e.message : String(e);
61
+ results.push({ name, passed: false, error: errorMessage });
62
+ console.log(`❌ ${name}: ${errorMessage}`);
63
+ }
64
+ }
65
+
66
+ function assert(condition: boolean, message: string): void {
67
+ if (!condition) throw new Error(message);
68
+ }
69
+
70
+ function assertEqual<T>(actual: T, expected: T, message: string): void {
71
+ if (actual !== expected) {
72
+ throw new Error(`${message}: expected ${expected}, got ${actual}`);
73
+ }
74
+ }
75
+
76
+ // ============================================================================
77
+ // Helper Functions - PushDrop Script Building
78
+ // ============================================================================
79
+
80
+ /**
81
+ * Create a minimally encoded push chunk for script building.
82
+ */
83
+ function createPushChunk(data: number[]): { op: number; data?: number[] } {
84
+ if (data.length === 0) {
85
+ return { op: 0 };
86
+ }
87
+ if (data.length === 1 && data[0] === 0) {
88
+ return { op: 0 };
89
+ }
90
+ if (data.length === 1 && data[0] > 0 && data[0] <= 16) {
91
+ return { op: 0x50 + data[0] };
92
+ }
93
+ if (data.length <= 75) {
94
+ return { op: data.length, data };
95
+ }
96
+ if (data.length <= 255) {
97
+ return { op: 0x4c, data };
98
+ }
99
+ if (data.length <= 65535) {
100
+ return { op: 0x4d, data };
101
+ }
102
+ return { op: 0x4e, data };
103
+ }
104
+
105
+ /**
106
+ * Build a PushDrop-style locking script with JSON payload.
107
+ * Format: <pubkey> OP_CHECKSIG <jsonBytes> OP_DROP
108
+ *
109
+ * This mimics what PushDrop.lock() produces for testing purposes.
110
+ */
111
+ function buildPushDropScript(privKey: PrivateKey, payload: object): LockingScript {
112
+ const pubKey = privKey.toPublicKey();
113
+ const pubKeyBytes = pubKey.toDER() as number[];
114
+ const jsonBytes = Array.from(new TextEncoder().encode(JSON.stringify(payload)));
115
+
116
+ const chunks: Array<{ op: number; data?: number[] }> = [];
117
+
118
+ // P2PK lock: <pubkey> OP_CHECKSIG
119
+ chunks.push({ op: pubKeyBytes.length, data: pubKeyBytes });
120
+ chunks.push({ op: OP.OP_CHECKSIG });
121
+
122
+ // Data field: <jsonBytes>
123
+ chunks.push(createPushChunk(jsonBytes));
124
+
125
+ // OP_DROP to clean stack
126
+ chunks.push({ op: OP.OP_DROP });
127
+
128
+ return new LockingScript(chunks);
129
+ }
130
+
131
+ async function createSignedTransaction(
132
+ privKey: PrivateKey,
133
+ sourceTx: Transaction,
134
+ sourceVout: number,
135
+ pushDropPayload: object,
136
+ changeSats: number = 9900
137
+ ): Promise<Transaction> {
138
+ const tx = new Transaction();
139
+ const pubKeyHash = privKey.toPublicKey().toHash();
140
+
141
+ tx.addInput({
142
+ sourceTransaction: sourceTx,
143
+ sourceOutputIndex: sourceVout,
144
+ unlockingScriptTemplate: new P2PKH().unlock(privKey),
145
+ });
146
+
147
+ tx.addOutput({
148
+ lockingScript: buildPushDropScript(privKey, pushDropPayload),
149
+ satoshis: 1, // PushDrop outputs need at least 1 sat
150
+ });
151
+
152
+ if (changeSats > 0) {
153
+ tx.addOutput({
154
+ lockingScript: new P2PKH().lock(pubKeyHash),
155
+ satoshis: changeSats,
156
+ });
157
+ }
158
+
159
+ await tx.sign();
160
+ return tx;
161
+ }
162
+
163
+ function createSourceTransaction(privKey: PrivateKey, satoshis: number = 10000): Transaction {
164
+ const pubKeyHash = privKey.toPublicKey().toHash();
165
+ const tx = new Transaction();
166
+ tx.addOutput({
167
+ lockingScript: new P2PKH().lock(pubKeyHash),
168
+ satoshis,
169
+ });
170
+ return tx;
171
+ }
172
+
173
+ // ============================================================================
174
+ // BEEF Format Tests
175
+ // ============================================================================
176
+
177
+ console.log('\n=== BEEF Format Tests ===\n');
178
+
179
+ test('BEEF: valid v2 magic bytes', async () => {
180
+ const privKey = PrivateKey.fromRandom();
181
+ const sourceTx = createSourceTransaction(privKey);
182
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, { test: true });
183
+
184
+ const beef = new Beef();
185
+ beef.mergeTransaction(tx);
186
+ const binary = beef.toBinary();
187
+
188
+ const result = validateBeef(binary);
189
+ assert(result.valid, result.error || 'BEEF should be valid');
190
+ assertEqual(result.version, 2, 'Should be BEEF v2');
191
+ });
192
+
193
+ test('BEEF: contains multiple transactions', async () => {
194
+ const privKey = PrivateKey.fromRandom();
195
+ const sourceTx = createSourceTransaction(privKey);
196
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, { test: true });
197
+
198
+ const beef = new Beef();
199
+ beef.mergeTransaction(tx);
200
+ const binary = beef.toBinary();
201
+
202
+ const result = validateBeef(binary);
203
+ assert(result.txCount! >= 2, `Should have at least 2 txs, got ${result.txCount}`);
204
+ });
205
+
206
+ test('BEEF: invalid magic bytes rejected', () => {
207
+ const garbage = [0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0];
208
+ const result = validateBeef(garbage);
209
+ assert(!result.valid, 'Should reject invalid magic');
210
+ });
211
+
212
+ test('BEEF: empty BEEF rejected', () => {
213
+ const emptyBeef = new Beef();
214
+ const binary = emptyBeef.toBinary();
215
+ const result = validateBeef(binary);
216
+ assert(!result.valid, 'Should reject empty BEEF');
217
+ });
218
+
219
+ // ============================================================================
220
+ // Identity Payload Tests
221
+ // ============================================================================
222
+
223
+ console.log('\n=== Identity Payload Tests ===\n');
224
+
225
+ test('Identity: valid payload accepted', () => {
226
+ const privKey = PrivateKey.fromRandom();
227
+ const identityKey = privKey.toPublicKey().toString();
228
+ const payload: OpenclawIdentityData = {
229
+ protocol: PROTOCOL_ID,
230
+ type: 'identity',
231
+ identityKey,
232
+ name: 'test-agent',
233
+ description: 'A test agent',
234
+ channels: { overlay: 'https://example.com' },
235
+ capabilities: ['testing'],
236
+ timestamp: new Date().toISOString(),
237
+ };
238
+
239
+ const script = buildPushDropScript(privKey, payload);
240
+ const parsed = parseIdentityOutput(script);
241
+
242
+ assert(parsed !== null, 'Should parse valid identity');
243
+ assertEqual(parsed!.identityKey, identityKey, 'Identity key should match');
244
+ assertEqual(parsed!.name, 'test-agent', 'Name should match');
245
+ });
246
+
247
+ test('Identity: wrong protocol rejected', () => {
248
+ const privKey = PrivateKey.fromRandom();
249
+ const payload = {
250
+ protocol: 'wrong-protocol',
251
+ type: 'identity',
252
+ identityKey: privKey.toPublicKey().toString(),
253
+ name: 'test',
254
+ description: '',
255
+ channels: {},
256
+ capabilities: [],
257
+ timestamp: new Date().toISOString(),
258
+ };
259
+
260
+ const script = buildPushDropScript(privKey, payload);
261
+ assert(parseIdentityOutput(script) === null, 'Should reject wrong protocol');
262
+ });
263
+
264
+ test('Identity: wrong type rejected', () => {
265
+ const privKey = PrivateKey.fromRandom();
266
+ const payload = {
267
+ protocol: PROTOCOL_ID,
268
+ type: 'service', // Wrong type
269
+ identityKey: privKey.toPublicKey().toString(),
270
+ name: 'test',
271
+ description: '',
272
+ channels: {},
273
+ capabilities: [],
274
+ timestamp: new Date().toISOString(),
275
+ };
276
+
277
+ const script = buildPushDropScript(privKey, payload);
278
+ assert(parseIdentityOutput(script) === null, 'Should reject wrong type');
279
+ });
280
+
281
+ test('Identity: invalid identity key rejected', () => {
282
+ const privKey = PrivateKey.fromRandom();
283
+ const payload = {
284
+ protocol: PROTOCOL_ID,
285
+ type: 'identity',
286
+ identityKey: 'not-a-valid-key',
287
+ name: 'test',
288
+ description: '',
289
+ channels: {},
290
+ capabilities: [],
291
+ timestamp: new Date().toISOString(),
292
+ };
293
+
294
+ const script = buildPushDropScript(privKey, payload);
295
+ assert(parseIdentityOutput(script) === null, 'Should reject invalid identity key');
296
+ });
297
+
298
+ test('Identity: short identity key rejected', () => {
299
+ const privKey = PrivateKey.fromRandom();
300
+ const payload = {
301
+ protocol: PROTOCOL_ID,
302
+ type: 'identity',
303
+ identityKey: '02abcd', // Too short
304
+ name: 'test',
305
+ description: '',
306
+ channels: {},
307
+ capabilities: [],
308
+ timestamp: new Date().toISOString(),
309
+ };
310
+
311
+ const script = buildPushDropScript(privKey, payload);
312
+ assert(parseIdentityOutput(script) === null, 'Should reject short identity key');
313
+ });
314
+
315
+ test('Identity: empty name rejected', () => {
316
+ const privKey = PrivateKey.fromRandom();
317
+ const payload = {
318
+ protocol: PROTOCOL_ID,
319
+ type: 'identity',
320
+ identityKey: privKey.toPublicKey().toString(),
321
+ name: '',
322
+ description: '',
323
+ channels: {},
324
+ capabilities: [],
325
+ timestamp: new Date().toISOString(),
326
+ };
327
+
328
+ const script = buildPushDropScript(privKey, payload);
329
+ assert(parseIdentityOutput(script) === null, 'Should reject empty name');
330
+ });
331
+
332
+ test('Identity: non-array capabilities rejected', () => {
333
+ const privKey = PrivateKey.fromRandom();
334
+ const payload = {
335
+ protocol: PROTOCOL_ID,
336
+ type: 'identity',
337
+ identityKey: privKey.toPublicKey().toString(),
338
+ name: 'test',
339
+ description: '',
340
+ channels: {},
341
+ capabilities: 'not-an-array',
342
+ timestamp: new Date().toISOString(),
343
+ };
344
+
345
+ const script = buildPushDropScript(privKey, payload);
346
+ assert(parseIdentityOutput(script) === null, 'Should reject non-array capabilities');
347
+ });
348
+
349
+ // ============================================================================
350
+ // Service Payload Tests
351
+ // ============================================================================
352
+
353
+ console.log('\n=== Service Payload Tests ===\n');
354
+
355
+ test('Service: valid payload accepted', () => {
356
+ const privKey = PrivateKey.fromRandom();
357
+ const identityKey = privKey.toPublicKey().toString();
358
+ const payload: OpenclawServiceData = {
359
+ protocol: PROTOCOL_ID,
360
+ type: 'service',
361
+ identityKey,
362
+ serviceId: 'test-service',
363
+ name: 'Test Service',
364
+ description: 'A test service',
365
+ pricing: { model: 'per-task', amountSats: 100 },
366
+ timestamp: new Date().toISOString(),
367
+ };
368
+
369
+ const script = buildPushDropScript(privKey, payload);
370
+ const parsed = parseServiceOutput(script);
371
+
372
+ assert(parsed !== null, 'Should parse valid service');
373
+ assertEqual(parsed!.serviceId, 'test-service', 'Service ID should match');
374
+ assertEqual(parsed!.pricing.amountSats, 100, 'Price should match');
375
+ });
376
+
377
+ test('Service: empty serviceId rejected', () => {
378
+ const privKey = PrivateKey.fromRandom();
379
+ const payload = {
380
+ protocol: PROTOCOL_ID,
381
+ type: 'service',
382
+ identityKey: privKey.toPublicKey().toString(),
383
+ serviceId: '',
384
+ name: 'Test',
385
+ description: '',
386
+ pricing: { model: 'per-task', amountSats: 100 },
387
+ timestamp: new Date().toISOString(),
388
+ };
389
+
390
+ const script = buildPushDropScript(privKey, payload);
391
+ assert(parseServiceOutput(script) === null, 'Should reject empty serviceId');
392
+ });
393
+
394
+ test('Service: missing pricing rejected', () => {
395
+ const privKey = PrivateKey.fromRandom();
396
+ const payload = {
397
+ protocol: PROTOCOL_ID,
398
+ type: 'service',
399
+ identityKey: privKey.toPublicKey().toString(),
400
+ serviceId: 'test',
401
+ name: 'Test',
402
+ description: '',
403
+ timestamp: new Date().toISOString(),
404
+ };
405
+
406
+ const script = buildPushDropScript(privKey, payload);
407
+ assert(parseServiceOutput(script) === null, 'Should reject missing pricing');
408
+ });
409
+
410
+ test('Service: invalid pricing rejected', () => {
411
+ const privKey = PrivateKey.fromRandom();
412
+ const payload = {
413
+ protocol: PROTOCOL_ID,
414
+ type: 'service',
415
+ identityKey: privKey.toPublicKey().toString(),
416
+ serviceId: 'test',
417
+ name: 'Test',
418
+ description: '',
419
+ pricing: { model: 'per-task', amountSats: 'not-a-number' },
420
+ timestamp: new Date().toISOString(),
421
+ };
422
+
423
+ const script = buildPushDropScript(privKey, payload);
424
+ assert(parseServiceOutput(script) === null, 'Should reject invalid pricing');
425
+ });
426
+
427
+ // ============================================================================
428
+ // Revocation Payload Tests
429
+ // ============================================================================
430
+
431
+ console.log('\n=== Revocation Payload Tests ===\n');
432
+
433
+ test('Revocation: valid payload accepted', () => {
434
+ const privKey = PrivateKey.fromRandom();
435
+ const identityKey = privKey.toPublicKey().toString();
436
+ const payload: OpenclawIdentityRevocationData = {
437
+ protocol: PROTOCOL_ID,
438
+ type: 'identity-revocation',
439
+ identityKey,
440
+ reason: 'Test revocation',
441
+ timestamp: new Date().toISOString(),
442
+ };
443
+
444
+ const script = buildPushDropScript(privKey, payload);
445
+ const parsed = parseRevocationOutput(script);
446
+
447
+ assert(parsed !== null, 'Should parse valid revocation');
448
+ assertEqual(parsed!.identityKey, identityKey, 'Identity key should match');
449
+ });
450
+
451
+ test('Revocation: invalid identity key rejected', () => {
452
+ const privKey = PrivateKey.fromRandom();
453
+ const payload = {
454
+ protocol: PROTOCOL_ID,
455
+ type: 'identity-revocation',
456
+ identityKey: 'invalid',
457
+ timestamp: new Date().toISOString(),
458
+ };
459
+
460
+ const script = buildPushDropScript(privKey, payload);
461
+ assert(parseRevocationOutput(script) === null, 'Should reject invalid identity key');
462
+ });
463
+
464
+ // ============================================================================
465
+ // Topic Manager Simulation Tests
466
+ // ============================================================================
467
+
468
+ console.log('\n=== Topic Manager Simulation Tests ===\n');
469
+
470
+ test('TopicManager: identity output admitted', async () => {
471
+ const privKey = PrivateKey.fromRandom();
472
+ const identityKey = privKey.toPublicKey().toString();
473
+ const sourceTx = createSourceTransaction(privKey);
474
+
475
+ const payload: OpenclawIdentityData = {
476
+ protocol: PROTOCOL_ID,
477
+ type: 'identity',
478
+ identityKey,
479
+ name: 'test-agent',
480
+ description: 'Test',
481
+ channels: {},
482
+ capabilities: [],
483
+ timestamp: new Date().toISOString(),
484
+ };
485
+
486
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, payload);
487
+ const beef = new Beef();
488
+ beef.mergeTransaction(tx);
489
+ const binary = beef.toBinary();
490
+
491
+ const result = identifyIdentityOutputs(binary);
492
+ assertEqual(result.outputsToAdmit.length, 1, 'Should admit 1 output');
493
+ assertEqual(result.outputsToAdmit[0], 0, 'Should admit output 0');
494
+ });
495
+
496
+ test('TopicManager: revocation output admitted', async () => {
497
+ const privKey = PrivateKey.fromRandom();
498
+ const identityKey = privKey.toPublicKey().toString();
499
+ const sourceTx = createSourceTransaction(privKey);
500
+
501
+ const payload: OpenclawIdentityRevocationData = {
502
+ protocol: PROTOCOL_ID,
503
+ type: 'identity-revocation',
504
+ identityKey,
505
+ timestamp: new Date().toISOString(),
506
+ };
507
+
508
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, payload);
509
+ const beef = new Beef();
510
+ beef.mergeTransaction(tx);
511
+ const binary = beef.toBinary();
512
+
513
+ const result = identifyIdentityOutputs(binary);
514
+ assertEqual(result.outputsToAdmit.length, 1, 'Should admit revocation');
515
+ });
516
+
517
+ test('TopicManager: service output admitted', async () => {
518
+ const privKey = PrivateKey.fromRandom();
519
+ const identityKey = privKey.toPublicKey().toString();
520
+ const sourceTx = createSourceTransaction(privKey);
521
+
522
+ const payload: OpenclawServiceData = {
523
+ protocol: PROTOCOL_ID,
524
+ type: 'service',
525
+ identityKey,
526
+ serviceId: 'test-svc',
527
+ name: 'Test Service',
528
+ description: 'Test',
529
+ pricing: { model: 'per-task', amountSats: 50 },
530
+ timestamp: new Date().toISOString(),
531
+ };
532
+
533
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, payload);
534
+ const beef = new Beef();
535
+ beef.mergeTransaction(tx);
536
+ const binary = beef.toBinary();
537
+
538
+ const result = identifyServiceOutputs(binary);
539
+ assertEqual(result.outputsToAdmit.length, 1, 'Should admit service');
540
+ });
541
+
542
+ test('TopicManager: invalid payload not admitted', async () => {
543
+ const privKey = PrivateKey.fromRandom();
544
+ const sourceTx = createSourceTransaction(privKey);
545
+
546
+ // Invalid payload (wrong protocol)
547
+ const payload = {
548
+ protocol: 'wrong',
549
+ type: 'identity',
550
+ identityKey: privKey.toPublicKey().toString(),
551
+ name: 'test',
552
+ description: '',
553
+ channels: {},
554
+ capabilities: [],
555
+ timestamp: new Date().toISOString(),
556
+ };
557
+
558
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, payload);
559
+ const beef = new Beef();
560
+ beef.mergeTransaction(tx);
561
+ const binary = beef.toBinary();
562
+
563
+ const result = identifyIdentityOutputs(binary);
564
+ assertEqual(result.outputsToAdmit.length, 0, 'Should not admit invalid payload');
565
+ });
566
+
567
+ // ============================================================================
568
+ // Transaction Chain Tests
569
+ // ============================================================================
570
+
571
+ console.log('\n=== Transaction Chain Tests ===\n');
572
+
573
+ test('Chain: two unconfirmed transactions', async () => {
574
+ const privKey = PrivateKey.fromRandom();
575
+ const identityKey = privKey.toPublicKey().toString();
576
+ const pubKeyHash = privKey.toPublicKey().toHash();
577
+
578
+ // Grandparent (simulating mined)
579
+ const grandparentTx = createSourceTransaction(privKey, 100000);
580
+
581
+ // Parent (first overlay tx)
582
+ const parentPayload: OpenclawIdentityData = {
583
+ protocol: PROTOCOL_ID,
584
+ type: 'identity',
585
+ identityKey,
586
+ name: 'parent-tx',
587
+ description: 'First',
588
+ channels: {},
589
+ capabilities: [],
590
+ timestamp: new Date().toISOString(),
591
+ };
592
+ const parentTx = await createSignedTransaction(privKey, grandparentTx, 0, parentPayload, 99900);
593
+
594
+ // Child (second overlay tx, spending parent's change)
595
+ const childPayload: OpenclawServiceData = {
596
+ protocol: PROTOCOL_ID,
597
+ type: 'service',
598
+ identityKey,
599
+ serviceId: 'child-svc',
600
+ name: 'Child Service',
601
+ description: 'Second',
602
+ pricing: { model: 'per-task', amountSats: 25 },
603
+ timestamp: new Date().toISOString(),
604
+ };
605
+
606
+ const childTx = new Transaction();
607
+ childTx.addInput({
608
+ sourceTransaction: parentTx,
609
+ sourceOutputIndex: 1, // Change output
610
+ unlockingScriptTemplate: new P2PKH().unlock(privKey),
611
+ });
612
+ childTx.addOutput({
613
+ lockingScript: buildPushDropScript(privKey, childPayload),
614
+ satoshis: 1,
615
+ });
616
+ childTx.addOutput({
617
+ lockingScript: new P2PKH().lock(pubKeyHash),
618
+ satoshis: 99800,
619
+ });
620
+ await childTx.sign();
621
+
622
+ // Build BEEF
623
+ const beef = new Beef();
624
+ beef.mergeTransaction(childTx);
625
+ const binary = beef.toBinary();
626
+
627
+ const validation = validateBeef(binary);
628
+ assert(validation.valid, validation.error || 'BEEF should be valid');
629
+ assert(validation.txCount! >= 3, `Should have at least 3 txs, got ${validation.txCount}`);
630
+
631
+ // Verify service output is admitted
632
+ const result = identifyServiceOutputs(binary);
633
+ assertEqual(result.outputsToAdmit.length, 1, 'Should admit service output');
634
+ });
635
+
636
+ test('Chain: BEEF ancestry validation', async () => {
637
+ const privKey = PrivateKey.fromRandom();
638
+ const sourceTx = createSourceTransaction(privKey);
639
+ const tx = await createSignedTransaction(privKey, sourceTx, 0, { test: true });
640
+
641
+ const beef = new Beef();
642
+ beef.mergeTransaction(tx);
643
+ const binary = beef.toBinary();
644
+
645
+ const result = validateBeefAncestry(binary);
646
+ assert(result.valid, result.error || 'Ancestry should be valid');
647
+ assert(result.chain!.length >= 2, 'Chain should have at least 2 txids');
648
+ });
649
+
650
+ // ============================================================================
651
+ // PushDrop Script Format Tests
652
+ // ============================================================================
653
+
654
+ console.log('\n=== PushDrop Script Format Tests ===\n');
655
+
656
+ test('Script: PushDrop format with P2PK lock', () => {
657
+ const privKey = PrivateKey.fromRandom();
658
+ const payload = { test: 'data' };
659
+ const script = buildPushDropScript(privKey, payload);
660
+ const chunks = script.chunks;
661
+
662
+ // First chunk should be pubkey push (33 bytes)
663
+ assertEqual(chunks[0].op, 33, 'First op should push 33 bytes (pubkey)');
664
+ assert(chunks[0].data !== undefined, 'Should have pubkey data');
665
+ assertEqual(chunks[0].data!.length, 33, 'Pubkey should be 33 bytes');
666
+
667
+ // Second chunk should be OP_CHECKSIG
668
+ assertEqual(chunks[1].op, OP.OP_CHECKSIG, 'Second op should be OP_CHECKSIG');
669
+
670
+ // Should have data field and OP_DROP
671
+ assert(chunks.length >= 4, 'Should have at least 4 chunks');
672
+ });
673
+
674
+ test('Script: JSON payload extraction', () => {
675
+ const privKey = PrivateKey.fromRandom();
676
+ const payload = { foo: 'bar', num: 42 };
677
+ const script = buildPushDropScript(privKey, payload);
678
+ const fields = extractPushDropFields(script);
679
+
680
+ assert(fields !== null, 'Should extract fields');
681
+ assert(fields!.length >= 1, 'Should have at least 1 field');
682
+
683
+ const jsonStr = new TextDecoder().decode(new Uint8Array(fields![0]));
684
+ const parsed = JSON.parse(jsonStr);
685
+ assertEqual(parsed.foo, 'bar', 'Foo should match');
686
+ assertEqual(parsed.num, 42, 'Num should match');
687
+ });
688
+
689
+ test('Script: large payload handling', () => {
690
+ const privKey = PrivateKey.fromRandom();
691
+ const largePayload = {
692
+ protocol: PROTOCOL_ID,
693
+ type: 'identity',
694
+ identityKey: privKey.toPublicKey().toString(),
695
+ name: 'test',
696
+ description: 'A'.repeat(500), // Large description
697
+ channels: {},
698
+ capabilities: Array(50).fill('cap'), // Many capabilities
699
+ timestamp: new Date().toISOString(),
700
+ };
701
+
702
+ const script = buildPushDropScript(privKey, largePayload);
703
+ const fields = extractPushDropFields(script);
704
+
705
+ assert(fields !== null, 'Should handle large payload');
706
+ const parsed = JSON.parse(new TextDecoder().decode(new Uint8Array(fields![0])));
707
+ assertEqual(parsed.description.length, 500, 'Description should be preserved');
708
+ });
709
+
710
+ // ============================================================================
711
+ // Summary
712
+ // ============================================================================
713
+
714
+ // Give async tests time to complete
715
+ setTimeout(() => {
716
+ console.log('\n========================================');
717
+ const passed = results.filter(r => r.passed).length;
718
+ const failed = results.filter(r => !r.passed).length;
719
+ console.log(`Tests completed: ${passed} passed, ${failed} failed`);
720
+ console.log('========================================\n');
721
+
722
+ if (failed > 0) {
723
+ console.log('Failed tests:');
724
+ results.filter(r => !r.passed).forEach(r => {
725
+ console.log(` - ${r.name}: ${r.error}`);
726
+ });
727
+ process.exit(1);
728
+ }
729
+ }, 2000);