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.
- package/README.md +406 -0
- package/SKILL.md +78 -0
- package/clawdbot.plugin.json +106 -0
- package/dist/cli-main.d.ts +7 -0
- package/dist/cli-main.js +192 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +14 -0
- package/dist/core/config.d.ts +11 -0
- package/dist/core/config.js +13 -0
- package/dist/core/index.d.ts +25 -0
- package/dist/core/index.js +26 -0
- package/dist/core/payment.d.ts +16 -0
- package/dist/core/payment.js +94 -0
- package/dist/core/types.d.ts +94 -0
- package/dist/core/types.js +4 -0
- package/dist/core/verify.d.ts +28 -0
- package/dist/core/verify.js +104 -0
- package/dist/core/wallet.d.ts +99 -0
- package/dist/core/wallet.js +219 -0
- package/dist/scripts/baemail/commands.d.ts +64 -0
- package/dist/scripts/baemail/commands.js +258 -0
- package/dist/scripts/baemail/handler.d.ts +36 -0
- package/dist/scripts/baemail/handler.js +284 -0
- package/dist/scripts/baemail/index.d.ts +5 -0
- package/dist/scripts/baemail/index.js +5 -0
- package/dist/scripts/config.d.ts +48 -0
- package/dist/scripts/config.js +68 -0
- package/dist/scripts/index.d.ts +7 -0
- package/dist/scripts/index.js +7 -0
- package/dist/scripts/messaging/connect.d.ts +8 -0
- package/dist/scripts/messaging/connect.js +114 -0
- package/dist/scripts/messaging/handlers.d.ts +21 -0
- package/dist/scripts/messaging/handlers.js +334 -0
- package/dist/scripts/messaging/inbox.d.ts +11 -0
- package/dist/scripts/messaging/inbox.js +51 -0
- package/dist/scripts/messaging/index.d.ts +8 -0
- package/dist/scripts/messaging/index.js +8 -0
- package/dist/scripts/messaging/poll.d.ts +7 -0
- package/dist/scripts/messaging/poll.js +52 -0
- package/dist/scripts/messaging/send.d.ts +7 -0
- package/dist/scripts/messaging/send.js +43 -0
- package/dist/scripts/output.d.ts +12 -0
- package/dist/scripts/output.js +19 -0
- package/dist/scripts/overlay/discover.d.ts +7 -0
- package/dist/scripts/overlay/discover.js +72 -0
- package/dist/scripts/overlay/index.d.ts +7 -0
- package/dist/scripts/overlay/index.js +7 -0
- package/dist/scripts/overlay/registration.d.ts +19 -0
- package/dist/scripts/overlay/registration.js +176 -0
- package/dist/scripts/overlay/services.d.ts +29 -0
- package/dist/scripts/overlay/services.js +167 -0
- package/dist/scripts/overlay/transaction.d.ts +42 -0
- package/dist/scripts/overlay/transaction.js +103 -0
- package/dist/scripts/payment/build.d.ts +24 -0
- package/dist/scripts/payment/build.js +54 -0
- package/dist/scripts/payment/commands.d.ts +15 -0
- package/dist/scripts/payment/commands.js +73 -0
- package/dist/scripts/payment/index.d.ts +6 -0
- package/dist/scripts/payment/index.js +6 -0
- package/dist/scripts/payment/types.d.ts +56 -0
- package/dist/scripts/payment/types.js +4 -0
- package/dist/scripts/services/index.d.ts +6 -0
- package/dist/scripts/services/index.js +6 -0
- package/dist/scripts/services/queue.d.ts +11 -0
- package/dist/scripts/services/queue.js +28 -0
- package/dist/scripts/services/request.d.ts +7 -0
- package/dist/scripts/services/request.js +82 -0
- package/dist/scripts/services/respond.d.ts +11 -0
- package/dist/scripts/services/respond.js +132 -0
- package/dist/scripts/types.d.ts +107 -0
- package/dist/scripts/types.js +4 -0
- package/dist/scripts/utils/index.d.ts +6 -0
- package/dist/scripts/utils/index.js +6 -0
- package/dist/scripts/utils/merkle.d.ts +12 -0
- package/dist/scripts/utils/merkle.js +47 -0
- package/dist/scripts/utils/storage.d.ts +66 -0
- package/dist/scripts/utils/storage.js +211 -0
- package/dist/scripts/utils/woc.d.ts +26 -0
- package/dist/scripts/utils/woc.js +91 -0
- package/dist/scripts/wallet/balance.d.ts +22 -0
- package/dist/scripts/wallet/balance.js +240 -0
- package/dist/scripts/wallet/identity.d.ts +70 -0
- package/dist/scripts/wallet/identity.js +151 -0
- package/dist/scripts/wallet/index.d.ts +6 -0
- package/dist/scripts/wallet/index.js +6 -0
- package/dist/scripts/wallet/setup.d.ts +15 -0
- package/dist/scripts/wallet/setup.js +105 -0
- package/dist/scripts/x-verification/commands.d.ts +27 -0
- package/dist/scripts/x-verification/commands.js +222 -0
- package/dist/scripts/x-verification/index.d.ts +4 -0
- package/dist/scripts/x-verification/index.js +4 -0
- package/dist/services/built-in/api-proxy/index.d.ts +6 -0
- package/dist/services/built-in/api-proxy/index.js +23 -0
- package/dist/services/built-in/code-develop/index.d.ts +6 -0
- package/dist/services/built-in/code-develop/index.js +23 -0
- package/dist/services/built-in/code-review/index.d.ts +10 -0
- package/dist/services/built-in/code-review/index.js +51 -0
- package/dist/services/built-in/image-analysis/index.d.ts +6 -0
- package/dist/services/built-in/image-analysis/index.js +33 -0
- package/dist/services/built-in/memory-store/index.d.ts +6 -0
- package/dist/services/built-in/memory-store/index.js +22 -0
- package/dist/services/built-in/roulette/index.d.ts +6 -0
- package/dist/services/built-in/roulette/index.js +27 -0
- package/dist/services/built-in/summarize/index.d.ts +6 -0
- package/dist/services/built-in/summarize/index.js +21 -0
- package/dist/services/built-in/tell-joke/handler.d.ts +7 -0
- package/dist/services/built-in/tell-joke/handler.js +122 -0
- package/dist/services/built-in/tell-joke/index.d.ts +9 -0
- package/dist/services/built-in/tell-joke/index.js +31 -0
- package/dist/services/built-in/translate/index.d.ts +6 -0
- package/dist/services/built-in/translate/index.js +21 -0
- package/dist/services/built-in/web-research/index.d.ts +9 -0
- package/dist/services/built-in/web-research/index.js +51 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.js +14 -0
- package/dist/services/loader.d.ts +77 -0
- package/dist/services/loader.js +292 -0
- package/dist/services/manager.d.ts +86 -0
- package/dist/services/manager.js +255 -0
- package/dist/services/registry.d.ts +98 -0
- package/dist/services/registry.js +204 -0
- package/dist/services/types.d.ts +230 -0
- package/dist/services/types.js +30 -0
- package/dist/test/cli.test.d.ts +7 -0
- package/dist/test/cli.test.js +329 -0
- package/dist/test/comprehensive-overlay.test.d.ts +13 -0
- package/dist/test/comprehensive-overlay.test.js +593 -0
- package/dist/test/key-derivation.test.d.ts +12 -0
- package/dist/test/key-derivation.test.js +86 -0
- package/dist/test/overlay-submit.test.d.ts +10 -0
- package/dist/test/overlay-submit.test.js +460 -0
- package/dist/test/request-response-flow.test.d.ts +5 -0
- package/dist/test/request-response-flow.test.js +209 -0
- package/dist/test/service-system.test.d.ts +5 -0
- package/dist/test/service-system.test.js +190 -0
- package/dist/test/utils/server-logic.d.ts +98 -0
- package/dist/test/utils/server-logic.js +286 -0
- package/dist/test/wallet.test.d.ts +7 -0
- package/dist/test/wallet.test.js +146 -0
- package/index.ts +1965 -0
- package/openclaw.plugin.json +106 -0
- package/package.json +73 -0
- package/src/cli-main.ts +230 -0
- package/src/cli.ts +16 -0
- package/src/core/README.md +246 -0
- package/src/core/config.ts +21 -0
- package/src/core/index.ts +42 -0
- package/src/core/payment.ts +111 -0
- package/src/core/types.ts +102 -0
- package/src/core/verify.ts +119 -0
- package/src/core/wallet.ts +282 -0
- package/src/scripts/baemail/commands.ts +326 -0
- package/src/scripts/baemail/handler.ts +338 -0
- package/src/scripts/baemail/index.ts +6 -0
- package/src/scripts/config.ts +81 -0
- package/src/scripts/index.ts +8 -0
- package/src/scripts/messaging/connect.ts +121 -0
- package/src/scripts/messaging/handlers.ts +394 -0
- package/src/scripts/messaging/inbox.ts +64 -0
- package/src/scripts/messaging/index.ts +9 -0
- package/src/scripts/messaging/poll.ts +59 -0
- package/src/scripts/messaging/send.ts +54 -0
- package/src/scripts/output.ts +21 -0
- package/src/scripts/overlay/discover.ts +81 -0
- package/src/scripts/overlay/index.ts +8 -0
- package/src/scripts/overlay/registration.ts +199 -0
- package/src/scripts/overlay/services.ts +199 -0
- package/src/scripts/overlay/transaction.ts +124 -0
- package/src/scripts/payment/build.ts +65 -0
- package/src/scripts/payment/commands.ts +92 -0
- package/src/scripts/payment/index.ts +7 -0
- package/src/scripts/payment/types.ts +62 -0
- package/src/scripts/services/index.ts +7 -0
- package/src/scripts/services/queue.ts +35 -0
- package/src/scripts/services/request.ts +98 -0
- package/src/scripts/services/respond.ts +149 -0
- package/src/scripts/types.ts +121 -0
- package/src/scripts/utils/index.ts +7 -0
- package/src/scripts/utils/merkle.ts +57 -0
- package/src/scripts/utils/storage.ts +231 -0
- package/src/scripts/utils/woc.ts +106 -0
- package/src/scripts/wallet/balance.ts +277 -0
- package/src/scripts/wallet/identity.ts +203 -0
- package/src/scripts/wallet/index.ts +7 -0
- package/src/scripts/wallet/setup.ts +121 -0
- package/src/scripts/x-verification/commands.ts +256 -0
- package/src/scripts/x-verification/index.ts +5 -0
- package/src/services/built-in/api-proxy/index.ts +26 -0
- package/src/services/built-in/api-proxy/prompt.md +26 -0
- package/src/services/built-in/code-develop/index.ts +26 -0
- package/src/services/built-in/code-develop/prompt.md +35 -0
- package/src/services/built-in/code-review/index.ts +54 -0
- package/src/services/built-in/code-review/prompt.md +105 -0
- package/src/services/built-in/image-analysis/index.ts +36 -0
- package/src/services/built-in/image-analysis/prompt.md +42 -0
- package/src/services/built-in/memory-store/index.ts +25 -0
- package/src/services/built-in/memory-store/prompt.md +45 -0
- package/src/services/built-in/roulette/index.ts +30 -0
- package/src/services/built-in/roulette/prompt.md +35 -0
- package/src/services/built-in/summarize/index.ts +24 -0
- package/src/services/built-in/summarize/prompt.md +27 -0
- package/src/services/built-in/tell-joke/handler.ts +134 -0
- package/src/services/built-in/tell-joke/index.ts +34 -0
- package/src/services/built-in/tell-joke/prompt.md +59 -0
- package/src/services/built-in/translate/index.ts +24 -0
- package/src/services/built-in/translate/prompt.md +23 -0
- package/src/services/built-in/web-research/index.ts +54 -0
- package/src/services/built-in/web-research/prompt.md +110 -0
- package/src/services/index.ts +16 -0
- package/src/services/loader.ts +344 -0
- package/src/services/manager.ts +304 -0
- package/src/services/registry.ts +246 -0
- package/src/services/types.ts +259 -0
- package/src/test/cli.test.ts +352 -0
- package/src/test/comprehensive-overlay.test.ts +729 -0
- package/src/test/key-derivation.test.ts +102 -0
- package/src/test/overlay-submit.test.ts +570 -0
- package/src/test/request-response-flow.test.ts +252 -0
- package/src/test/service-system.test.ts +241 -0
- package/src/test/utils/server-logic.ts +368 -0
- package/src/test/wallet.test.ts +166 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for key derivation consistency.
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: These tests ensure that transaction signing uses the correct
|
|
5
|
+
* child private key that matches the derived address.
|
|
6
|
+
*
|
|
7
|
+
* Bug history: Initially, code was deriving a child address using BRC-29
|
|
8
|
+
* but signing with the root private key, causing OP_EQUALVERIFY failures.
|
|
9
|
+
*
|
|
10
|
+
* Run: node dist/test/key-derivation.test.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { PrivateKey, Transaction, P2PKH, CachedKeyDeriver, Utils } from '@bsv/sdk';
|
|
14
|
+
import { brc29ProtocolID } from '@bsv/wallet-toolbox';
|
|
15
|
+
|
|
16
|
+
async function assert(condition: boolean, message: string) {
|
|
17
|
+
if (!condition) {
|
|
18
|
+
throw new Error(`Assertion failed: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function runTests() {
|
|
23
|
+
console.log('🧪 Running Key Derivation Tests...\n');
|
|
24
|
+
|
|
25
|
+
// Test setup
|
|
26
|
+
const rootPrivKey = PrivateKey.fromRandom();
|
|
27
|
+
const keyDeriver = new CachedKeyDeriver(rootPrivKey);
|
|
28
|
+
const derivationPrefix = Utils.toBase64(Array.from(Utils.toArray('import', 'utf8')));
|
|
29
|
+
const derivationSuffix = Utils.toBase64(Array.from(Utils.toArray('now', 'utf8')));
|
|
30
|
+
const keyString = `${derivationPrefix} ${derivationSuffix}`;
|
|
31
|
+
|
|
32
|
+
const childPrivKey = keyDeriver.derivePrivateKey(brc29ProtocolID, keyString, 'self');
|
|
33
|
+
const pubKey = keyDeriver.derivePublicKey(brc29ProtocolID, keyString, 'self', true);
|
|
34
|
+
const derivedAddress = pubKey.toAddress();
|
|
35
|
+
const hashResult = pubKey.toHash();
|
|
36
|
+
const derivedHash160 = typeof hashResult === 'string'
|
|
37
|
+
? new Uint8Array(hashResult.match(/.{2}/g)!.map(h => parseInt(h, 16)))
|
|
38
|
+
: new Uint8Array(hashResult);
|
|
39
|
+
|
|
40
|
+
// Test 1: Consistency
|
|
41
|
+
console.log('✓ Test 1: Derived keys are consistent');
|
|
42
|
+
const keyDeriver2 = new CachedKeyDeriver(rootPrivKey);
|
|
43
|
+
const childPrivKey2 = keyDeriver2.derivePrivateKey(brc29ProtocolID, keyString, 'self');
|
|
44
|
+
await assert(childPrivKey.toHex() === childPrivKey2.toHex(), 'Child keys should be identical');
|
|
45
|
+
|
|
46
|
+
// Test 2: Child key matches derived address
|
|
47
|
+
console.log('✓ Test 2: Child private key matches derived address');
|
|
48
|
+
const childPubKey = childPrivKey.toPublicKey();
|
|
49
|
+
const childAddress = childPubKey.toAddress();
|
|
50
|
+
await assert(childAddress === derivedAddress, 'Child key address should match derived address');
|
|
51
|
+
|
|
52
|
+
// Test 3: Root key does NOT match (critical!)
|
|
53
|
+
console.log('✓ Test 3: CRITICAL - Root key does NOT match derived address');
|
|
54
|
+
const rootAddress = rootPrivKey.toPublicKey().toAddress();
|
|
55
|
+
await assert(rootAddress !== derivedAddress, 'Root address must differ from derived address');
|
|
56
|
+
|
|
57
|
+
// Test 4: Transaction with child key succeeds
|
|
58
|
+
console.log('✓ Test 4: CRITICAL - Transaction signed with child key validates');
|
|
59
|
+
const fundingTx = new Transaction();
|
|
60
|
+
fundingTx.addOutput({
|
|
61
|
+
lockingScript: new P2PKH().lock(Array.from(derivedHash160)),
|
|
62
|
+
satoshis: 1000,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const spendingTx = new Transaction();
|
|
66
|
+
spendingTx.addInput({
|
|
67
|
+
sourceTransaction: fundingTx,
|
|
68
|
+
sourceOutputIndex: 0,
|
|
69
|
+
unlockingScriptTemplate: new P2PKH().unlock(childPrivKey),
|
|
70
|
+
});
|
|
71
|
+
spendingTx.addOutput({
|
|
72
|
+
lockingScript: new P2PKH().lock(Array.from(derivedHash160)),
|
|
73
|
+
satoshis: 900,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await spendingTx.sign();
|
|
77
|
+
const inputScript = spendingTx.inputs[0].unlockingScript;
|
|
78
|
+
await assert(!!inputScript, 'Unlocking script should be present');
|
|
79
|
+
await assert(Array.from(inputScript!.toBinary()).length > 0, 'Script should have content');
|
|
80
|
+
|
|
81
|
+
// Test 5: Different paths produce different addresses
|
|
82
|
+
console.log('✓ Test 5: Different derivation paths produce different addresses');
|
|
83
|
+
const path1 = Utils.toBase64(Array.from(Utils.toArray('import', 'utf8'))) + ' ' + Utils.toBase64(Array.from(Utils.toArray('now', 'utf8')));
|
|
84
|
+
const path2 = Utils.toBase64(Array.from(Utils.toArray('import', 'utf8'))) + ' ' + Utils.toBase64(Array.from(Utils.toArray('later', 'utf8')));
|
|
85
|
+
|
|
86
|
+
const pubKey1 = keyDeriver.derivePublicKey(brc29ProtocolID, path1, 'self', true);
|
|
87
|
+
const pubKey2 = keyDeriver.derivePublicKey(brc29ProtocolID, path2, 'self', true);
|
|
88
|
+
|
|
89
|
+
await assert(pubKey1.toAddress() !== pubKey2.toAddress(), 'Different paths should produce different addresses');
|
|
90
|
+
|
|
91
|
+
console.log('\n✅ All tests passed!\n');
|
|
92
|
+
console.log('Key derivation is working correctly:');
|
|
93
|
+
console.log(` Root address: ${rootAddress}`);
|
|
94
|
+
console.log(` Derived address: ${derivedAddress}`);
|
|
95
|
+
console.log(` Child key works: YES`);
|
|
96
|
+
console.log(` Root key works: NO (correctly rejected)\n`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
runTests().catch((err) => {
|
|
100
|
+
console.error('\n❌ Tests failed:', err.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for overlay /submit endpoint compatibility.
|
|
3
|
+
*
|
|
4
|
+
* These tests validate that the client constructs BEEF and payloads
|
|
5
|
+
* in the exact format expected by the clawdbot-overlay server's
|
|
6
|
+
* topic managers using PushDrop tokens.
|
|
7
|
+
*
|
|
8
|
+
* Run with: npx tsx src/test/overlay-submit.test.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Beef, Transaction, PrivateKey, P2PKH, LockingScript, OP, PushDrop } from '@bsv/sdk';
|
|
12
|
+
|
|
13
|
+
const PROTOCOL_ID = 'clawdbot-overlay-v1';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Server-side logic (using PushDrop for validation)
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
interface ClawdbotIdentityData {
|
|
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
|
+
interface ClawdbotServiceData {
|
|
31
|
+
protocol: string;
|
|
32
|
+
type: 'service';
|
|
33
|
+
identityKey: string;
|
|
34
|
+
serviceId: string;
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
pricing: { model: string; amountSats: number };
|
|
38
|
+
timestamp: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract data fields from a PushDrop script using the SDK's decode method.
|
|
43
|
+
*/
|
|
44
|
+
function extractPushDropFields(script: LockingScript): number[][] | null {
|
|
45
|
+
try {
|
|
46
|
+
const decoded = PushDrop.decode(script);
|
|
47
|
+
return decoded.fields;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse identity output using PushDrop decode and server's validation logic.
|
|
55
|
+
*/
|
|
56
|
+
function parseIdentityOutput(script: LockingScript): ClawdbotIdentityData | null {
|
|
57
|
+
const fields = extractPushDropFields(script);
|
|
58
|
+
if (!fields || fields.length < 1) return null;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const payload = JSON.parse(
|
|
62
|
+
new TextDecoder().decode(new Uint8Array(fields[0]))
|
|
63
|
+
) as ClawdbotIdentityData;
|
|
64
|
+
|
|
65
|
+
// Server validation rules
|
|
66
|
+
if (payload.protocol !== PROTOCOL_ID) return null;
|
|
67
|
+
if (payload.type !== 'identity') return null;
|
|
68
|
+
if (typeof payload.identityKey !== 'string' || !/^[0-9a-fA-F]{66}$/.test(payload.identityKey)) return null;
|
|
69
|
+
if (typeof payload.name !== 'string' || payload.name.length === 0) return null;
|
|
70
|
+
if (!Array.isArray(payload.capabilities)) return null;
|
|
71
|
+
if (typeof payload.timestamp !== 'string') return null;
|
|
72
|
+
|
|
73
|
+
return payload;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parse service output using PushDrop decode and server's validation logic.
|
|
81
|
+
*/
|
|
82
|
+
function parseServiceOutput(script: LockingScript): ClawdbotServiceData | null {
|
|
83
|
+
const fields = extractPushDropFields(script);
|
|
84
|
+
if (!fields || fields.length < 1) return null;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const payload = JSON.parse(
|
|
88
|
+
new TextDecoder().decode(new Uint8Array(fields[0]))
|
|
89
|
+
) as ClawdbotServiceData;
|
|
90
|
+
|
|
91
|
+
// Server validation rules
|
|
92
|
+
if (payload.protocol !== PROTOCOL_ID) return null;
|
|
93
|
+
if (payload.type !== 'service') return null;
|
|
94
|
+
if (typeof payload.identityKey !== 'string' || !/^[0-9a-fA-F]{66}$/.test(payload.identityKey)) return null;
|
|
95
|
+
if (typeof payload.serviceId !== 'string' || payload.serviceId.length === 0) return null;
|
|
96
|
+
if (typeof payload.name !== 'string' || payload.name.length === 0) return null;
|
|
97
|
+
if (!payload.pricing || typeof payload.pricing.amountSats !== 'number') return null;
|
|
98
|
+
if (typeof payload.timestamp !== 'string') return null;
|
|
99
|
+
|
|
100
|
+
return payload;
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a minimally encoded push chunk for script building.
|
|
108
|
+
*/
|
|
109
|
+
function createPushChunk(data: number[]): { op: number; data?: number[] } {
|
|
110
|
+
if (data.length === 0) {
|
|
111
|
+
return { op: 0 };
|
|
112
|
+
}
|
|
113
|
+
if (data.length === 1 && data[0] === 0) {
|
|
114
|
+
return { op: 0 };
|
|
115
|
+
}
|
|
116
|
+
if (data.length === 1 && data[0] > 0 && data[0] <= 16) {
|
|
117
|
+
return { op: 0x50 + data[0] };
|
|
118
|
+
}
|
|
119
|
+
if (data.length <= 75) {
|
|
120
|
+
return { op: data.length, data };
|
|
121
|
+
}
|
|
122
|
+
if (data.length <= 255) {
|
|
123
|
+
return { op: 0x4c, data };
|
|
124
|
+
}
|
|
125
|
+
if (data.length <= 65535) {
|
|
126
|
+
return { op: 0x4d, data };
|
|
127
|
+
}
|
|
128
|
+
return { op: 0x4e, data };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build a PushDrop-style locking script with JSON payload.
|
|
133
|
+
* Format: <pubkey> OP_CHECKSIG <jsonBytes> OP_DROP
|
|
134
|
+
*/
|
|
135
|
+
function buildPushDropScript(privKey: PrivateKey, payload: object): LockingScript {
|
|
136
|
+
const pubKey = privKey.toPublicKey();
|
|
137
|
+
const pubKeyBytes = pubKey.toDER() as number[];
|
|
138
|
+
const jsonBytes = Array.from(new TextEncoder().encode(JSON.stringify(payload)));
|
|
139
|
+
|
|
140
|
+
const chunks: Array<{ op: number; data?: number[] }> = [];
|
|
141
|
+
|
|
142
|
+
// P2PK lock: <pubkey> OP_CHECKSIG
|
|
143
|
+
chunks.push({ op: pubKeyBytes.length, data: pubKeyBytes });
|
|
144
|
+
chunks.push({ op: OP.OP_CHECKSIG });
|
|
145
|
+
|
|
146
|
+
// Data field: <jsonBytes>
|
|
147
|
+
chunks.push(createPushChunk(jsonBytes));
|
|
148
|
+
|
|
149
|
+
// OP_DROP to clean stack
|
|
150
|
+
chunks.push({ op: OP.OP_DROP });
|
|
151
|
+
|
|
152
|
+
return new LockingScript(chunks);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Simulate the server's identifyAdmissibleOutputs logic.
|
|
157
|
+
*/
|
|
158
|
+
function identifyAdmissibleOutputs(
|
|
159
|
+
beef: number[],
|
|
160
|
+
type: 'identity' | 'service'
|
|
161
|
+
): { outputsToAdmit: number[]; coinsToRetain: number[] } {
|
|
162
|
+
// Parse BEEF and get the newest (subject) transaction
|
|
163
|
+
const parsedBeef = Beef.fromBinary(beef);
|
|
164
|
+
const subjectTx = parsedBeef.txs[0]._tx;
|
|
165
|
+
if (!subjectTx) {
|
|
166
|
+
return { outputsToAdmit: [], coinsToRetain: [] };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const outputsToAdmit: number[] = [];
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < subjectTx.outputs.length; i++) {
|
|
172
|
+
const output = subjectTx.outputs[i];
|
|
173
|
+
if (output.lockingScript) {
|
|
174
|
+
const parsed = type === 'identity'
|
|
175
|
+
? parseIdentityOutput(output.lockingScript)
|
|
176
|
+
: parseServiceOutput(output.lockingScript);
|
|
177
|
+
if (parsed !== null) {
|
|
178
|
+
outputsToAdmit.push(i);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { outputsToAdmit, coinsToRetain: [] };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Test utilities
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
let testsPassed = 0;
|
|
191
|
+
let testsFailed = 0;
|
|
192
|
+
|
|
193
|
+
function assert(condition: boolean, message: string): void {
|
|
194
|
+
if (!condition) {
|
|
195
|
+
console.error(`❌ FAIL: ${message}`);
|
|
196
|
+
testsFailed++;
|
|
197
|
+
throw new Error(message);
|
|
198
|
+
}
|
|
199
|
+
console.log(`✅ PASS: ${message}`);
|
|
200
|
+
testsPassed++;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function assertThrows(fn: () => void, message: string): void {
|
|
204
|
+
try {
|
|
205
|
+
fn();
|
|
206
|
+
console.error(`❌ FAIL: ${message} (expected to throw)`);
|
|
207
|
+
testsFailed++;
|
|
208
|
+
} catch {
|
|
209
|
+
console.log(`✅ PASS: ${message}`);
|
|
210
|
+
testsPassed++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Test: BEEF format validation
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
async function testBeefFormat(): Promise<void> {
|
|
219
|
+
console.log('\n=== Test: BEEF Format Validation ===');
|
|
220
|
+
|
|
221
|
+
// Create a minimal transaction chain
|
|
222
|
+
const privKey = PrivateKey.fromRandom();
|
|
223
|
+
const pubKeyHash = privKey.toPublicKey().toHash();
|
|
224
|
+
|
|
225
|
+
// Source transaction (simulating a mined tx with merkle proof)
|
|
226
|
+
const sourceTx = new Transaction();
|
|
227
|
+
sourceTx.addOutput({
|
|
228
|
+
lockingScript: new P2PKH().lock(pubKeyHash),
|
|
229
|
+
satoshis: 10000,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Spending transaction with PushDrop output
|
|
233
|
+
const tx = new Transaction();
|
|
234
|
+
tx.addInput({
|
|
235
|
+
sourceTransaction: sourceTx,
|
|
236
|
+
sourceOutputIndex: 0,
|
|
237
|
+
unlockingScriptTemplate: new P2PKH().unlock(privKey),
|
|
238
|
+
});
|
|
239
|
+
tx.addOutput({
|
|
240
|
+
lockingScript: buildPushDropScript(privKey, { protocol: PROTOCOL_ID, type: 'identity', test: true }),
|
|
241
|
+
satoshis: 1,
|
|
242
|
+
});
|
|
243
|
+
await tx.sign();
|
|
244
|
+
|
|
245
|
+
// Build BEEF
|
|
246
|
+
const beef = new Beef();
|
|
247
|
+
beef.mergeTransaction(tx);
|
|
248
|
+
const binary = beef.toBinary();
|
|
249
|
+
|
|
250
|
+
// Validate BEEF magic bytes
|
|
251
|
+
const magic = binary.slice(0, 4);
|
|
252
|
+
const magicHex = magic.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
253
|
+
assert(
|
|
254
|
+
magicHex === '0100beef' || magicHex === '0200beef',
|
|
255
|
+
`BEEF magic bytes should be 0100beef or 0200beef, got ${magicHex}`
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Validate BEEF can be parsed
|
|
259
|
+
const parsed = Beef.fromBinary(binary);
|
|
260
|
+
assert(parsed.txs.length >= 1, `BEEF should contain at least 1 transaction, got ${parsed.txs.length}`);
|
|
261
|
+
|
|
262
|
+
// Validate the newest transaction can be found in BEEF
|
|
263
|
+
const beefTx = parsed.txs[0] as { txid?: string; _tx?: Transaction };
|
|
264
|
+
const newestTxid = beefTx.txid || beefTx._tx?.id('hex');
|
|
265
|
+
assert(newestTxid === tx.id('hex'), `Newest transaction in BEEF should match original, got ${newestTxid?.slice(0, 16)}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// Test: Identity payload validation
|
|
270
|
+
// ============================================================================
|
|
271
|
+
|
|
272
|
+
async function testIdentityPayload(): Promise<void> {
|
|
273
|
+
console.log('\n=== Test: Identity Payload Validation ===');
|
|
274
|
+
|
|
275
|
+
const privKey = PrivateKey.fromRandom();
|
|
276
|
+
const identityKey = privKey.toPublicKey().toString();
|
|
277
|
+
|
|
278
|
+
// Valid identity payload
|
|
279
|
+
const validPayload: ClawdbotIdentityData = {
|
|
280
|
+
protocol: PROTOCOL_ID,
|
|
281
|
+
type: 'identity',
|
|
282
|
+
identityKey,
|
|
283
|
+
name: 'test-agent',
|
|
284
|
+
description: 'A test agent',
|
|
285
|
+
channels: { overlay: 'https://example.com' },
|
|
286
|
+
capabilities: ['test'],
|
|
287
|
+
timestamp: new Date().toISOString(),
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const script = buildPushDropScript(privKey, validPayload);
|
|
291
|
+
const parsed = parseIdentityOutput(script);
|
|
292
|
+
|
|
293
|
+
assert(parsed !== null, 'Valid identity payload should be parsed');
|
|
294
|
+
assert(parsed!.identityKey === identityKey, 'Identity key should match');
|
|
295
|
+
assert(parsed!.name === 'test-agent', 'Name should match');
|
|
296
|
+
assert(parsed!.type === 'identity', 'Type should be identity');
|
|
297
|
+
|
|
298
|
+
// Invalid: wrong protocol
|
|
299
|
+
const wrongProtocol = { ...validPayload, protocol: 'wrong-protocol' };
|
|
300
|
+
const script2 = buildPushDropScript(privKey, wrongProtocol);
|
|
301
|
+
assert(parseIdentityOutput(script2) === null, 'Wrong protocol should be rejected');
|
|
302
|
+
|
|
303
|
+
// Invalid: wrong type
|
|
304
|
+
const wrongType = { ...validPayload, type: 'service' as const };
|
|
305
|
+
const script3 = buildPushDropScript(privKey, wrongType);
|
|
306
|
+
assert(parseIdentityOutput(script3) === null, 'Wrong type should be rejected');
|
|
307
|
+
|
|
308
|
+
// Invalid: bad identity key
|
|
309
|
+
const badKey = { ...validPayload, identityKey: 'not-a-valid-key' };
|
|
310
|
+
const script4 = buildPushDropScript(privKey, badKey);
|
|
311
|
+
assert(parseIdentityOutput(script4) === null, 'Invalid identity key should be rejected');
|
|
312
|
+
|
|
313
|
+
// Invalid: empty name
|
|
314
|
+
const emptyName = { ...validPayload, name: '' };
|
|
315
|
+
const script5 = buildPushDropScript(privKey, emptyName);
|
|
316
|
+
assert(parseIdentityOutput(script5) === null, 'Empty name should be rejected');
|
|
317
|
+
|
|
318
|
+
// Invalid: capabilities not array
|
|
319
|
+
const badCaps = { ...validPayload, capabilities: 'not-array' as unknown as string[] };
|
|
320
|
+
const script6 = buildPushDropScript(privKey, badCaps);
|
|
321
|
+
assert(parseIdentityOutput(script6) === null, 'Non-array capabilities should be rejected');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// Test: Service payload validation
|
|
326
|
+
// ============================================================================
|
|
327
|
+
|
|
328
|
+
async function testServicePayload(): Promise<void> {
|
|
329
|
+
console.log('\n=== Test: Service Payload Validation ===');
|
|
330
|
+
|
|
331
|
+
const privKey = PrivateKey.fromRandom();
|
|
332
|
+
const identityKey = privKey.toPublicKey().toString();
|
|
333
|
+
|
|
334
|
+
// Valid service payload
|
|
335
|
+
const validPayload: ClawdbotServiceData = {
|
|
336
|
+
protocol: PROTOCOL_ID,
|
|
337
|
+
type: 'service',
|
|
338
|
+
identityKey,
|
|
339
|
+
serviceId: 'test-service',
|
|
340
|
+
name: 'Test Service',
|
|
341
|
+
description: 'A test service',
|
|
342
|
+
pricing: { model: 'per-task', amountSats: 100 },
|
|
343
|
+
timestamp: new Date().toISOString(),
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const script = buildPushDropScript(privKey, validPayload);
|
|
347
|
+
const parsed = parseServiceOutput(script);
|
|
348
|
+
|
|
349
|
+
assert(parsed !== null, 'Valid service payload should be parsed');
|
|
350
|
+
assert(parsed!.serviceId === 'test-service', 'Service ID should match');
|
|
351
|
+
assert(parsed!.pricing.amountSats === 100, 'Price should match');
|
|
352
|
+
|
|
353
|
+
// Invalid: missing pricing
|
|
354
|
+
const noPricing = { ...validPayload, pricing: undefined as unknown as { model: string; amountSats: number } };
|
|
355
|
+
const script2 = buildPushDropScript(privKey, noPricing);
|
|
356
|
+
assert(parseServiceOutput(script2) === null, 'Missing pricing should be rejected');
|
|
357
|
+
|
|
358
|
+
// Invalid: empty serviceId
|
|
359
|
+
const emptyId = { ...validPayload, serviceId: '' };
|
|
360
|
+
const script3 = buildPushDropScript(privKey, emptyId);
|
|
361
|
+
assert(parseServiceOutput(script3) === null, 'Empty serviceId should be rejected');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// Test: Full BEEF submission simulation
|
|
366
|
+
// ============================================================================
|
|
367
|
+
|
|
368
|
+
async function testBeefSubmission(): Promise<void> {
|
|
369
|
+
console.log('\n=== Test: BEEF Submission Simulation ===');
|
|
370
|
+
|
|
371
|
+
const privKey = PrivateKey.fromRandom();
|
|
372
|
+
const pubKeyHash = privKey.toPublicKey().toHash();
|
|
373
|
+
const identityKey = privKey.toPublicKey().toString();
|
|
374
|
+
|
|
375
|
+
// Create source transaction (simulating confirmed tx)
|
|
376
|
+
const sourceTx = new Transaction();
|
|
377
|
+
sourceTx.addOutput({
|
|
378
|
+
lockingScript: new P2PKH().lock(pubKeyHash),
|
|
379
|
+
satoshis: 10000,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Valid identity registration
|
|
383
|
+
const identityPayload: ClawdbotIdentityData = {
|
|
384
|
+
protocol: PROTOCOL_ID,
|
|
385
|
+
type: 'identity',
|
|
386
|
+
identityKey,
|
|
387
|
+
name: 'test-agent',
|
|
388
|
+
description: 'Test agent for unit tests',
|
|
389
|
+
channels: { overlay: 'https://clawoverlay.com' },
|
|
390
|
+
capabilities: ['testing'],
|
|
391
|
+
timestamp: new Date().toISOString(),
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const tx = new Transaction();
|
|
395
|
+
tx.addInput({
|
|
396
|
+
sourceTransaction: sourceTx,
|
|
397
|
+
sourceOutputIndex: 0,
|
|
398
|
+
unlockingScriptTemplate: new P2PKH().unlock(privKey),
|
|
399
|
+
});
|
|
400
|
+
tx.addOutput({
|
|
401
|
+
lockingScript: buildPushDropScript(privKey, identityPayload),
|
|
402
|
+
satoshis: 1,
|
|
403
|
+
});
|
|
404
|
+
tx.addOutput({
|
|
405
|
+
lockingScript: new P2PKH().lock(pubKeyHash),
|
|
406
|
+
satoshis: 9900,
|
|
407
|
+
});
|
|
408
|
+
await tx.sign();
|
|
409
|
+
|
|
410
|
+
// Build BEEF with ancestry
|
|
411
|
+
const beef = new Beef();
|
|
412
|
+
beef.mergeTransaction(tx);
|
|
413
|
+
const beefBinary = beef.toBinary();
|
|
414
|
+
|
|
415
|
+
// Simulate server's topic manager
|
|
416
|
+
const result = identifyAdmissibleOutputs(beefBinary, 'identity');
|
|
417
|
+
|
|
418
|
+
assert(result.outputsToAdmit.length === 1, `Should admit 1 output, got ${result.outputsToAdmit.length}`);
|
|
419
|
+
assert(result.outputsToAdmit[0] === 0, 'Should admit output index 0 (PushDrop)');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ============================================================================
|
|
423
|
+
// Test: Chained transactions (stored BEEF)
|
|
424
|
+
// ============================================================================
|
|
425
|
+
|
|
426
|
+
async function testChainedBeef(): Promise<void> {
|
|
427
|
+
console.log('\n=== Test: Chained BEEF (multiple unconfirmed txs) ===');
|
|
428
|
+
|
|
429
|
+
const privKey = PrivateKey.fromRandom();
|
|
430
|
+
const pubKeyHash = privKey.toPublicKey().toHash();
|
|
431
|
+
const identityKey = privKey.toPublicKey().toString();
|
|
432
|
+
|
|
433
|
+
// Grandparent tx (simulating mined tx - would have merkle proof)
|
|
434
|
+
const grandparentTx = new Transaction();
|
|
435
|
+
grandparentTx.addOutput({
|
|
436
|
+
lockingScript: new P2PKH().lock(pubKeyHash),
|
|
437
|
+
satoshis: 100000,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Parent tx (first overlay submission - unconfirmed)
|
|
441
|
+
const parentTx = new Transaction();
|
|
442
|
+
parentTx.addInput({
|
|
443
|
+
sourceTransaction: grandparentTx,
|
|
444
|
+
sourceOutputIndex: 0,
|
|
445
|
+
unlockingScriptTemplate: new P2PKH().unlock(privKey),
|
|
446
|
+
});
|
|
447
|
+
parentTx.addOutput({
|
|
448
|
+
lockingScript: buildPushDropScript(privKey, {
|
|
449
|
+
protocol: PROTOCOL_ID,
|
|
450
|
+
type: 'identity',
|
|
451
|
+
identityKey,
|
|
452
|
+
name: 'parent-tx',
|
|
453
|
+
description: 'First registration',
|
|
454
|
+
channels: {},
|
|
455
|
+
capabilities: [],
|
|
456
|
+
timestamp: new Date().toISOString(),
|
|
457
|
+
}),
|
|
458
|
+
satoshis: 1,
|
|
459
|
+
});
|
|
460
|
+
parentTx.addOutput({
|
|
461
|
+
lockingScript: new P2PKH().lock(pubKeyHash),
|
|
462
|
+
satoshis: 99900,
|
|
463
|
+
});
|
|
464
|
+
await parentTx.sign();
|
|
465
|
+
|
|
466
|
+
// Child tx (second overlay submission - spending parent's change)
|
|
467
|
+
const childTx = new Transaction();
|
|
468
|
+
childTx.addInput({
|
|
469
|
+
sourceTransaction: parentTx,
|
|
470
|
+
sourceOutputIndex: 1, // Spend the change output
|
|
471
|
+
unlockingScriptTemplate: new P2PKH().unlock(privKey),
|
|
472
|
+
});
|
|
473
|
+
childTx.addOutput({
|
|
474
|
+
lockingScript: buildPushDropScript(privKey, {
|
|
475
|
+
protocol: PROTOCOL_ID,
|
|
476
|
+
type: 'service',
|
|
477
|
+
identityKey,
|
|
478
|
+
serviceId: 'test-svc',
|
|
479
|
+
name: 'Test Service',
|
|
480
|
+
description: 'Service from child tx',
|
|
481
|
+
pricing: { model: 'per-task', amountSats: 50 },
|
|
482
|
+
timestamp: new Date().toISOString(),
|
|
483
|
+
}),
|
|
484
|
+
satoshis: 1,
|
|
485
|
+
});
|
|
486
|
+
childTx.addOutput({
|
|
487
|
+
lockingScript: new P2PKH().lock(pubKeyHash),
|
|
488
|
+
satoshis: 99800,
|
|
489
|
+
});
|
|
490
|
+
await childTx.sign();
|
|
491
|
+
|
|
492
|
+
// Build BEEF - should include full chain
|
|
493
|
+
const beef = new Beef();
|
|
494
|
+
beef.mergeTransaction(childTx);
|
|
495
|
+
const beefBinary = beef.toBinary();
|
|
496
|
+
|
|
497
|
+
// Verify BEEF contains all transactions
|
|
498
|
+
const parsedBeef = Beef.fromBinary(beefBinary);
|
|
499
|
+
assert(parsedBeef.txs.length >= 2, `BEEF should contain at least 2 txs for chain, got ${parsedBeef.txs.length}`);
|
|
500
|
+
|
|
501
|
+
// Verify child tx is the newest in BEEF
|
|
502
|
+
const beefTx = parsedBeef.txs[0] as { txid?: string; _tx?: Transaction };
|
|
503
|
+
const newestTxid = beefTx.txid || beefTx._tx?.id('hex');
|
|
504
|
+
assert(newestTxid === childTx.id('hex'), 'Newest tx in BEEF should be the child transaction');
|
|
505
|
+
|
|
506
|
+
// Simulate server validation
|
|
507
|
+
const result = identifyAdmissibleOutputs(beefBinary, 'service');
|
|
508
|
+
assert(result.outputsToAdmit.length === 1, 'Should admit the service output');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ============================================================================
|
|
512
|
+
// Test: Invalid BEEF handling
|
|
513
|
+
// ============================================================================
|
|
514
|
+
|
|
515
|
+
async function testInvalidBeef(): Promise<void> {
|
|
516
|
+
console.log('\n=== Test: Invalid BEEF Handling ===');
|
|
517
|
+
|
|
518
|
+
// Empty BEEF
|
|
519
|
+
const emptyBeef = new Beef();
|
|
520
|
+
const emptyBinary = emptyBeef.toBinary();
|
|
521
|
+
|
|
522
|
+
assertThrows(
|
|
523
|
+
() => Transaction.fromBEEF(emptyBinary),
|
|
524
|
+
'Empty BEEF should throw when extracting transaction'
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// Malformed BEEF (random bytes)
|
|
528
|
+
const garbage = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
529
|
+
assertThrows(
|
|
530
|
+
() => Beef.fromBinary(Array.from(garbage)),
|
|
531
|
+
'Garbage bytes should throw when parsing BEEF'
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// BEEF with wrong magic
|
|
535
|
+
const wrongMagic = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF, 0, 0]);
|
|
536
|
+
assertThrows(
|
|
537
|
+
() => Beef.fromBinary(Array.from(wrongMagic)),
|
|
538
|
+
'Wrong magic bytes should throw when parsing BEEF'
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ============================================================================
|
|
543
|
+
// Main test runner
|
|
544
|
+
// ============================================================================
|
|
545
|
+
|
|
546
|
+
async function runTests(): Promise<void> {
|
|
547
|
+
console.log('Starting overlay submit tests (PushDrop format)...\n');
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
await testBeefFormat();
|
|
551
|
+
await testIdentityPayload();
|
|
552
|
+
await testServicePayload();
|
|
553
|
+
await testBeefSubmission();
|
|
554
|
+
await testChainedBeef();
|
|
555
|
+
await testInvalidBeef();
|
|
556
|
+
|
|
557
|
+
console.log(`\n========================================`);
|
|
558
|
+
console.log(`Tests completed: ${testsPassed} passed, ${testsFailed} failed`);
|
|
559
|
+
console.log(`========================================`);
|
|
560
|
+
|
|
561
|
+
if (testsFailed > 0) {
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
} catch (e) {
|
|
565
|
+
console.error('\nTest suite failed:', e);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
runTests();
|