@weave_protocol/domere 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/PLANNING.md +231 -0
  2. package/README.md +50 -0
  3. package/dist/anchoring/ethereum.d.ts +135 -0
  4. package/dist/anchoring/ethereum.d.ts.map +1 -0
  5. package/dist/anchoring/ethereum.js +474 -0
  6. package/dist/anchoring/ethereum.js.map +1 -0
  7. package/dist/anchoring/index.d.ts +93 -0
  8. package/dist/anchoring/index.d.ts.map +1 -0
  9. package/dist/anchoring/index.js +184 -0
  10. package/dist/anchoring/index.js.map +1 -0
  11. package/dist/anchoring/merkle.d.ts +91 -0
  12. package/dist/anchoring/merkle.d.ts.map +1 -0
  13. package/dist/anchoring/merkle.js +203 -0
  14. package/dist/anchoring/merkle.js.map +1 -0
  15. package/dist/anchoring/solana.d.ts +85 -0
  16. package/dist/anchoring/solana.d.ts.map +1 -0
  17. package/dist/anchoring/solana.js +301 -0
  18. package/dist/anchoring/solana.js.map +1 -0
  19. package/dist/constants.d.ts +130 -0
  20. package/dist/constants.d.ts.map +1 -0
  21. package/dist/constants.js +536 -0
  22. package/dist/constants.js.map +1 -0
  23. package/dist/index.d.ts +13 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +37 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/language/code-analyzer.d.ts +80 -0
  28. package/dist/language/code-analyzer.d.ts.map +1 -0
  29. package/dist/language/code-analyzer.js +489 -0
  30. package/dist/language/code-analyzer.js.map +1 -0
  31. package/dist/language/detector.d.ts +53 -0
  32. package/dist/language/detector.d.ts.map +1 -0
  33. package/dist/language/detector.js +248 -0
  34. package/dist/language/detector.js.map +1 -0
  35. package/dist/language/index.d.ts +61 -0
  36. package/dist/language/index.d.ts.map +1 -0
  37. package/dist/language/index.js +109 -0
  38. package/dist/language/index.js.map +1 -0
  39. package/dist/language/nl-analyzer.d.ts +59 -0
  40. package/dist/language/nl-analyzer.d.ts.map +1 -0
  41. package/dist/language/nl-analyzer.js +350 -0
  42. package/dist/language/nl-analyzer.js.map +1 -0
  43. package/dist/language/semantic.d.ts +48 -0
  44. package/dist/language/semantic.d.ts.map +1 -0
  45. package/dist/language/semantic.js +329 -0
  46. package/dist/language/semantic.js.map +1 -0
  47. package/dist/storage/index.d.ts +6 -0
  48. package/dist/storage/index.d.ts.map +1 -0
  49. package/dist/storage/index.js +6 -0
  50. package/dist/storage/index.js.map +1 -0
  51. package/dist/storage/memory.d.ts +48 -0
  52. package/dist/storage/memory.d.ts.map +1 -0
  53. package/dist/storage/memory.js +211 -0
  54. package/dist/storage/memory.js.map +1 -0
  55. package/dist/thread/drift.d.ts +43 -0
  56. package/dist/thread/drift.d.ts.map +1 -0
  57. package/dist/thread/drift.js +248 -0
  58. package/dist/thread/drift.js.map +1 -0
  59. package/dist/thread/index.d.ts +9 -0
  60. package/dist/thread/index.d.ts.map +1 -0
  61. package/dist/thread/index.js +9 -0
  62. package/dist/thread/index.js.map +1 -0
  63. package/dist/thread/intent.d.ts +68 -0
  64. package/dist/thread/intent.d.ts.map +1 -0
  65. package/dist/thread/intent.js +333 -0
  66. package/dist/thread/intent.js.map +1 -0
  67. package/dist/thread/manager.d.ts +85 -0
  68. package/dist/thread/manager.d.ts.map +1 -0
  69. package/dist/thread/manager.js +305 -0
  70. package/dist/thread/manager.js.map +1 -0
  71. package/dist/thread/weave.d.ts +61 -0
  72. package/dist/thread/weave.d.ts.map +1 -0
  73. package/dist/thread/weave.js +158 -0
  74. package/dist/thread/weave.js.map +1 -0
  75. package/dist/tools/index.d.ts +18 -0
  76. package/dist/tools/index.d.ts.map +1 -0
  77. package/dist/tools/index.js +102 -0
  78. package/dist/tools/index.js.map +1 -0
  79. package/dist/types.d.ts +466 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +48 -0
  82. package/dist/types.js.map +1 -0
  83. package/package.json +24 -0
  84. package/src/anchoring/ethereum.ts +568 -0
  85. package/src/anchoring/index.ts +236 -0
  86. package/src/anchoring/merkle.ts +256 -0
  87. package/src/anchoring/solana.ts +370 -0
  88. package/src/constants.ts +566 -0
  89. package/src/index.ts +43 -0
  90. package/src/language/code-analyzer.ts +564 -0
  91. package/src/language/detector.ts +297 -0
  92. package/src/language/index.ts +129 -0
  93. package/src/language/nl-analyzer.ts +411 -0
  94. package/src/language/semantic.ts +385 -0
  95. package/src/storage/index.ts +6 -0
  96. package/src/storage/memory.ts +271 -0
  97. package/src/thread/drift.ts +319 -0
  98. package/src/thread/index.ts +9 -0
  99. package/src/thread/intent.ts +409 -0
  100. package/src/thread/manager.ts +414 -0
  101. package/src/thread/weave.ts +205 -0
  102. package/src/tools/index.ts +107 -0
  103. package/src/types.ts +736 -0
  104. package/tsconfig.json +19 -0
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@weave_protocol/domere",
3
+ "version": "1.0.0",
4
+ "description": "The Judge Protocol - Thread identity, intent verification, and blockchain anchoring",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": { "domere": "./dist/index.js" },
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "start": "node dist/index.js",
11
+ "dev": "tsx src/index.ts"
12
+ },
13
+ "keywords": ["ai", "security", "mcp", "blockchain", "solana", "ethereum", "intent-verification"],
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^0.5.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^20.10.0",
20
+ "typescript": "^5.3.0",
21
+ "tsx": "^4.7.0"
22
+ },
23
+ "engines": { "node": ">=18.0.0" }
24
+ }
@@ -0,0 +1,568 @@
1
+ /**
2
+ * Dōmere - The Judge Protocol
3
+ * Ethereum Anchoring Client
4
+ *
5
+ * Note: This is the client interface. The actual Ethereum contract
6
+ * should be deployed separately.
7
+ */
8
+
9
+ import type {
10
+ AnchorRequest,
11
+ AnchorResult,
12
+ AnchorVerification,
13
+ BlockchainNetwork
14
+ } from '../types.js';
15
+ import { AnchoringError } from '../types.js';
16
+ import { DEFAULT_CONFIG, PROTOCOL_FEES } from '../constants.js';
17
+
18
+ // ============================================================================
19
+ // Ethereum Client Interface
20
+ // ============================================================================
21
+
22
+ export interface EthereumConfig {
23
+ rpc_url: string;
24
+ contract_address: string;
25
+ chain_id?: number;
26
+ }
27
+
28
+ export interface EthereumAnchorData {
29
+ threadId: string; // bytes32
30
+ merkleRoot: string; // bytes32
31
+ hopCount: number; // uint256
32
+ intentHash: string; // bytes32
33
+ compliant: boolean;
34
+ }
35
+
36
+ // ============================================================================
37
+ // Ethereum Anchoring Client
38
+ // ============================================================================
39
+
40
+ export class EthereumAnchorClient {
41
+ private config: EthereumConfig;
42
+ private isTestnet: boolean;
43
+
44
+ constructor(config?: Partial<EthereumConfig>) {
45
+ this.config = {
46
+ rpc_url: config?.rpc_url ?? DEFAULT_CONFIG.anchoring.ethereum_rpc,
47
+ contract_address: config?.contract_address ?? DEFAULT_CONFIG.anchoring.ethereum_contract,
48
+ chain_id: config?.chain_id ?? 1, // Mainnet default
49
+ };
50
+ this.isTestnet = this.config.rpc_url.includes('sepolia') ||
51
+ this.config.rpc_url.includes('goerli') ||
52
+ this.config.chain_id !== 1;
53
+ }
54
+
55
+ /**
56
+ * Prepare anchor data for Ethereum
57
+ */
58
+ prepareAnchorData(request: AnchorRequest): EthereumAnchorData {
59
+ return {
60
+ threadId: this.stringToBytes32(request.thread_id),
61
+ merkleRoot: this.ensureBytes32(request.merkle_root),
62
+ hopCount: request.hop_count,
63
+ intentHash: this.ensureBytes32(request.intent_hash),
64
+ compliant: request.compliant,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Estimate gas cost
70
+ */
71
+ async estimateGas(): Promise<{
72
+ gas_limit: number;
73
+ gas_price_gwei: number;
74
+ estimated_eth: string;
75
+ protocol_fee_eth: string;
76
+ total_eth: string;
77
+ usd_estimate?: string;
78
+ }> {
79
+ // Typical gas for storing thread anchor data
80
+ const gasLimit = 80000; // Conservative estimate
81
+
82
+ // This would normally be fetched from the network
83
+ // Using placeholder values
84
+ const gasPriceGwei = 30; // Moderate gas price
85
+ const gasPriceWei = gasPriceGwei * 1e9;
86
+ const gasCostWei = gasLimit * gasPriceWei;
87
+ const gasCostEth = gasCostWei / 1e18;
88
+
89
+ // Protocol fee is 5% of gas
90
+ const protocolFeeEth = gasCostEth * (PROTOCOL_FEES.ethereum.protocol_fee_bps / 10000);
91
+
92
+ const totalEth = gasCostEth + protocolFeeEth;
93
+
94
+ return {
95
+ gas_limit: gasLimit,
96
+ gas_price_gwei: gasPriceGwei,
97
+ estimated_eth: gasCostEth.toFixed(6),
98
+ protocol_fee_eth: protocolFeeEth.toFixed(6),
99
+ total_eth: totalEth.toFixed(6),
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Create anchor transaction
105
+ *
106
+ * Returns unsigned transaction data for client-side signing.
107
+ */
108
+ async createAnchorTransaction(request: AnchorRequest): Promise<{
109
+ to: string;
110
+ data: string;
111
+ value: string;
112
+ gas_limit: number;
113
+ chain_id: number;
114
+ estimated_cost: any;
115
+ }> {
116
+ const anchorData = this.prepareAnchorData(request);
117
+ const cost = await this.estimateGas();
118
+
119
+ // Encode function call: anchorThread(bytes32,bytes32,uint256,bytes32,bool)
120
+ const functionSelector = '0x' + this.keccak256('anchorThread(bytes32,bytes32,uint256,bytes32,bool)').slice(0, 8);
121
+
122
+ const encodedData = functionSelector +
123
+ this.encodeBytes32(anchorData.threadId) +
124
+ this.encodeBytes32(anchorData.merkleRoot) +
125
+ this.encodeUint256(anchorData.hopCount) +
126
+ this.encodeBytes32(anchorData.intentHash) +
127
+ this.encodeBool(anchorData.compliant);
128
+
129
+ return {
130
+ to: this.config.contract_address,
131
+ data: encodedData,
132
+ value: '0', // Protocol fee handled by contract
133
+ gas_limit: cost.gas_limit,
134
+ chain_id: this.config.chain_id!,
135
+ estimated_cost: cost,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Submit signed transaction
141
+ */
142
+ async submitSignedTransaction(signedTransaction: string): Promise<AnchorResult> {
143
+ // This is a placeholder - real implementation would:
144
+ // 1. Submit to Ethereum RPC (eth_sendRawTransaction)
145
+ // 2. Wait for confirmation
146
+ // 3. Return result
147
+
148
+ const network: BlockchainNetwork = this.isTestnet ? 'ethereum-sepolia' : 'ethereum';
149
+
150
+ // Simulate success for testing
151
+ const mockTxHash = '0x' + Array(64).fill(0).map(() =>
152
+ Math.floor(Math.random() * 16).toString(16)
153
+ ).join('');
154
+
155
+ const mockBlockNumber = 19000000 + Math.floor(Math.random() * 100000);
156
+
157
+ return {
158
+ success: true,
159
+ network,
160
+ transaction_id: mockTxHash,
161
+ block: mockBlockNumber,
162
+ timestamp: new Date(),
163
+ network_fee: '0.002',
164
+ protocol_fee: '0.0001',
165
+ total_cost: '0.0021',
166
+ verification_url: this.getExplorerUrl(mockTxHash),
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Verify anchor on-chain
172
+ */
173
+ async verifyAnchor(
174
+ threadId: string,
175
+ expectedMerkleRoot: string
176
+ ): Promise<AnchorVerification> {
177
+ // In production, this would call the contract's verifyAnchor function
178
+
179
+ const network: BlockchainNetwork = this.isTestnet ? 'ethereum-sepolia' : 'ethereum';
180
+
181
+ return {
182
+ valid: true,
183
+ thread_id: threadId,
184
+ merkle_root: expectedMerkleRoot,
185
+ anchor: {
186
+ network,
187
+ transaction_id: 'verification_pending',
188
+ timestamp: new Date(),
189
+ verified: false,
190
+ },
191
+ verified_at: new Date(),
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Create batch certification transaction
197
+ */
198
+ async createCertificationTransaction(config: {
199
+ period_id: string;
200
+ merkle_root: string;
201
+ attestation_count: number;
202
+ violation_count: number;
203
+ period_start: Date;
204
+ period_end: Date;
205
+ }): Promise<{
206
+ to: string;
207
+ data: string;
208
+ estimated_cost: any;
209
+ }> {
210
+ // Encode function call: certifyPeriod(bytes32,bytes32,uint256,uint256,uint256,uint256)
211
+ const functionSelector = '0x' + this.keccak256(
212
+ 'certifyPeriod(bytes32,bytes32,uint256,uint256,uint256,uint256)'
213
+ ).slice(0, 8);
214
+
215
+ const encodedData = functionSelector +
216
+ this.encodeBytes32(this.stringToBytes32(config.period_id)) +
217
+ this.encodeBytes32(this.ensureBytes32(config.merkle_root)) +
218
+ this.encodeUint256(config.attestation_count) +
219
+ this.encodeUint256(config.violation_count) +
220
+ this.encodeUint256(Math.floor(config.period_start.getTime() / 1000)) +
221
+ this.encodeUint256(Math.floor(config.period_end.getTime() / 1000));
222
+
223
+ return {
224
+ to: this.config.contract_address,
225
+ data: encodedData,
226
+ estimated_cost: await this.estimateGas(),
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Get explorer URL
232
+ */
233
+ getExplorerUrl(txHash: string): string {
234
+ const base = this.isTestnet
235
+ ? 'https://sepolia.etherscan.io'
236
+ : 'https://etherscan.io';
237
+ return `${base}/tx/${txHash}`;
238
+ }
239
+
240
+ /**
241
+ * Get contract address
242
+ */
243
+ getContractAddress(): string {
244
+ return this.config.contract_address;
245
+ }
246
+
247
+ // ============================================================================
248
+ // Encoding Utilities
249
+ // ============================================================================
250
+
251
+ /**
252
+ * Convert string to bytes32 hex
253
+ */
254
+ private stringToBytes32(str: string): string {
255
+ const crypto = require('crypto');
256
+ const hash = crypto.createHash('sha256').update(str).digest('hex');
257
+ return '0x' + hash;
258
+ }
259
+
260
+ /**
261
+ * Ensure value is bytes32 format
262
+ */
263
+ private ensureBytes32(hex: string): string {
264
+ let cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
265
+ cleanHex = cleanHex.padStart(64, '0');
266
+ return '0x' + cleanHex;
267
+ }
268
+
269
+ /**
270
+ * Encode bytes32 for ABI
271
+ */
272
+ private encodeBytes32(value: string): string {
273
+ const clean = value.startsWith('0x') ? value.slice(2) : value;
274
+ return clean.padStart(64, '0');
275
+ }
276
+
277
+ /**
278
+ * Encode uint256 for ABI
279
+ */
280
+ private encodeUint256(value: number): string {
281
+ return value.toString(16).padStart(64, '0');
282
+ }
283
+
284
+ /**
285
+ * Encode bool for ABI
286
+ */
287
+ private encodeBool(value: boolean): string {
288
+ return value ? '0'.repeat(63) + '1' : '0'.repeat(64);
289
+ }
290
+
291
+ /**
292
+ * Simple keccak256 (placeholder - use ethers.js in production)
293
+ */
294
+ private keccak256(input: string): string {
295
+ // In production, use ethers.js keccak256
296
+ // This is a placeholder that returns a deterministic hash
297
+ const crypto = require('crypto');
298
+ return crypto.createHash('sha256').update(input).digest('hex');
299
+ }
300
+ }
301
+
302
+ // ============================================================================
303
+ // Ethereum Contract ABI (for reference)
304
+ // ============================================================================
305
+
306
+ export const ETHEREUM_CONTRACT_ABI = [
307
+ {
308
+ "inputs": [
309
+ { "name": "threadId", "type": "bytes32" },
310
+ { "name": "merkleRoot", "type": "bytes32" },
311
+ { "name": "hopCount", "type": "uint256" },
312
+ { "name": "intentHash", "type": "bytes32" },
313
+ { "name": "compliant", "type": "bool" }
314
+ ],
315
+ "name": "anchorThread",
316
+ "outputs": [],
317
+ "stateMutability": "payable",
318
+ "type": "function"
319
+ },
320
+ {
321
+ "inputs": [
322
+ { "name": "periodId", "type": "bytes32" },
323
+ { "name": "merkleRoot", "type": "bytes32" },
324
+ { "name": "attestationCount", "type": "uint256" },
325
+ { "name": "violationCount", "type": "uint256" },
326
+ { "name": "periodStart", "type": "uint256" },
327
+ { "name": "periodEnd", "type": "uint256" }
328
+ ],
329
+ "name": "certifyPeriod",
330
+ "outputs": [],
331
+ "stateMutability": "payable",
332
+ "type": "function"
333
+ },
334
+ {
335
+ "inputs": [
336
+ { "name": "threadId", "type": "bytes32" },
337
+ { "name": "expectedMerkleRoot", "type": "bytes32" }
338
+ ],
339
+ "name": "verifyAnchor",
340
+ "outputs": [
341
+ { "name": "valid", "type": "bool" },
342
+ { "name": "timestamp", "type": "uint256" }
343
+ ],
344
+ "stateMutability": "view",
345
+ "type": "function"
346
+ },
347
+ {
348
+ "inputs": [
349
+ { "name": "threadId", "type": "bytes32" },
350
+ { "name": "attestationHash", "type": "bytes32" },
351
+ { "name": "merkleProof", "type": "bytes32[]" }
352
+ ],
353
+ "name": "verifyAttestation",
354
+ "outputs": [{ "name": "", "type": "bool" }],
355
+ "stateMutability": "view",
356
+ "type": "function"
357
+ },
358
+ {
359
+ "anonymous": false,
360
+ "inputs": [
361
+ { "indexed": true, "name": "threadId", "type": "bytes32" },
362
+ { "indexed": false, "name": "merkleRoot", "type": "bytes32" },
363
+ { "indexed": false, "name": "anchorer", "type": "address" },
364
+ { "indexed": false, "name": "timestamp", "type": "uint256" }
365
+ ],
366
+ "name": "ThreadAnchored",
367
+ "type": "event"
368
+ },
369
+ {
370
+ "anonymous": false,
371
+ "inputs": [
372
+ { "indexed": true, "name": "periodId", "type": "bytes32" },
373
+ { "indexed": false, "name": "merkleRoot", "type": "bytes32" },
374
+ { "indexed": false, "name": "attestationCount", "type": "uint256" },
375
+ { "indexed": false, "name": "timestamp", "type": "uint256" }
376
+ ],
377
+ "name": "PeriodCertified",
378
+ "type": "event"
379
+ }
380
+ ];
381
+
382
+ // ============================================================================
383
+ // Solidity Contract (for reference)
384
+ // ============================================================================
385
+
386
+ export const ETHEREUM_CONTRACT_SOURCE = `
387
+ // SPDX-License-Identifier: MIT
388
+ pragma solidity ^0.8.19;
389
+
390
+ /**
391
+ * @title DomereProtocol
392
+ * @dev Thread anchoring and compliance certification for AI agent security
393
+ */
394
+ contract DomereProtocol {
395
+
396
+ // Protocol fee (5% of gas, calculated off-chain and sent as msg.value)
397
+ uint256 public protocolFeeBps = 500;
398
+ address public treasury;
399
+ address public owner;
400
+
401
+ struct ThreadAnchor {
402
+ bytes32 merkleRoot;
403
+ uint256 hopCount;
404
+ bytes32 intentHash;
405
+ bool compliant;
406
+ uint256 timestamp;
407
+ address anchorer;
408
+ }
409
+
410
+ struct CompliancePeriod {
411
+ bytes32 merkleRoot;
412
+ uint256 attestationCount;
413
+ uint256 violationCount;
414
+ uint256 periodStart;
415
+ uint256 periodEnd;
416
+ uint256 timestamp;
417
+ bool certified;
418
+ }
419
+
420
+ mapping(bytes32 => ThreadAnchor) public anchors;
421
+ mapping(bytes32 => CompliancePeriod) public periods;
422
+ mapping(address => bool) public authorizedAnchors;
423
+
424
+ event ThreadAnchored(
425
+ bytes32 indexed threadId,
426
+ bytes32 merkleRoot,
427
+ address anchorer,
428
+ uint256 timestamp
429
+ );
430
+
431
+ event PeriodCertified(
432
+ bytes32 indexed periodId,
433
+ bytes32 merkleRoot,
434
+ uint256 attestationCount,
435
+ uint256 timestamp
436
+ );
437
+
438
+ modifier onlyOwner() {
439
+ require(msg.sender == owner, "Not owner");
440
+ _;
441
+ }
442
+
443
+ constructor(address _treasury) {
444
+ owner = msg.sender;
445
+ treasury = _treasury;
446
+ authorizedAnchors[msg.sender] = true;
447
+ }
448
+
449
+ /**
450
+ * @dev Anchor a thread to the blockchain
451
+ */
452
+ function anchorThread(
453
+ bytes32 threadId,
454
+ bytes32 merkleRoot,
455
+ uint256 hopCount,
456
+ bytes32 intentHash,
457
+ bool compliant
458
+ ) external payable {
459
+ require(anchors[threadId].timestamp == 0, "Thread already anchored");
460
+
461
+ // Store anchor
462
+ anchors[threadId] = ThreadAnchor({
463
+ merkleRoot: merkleRoot,
464
+ hopCount: hopCount,
465
+ intentHash: intentHash,
466
+ compliant: compliant,
467
+ timestamp: block.timestamp,
468
+ anchorer: msg.sender
469
+ });
470
+
471
+ // Transfer protocol fee to treasury
472
+ if (msg.value > 0) {
473
+ payable(treasury).transfer(msg.value);
474
+ }
475
+
476
+ emit ThreadAnchored(threadId, merkleRoot, msg.sender, block.timestamp);
477
+ }
478
+
479
+ /**
480
+ * @dev Certify a compliance period
481
+ */
482
+ function certifyPeriod(
483
+ bytes32 periodId,
484
+ bytes32 merkleRoot,
485
+ uint256 attestationCount,
486
+ uint256 violationCount,
487
+ uint256 periodStart,
488
+ uint256 periodEnd
489
+ ) external payable {
490
+ require(authorizedAnchors[msg.sender], "Not authorized");
491
+
492
+ periods[periodId] = CompliancePeriod({
493
+ merkleRoot: merkleRoot,
494
+ attestationCount: attestationCount,
495
+ violationCount: violationCount,
496
+ periodStart: periodStart,
497
+ periodEnd: periodEnd,
498
+ timestamp: block.timestamp,
499
+ certified: true
500
+ });
501
+
502
+ if (msg.value > 0) {
503
+ payable(treasury).transfer(msg.value);
504
+ }
505
+
506
+ emit PeriodCertified(periodId, merkleRoot, attestationCount, block.timestamp);
507
+ }
508
+
509
+ /**
510
+ * @dev Verify a thread anchor
511
+ */
512
+ function verifyAnchor(
513
+ bytes32 threadId,
514
+ bytes32 expectedMerkleRoot
515
+ ) external view returns (bool valid, uint256 timestamp) {
516
+ ThreadAnchor memory anchor = anchors[threadId];
517
+ return (
518
+ anchor.merkleRoot == expectedMerkleRoot && anchor.timestamp > 0,
519
+ anchor.timestamp
520
+ );
521
+ }
522
+
523
+ /**
524
+ * @dev Verify an attestation using Merkle proof
525
+ */
526
+ function verifyAttestation(
527
+ bytes32 threadId,
528
+ bytes32 attestationHash,
529
+ bytes32[] calldata merkleProof
530
+ ) external view returns (bool) {
531
+ ThreadAnchor memory anchor = anchors[threadId];
532
+ require(anchor.timestamp > 0, "Thread not anchored");
533
+
534
+ bytes32 computedHash = attestationHash;
535
+ for (uint256 i = 0; i < merkleProof.length; i++) {
536
+ bytes32 proofElement = merkleProof[i];
537
+ if (computedHash <= proofElement) {
538
+ computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
539
+ } else {
540
+ computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
541
+ }
542
+ }
543
+
544
+ return computedHash == anchor.merkleRoot;
545
+ }
546
+
547
+ /**
548
+ * @dev Authorize an address to certify periods
549
+ */
550
+ function authorizeAnchor(address addr) external onlyOwner {
551
+ authorizedAnchors[addr] = true;
552
+ }
553
+
554
+ /**
555
+ * @dev Revoke authorization
556
+ */
557
+ function revokeAuthorization(address addr) external onlyOwner {
558
+ authorizedAnchors[addr] = false;
559
+ }
560
+
561
+ /**
562
+ * @dev Update treasury address
563
+ */
564
+ function setTreasury(address _treasury) external onlyOwner {
565
+ treasury = _treasury;
566
+ }
567
+ }
568
+ `;