/ CAFF_NODE_INTEGRATION_PLAN.md
CAFF_NODE_INTEGRATION_PLAN.md
1 # Caff Node Integration Plan for ComposableScan 2 3 ## ๐ฏ Goal 4 5 Integrate **Caff Node** functionality into ComposableScan to enable: 6 1. **Bi-directional Transaction Mapping**: 0x (Ethereum) โ TX~ (Espresso) 7 2. **Block State Correlation**: L2 blocks โ Espresso blocks 8 3. **Transaction Confirmation**: Espresso confirmation status for L2 transactions 9 4. **Enhanced Search**: Search by 0x transaction hash 10 11 **Key Principle**: **NO breaking changes, minimal new files, leverage existing structure** 12 13 --- 14 15 ## ๐ Understanding Caff Node 16 17 ### What is a Caff Node? 18 19 A **Caff Node** (Caffeinated Node) is a full node of an L2 chain (e.g., Rari) that reads confirmations from the Espresso Network. 20 21 ``` 22 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 23 โ Caff Node Architecture โ 24 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค 25 โ โ 26 โ L2 Transaction (Rari) โ 27 โ โ โ 28 โ โ 1. Submitted to L2 Sequencer โ 29 โ โผ โ 30 โ โโโโโโโโโโโโโโโโโโโโ โ 31 โ โ L2 Sequencer โ โ 32 โ โ โข Collects txs โ โ 33 โ โ โข Orders txs โ โ 34 โ โโโโโโโโโโฌโโโโโโโโโโ โ 35 โ โ 2. Submits to Espresso โ 36 โ โผ โ 37 โ โโโโโโโโโโโโโโโโโโโโ โ 38 โ โ Espresso Network โ โ 39 โ โ โข HotShot โ โ 40 โ โ โข Confirmation โ (6-8 seconds) โ 41 โ โ โข DA Layer โ โ 42 โ โโโโโโโโโโฌโโโโโโโโโโ โ 43 โ โ 3. Espresso confirmation โ 44 โ โผ โ 45 โ โโโโโโโโโโโโโโโโโโโโ โ 46 โ โ Caff Node โ โ 47 โ โ โข Full L2 node โ โ 48 โ โ โข Reads Espresso โ โ 49 โ โ โข Derives state โ โ 50 โ โโโโโโโโโโฌโโโโโโโโโโ โ 51 โ โ 4. RPC interface (standard Ethereum JSON-RPC) โ 52 โ โผ โ 53 โ โโโโโโโโโโโโโโโโโโโโ โ 54 โ โ Application โ โ 55 โ โ โข eth_getBlock โ โ 56 โ โ โข eth_getTx โ โ 57 โ โโโโโโโโโโโโโโโโโโโโ โ 58 โ โ 59 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 60 ``` 61 62 ### Caff Node API 63 64 **Endpoint**: `https://rari.caff.testnet.espresso.network` 65 66 **Interface**: Standard Ethereum JSON-RPC 67 68 **Key Methods**: 69 ```json 70 { 71 "jsonrpc": "2.0", 72 "method": "eth_getBlockByNumber", 73 "params": ["0x32c9dc", true], 74 "id": 1 75 } 76 77 { 78 "jsonrpc": "2.0", 79 "method": "eth_getTransactionByHash", 80 "params": ["0xaa6594b9083eea574c350021c592c3dd6c93e83d..."], 81 "id": 1 82 } 83 84 { 85 "jsonrpc": "2.0", 86 "method": "eth_blockNumber", 87 "params": [], 88 "id": 1 89 } 90 ``` 91 92 **Response Format**: 93 ```json 94 { 95 "jsonrpc": "2.0", 96 "id": 1, 97 "result": { 98 "number": "0x32c9dc", 99 "hash": "0xe5c35783a15739fd6f7a21aec9c69c78758a1512...", 100 "parentHash": "0xfdd541a33db9a46f4133b23669cdede69011cd27...", 101 "timestamp": "0x66c5d38d", 102 "transactions": [ 103 { 104 "hash": "0xaa6594b9083eea574c350021c592c3dd6c93e83d...", 105 "from": "0x...", 106 "to": "0x...", 107 "value": "0x...", 108 "blockNumber": "0x32c9dc", 109 "blockHash": "0xe5c35783a15739fd6f7a21aec9c69c78758a1512...", 110 "transactionIndex": "0x0" 111 } 112 ] 113 } 114 } 115 ``` 116 117 --- 118 119 ## ๐ Correlation Strategy: 0x โ TX~ 120 121 ### Challenge 122 123 - **Espresso Network** uses: `TX~<base64>` format (44 chars) 124 - **L2 (Rari)** uses: `0x<hex>` format (66 chars) 125 - **No direct hash mapping** between the two formats 126 127 ### Correlation Approaches 128 129 #### Approach 1: **Timestamp + Block Height Correlation** (Primary) 130 131 **Logic**: 132 ``` 133 Given: L2 transaction with timestamp T and block B 134 1. Query Espresso blocks around timestamp T (ยฑ30 minutes) 135 2. For each Espresso block: 136 - Get namespace transactions (e.g., RARI = 1380012617) 137 - Check transaction count, size, offset 138 3. Match by: 139 - Timestamp proximity (< 5 min diff) 140 - Transaction count in block 141 - Transaction size 142 - Transaction index/offset 143 ``` 144 145 **Confidence Scoring**: 146 ```typescript 147 interface CorrelationMatch { 148 espresso_tx: string; // TX~... 149 l2_tx: string; // 0x... 150 confidence: number; // 0.0 - 1.0 151 matching_factors: { 152 timestamp_diff: number; // seconds 153 block_size_match: boolean; 154 index_match: boolean; 155 namespace_match: boolean; 156 } 157 } 158 159 // Confidence calculation 160 confidence = 1.0 161 if (timestamp_diff > 60) confidence -= 0.2 // > 1 min 162 if (timestamp_diff > 180) confidence -= 0.3 // > 3 min 163 if (!block_size_match) confidence -= 0.2 164 if (!index_match) confidence -= 0.1 165 if (!namespace_match) confidence -= 0.3 166 ``` 167 168 #### Approach 2: **Block Number Mapping** (Secondary) 169 170 **Logic**: 171 ``` 172 Given: L2 block number N 173 1. Query Rari block N โ get timestamp T 174 2. Query Espresso blocks with timestamp โ T 175 3. Find Espresso block with RARI namespace transactions 176 4. Match all transactions in sequence 177 ``` 178 179 **Mapping Structure**: 180 ```typescript 181 interface BlockMapping { 182 l2_block: number; 183 l2_timestamp: number; 184 espresso_block: number; 185 espresso_timestamp: number; 186 namespace: number; 187 tx_count: number; 188 correlation_quality: 'high' | 'medium' | 'low'; 189 } 190 ``` 191 192 #### Approach 3: **Transaction Metadata Matching** (Tertiary) 193 194 **Logic**: 195 ``` 196 Match by transaction characteristics: 197 - Transaction size (bytes) 198 - Gas used 199 - Value transferred 200 - Contract interaction 201 ``` 202 203 **Limitations**: 204 - Requires detailed transaction data 205 - Multiple transactions may have similar characteristics 206 - Only usable as supporting evidence 207 208 --- 209 210 ## ๐๏ธ Implementation Plan 211 212 ### Phase 1: Add Caff Node Client (No Breaking Changes) 213 214 **New File**: `src/services/caff/client.ts` 215 216 ```typescript 217 // src/services/caff/client.ts 218 219 interface CaffNodeConfig { 220 rpcUrl: string; 221 timeout: number; 222 } 223 224 interface EthereumTransaction { 225 hash: string; // 0x... 226 blockNumber: string; // 0x... (hex) 227 blockHash: string; // 0x... 228 from: string; 229 to: string; 230 value: string; 231 gas: string; 232 gasPrice: string; 233 input: string; 234 nonce: string; 235 transactionIndex: string; 236 timestamp?: number; // Added from block 237 } 238 239 interface EthereumBlock { 240 number: string; // 0x... (hex) 241 hash: string; // 0x... 242 parentHash: string; 243 timestamp: string; // 0x... (hex) 244 transactions: EthereumTransaction[]; 245 gasUsed: string; 246 gasLimit: string; 247 size: string; 248 } 249 250 class CaffNodeClient { 251 private config: CaffNodeConfig; 252 private requestId: number = 1; 253 254 constructor(config: CaffNodeConfig) { 255 this.config = config; 256 } 257 258 private async jsonRpcCall(method: string, params: any[]): Promise<any> { 259 const response = await fetch(this.config.rpcUrl, { 260 method: 'POST', 261 headers: { 'Content-Type': 'application/json' }, 262 body: JSON.stringify({ 263 jsonrpc: '2.0', 264 method, 265 params, 266 id: this.requestId++ 267 }) 268 }); 269 270 if (!response.ok) { 271 throw new Error(`Caff Node request failed: ${response.statusText}`); 272 } 273 274 const data = await response.json(); 275 if (data.error) { 276 throw new Error(`Caff Node error: ${data.error.message}`); 277 } 278 279 return data.result; 280 } 281 282 async getBlockNumber(): Promise<number> { 283 const result = await this.jsonRpcCall('eth_blockNumber', []); 284 return parseInt(result, 16); 285 } 286 287 async getBlockByNumber(blockNumber: number | 'latest', fullTx: boolean = true): Promise<EthereumBlock> { 288 const blockParam = blockNumber === 'latest' ? 'latest' : `0x${blockNumber.toString(16)}`; 289 return await this.jsonRpcCall('eth_getBlockByNumber', [blockParam, fullTx]); 290 } 291 292 async getBlockByHash(blockHash: string, fullTx: boolean = true): Promise<EthereumBlock> { 293 return await this.jsonRpcCall('eth_getBlockByHash', [blockHash, fullTx]); 294 } 295 296 async getTransactionByHash(txHash: string): Promise<EthereumTransaction> { 297 return await this.jsonRpcCall('eth_getTransactionByHash', [txHash]); 298 } 299 300 async getTransactionReceipt(txHash: string): Promise<any> { 301 return await this.jsonRpcCall('eth_getTransactionReceipt', [txHash]); 302 } 303 } 304 305 // Singleton instance 306 let caffNodeClient: CaffNodeClient | null = null; 307 308 export function getCaffNodeClient(network: 'mainnet' | 'testnet' = 'testnet'): CaffNodeClient { 309 if (!caffNodeClient) { 310 const rpcUrl = network === 'testnet' 311 ? 'https://rari.caff.testnet.espresso.network' 312 : 'https://rari.caff.mainnet.espresso.network'; // Update when available 313 314 caffNodeClient = new CaffNodeClient({ 315 rpcUrl, 316 timeout: 30000 317 }); 318 } 319 return caffNodeClient; 320 } 321 322 export { CaffNodeClient, type EthereumTransaction, type EthereumBlock }; 323 ``` 324 325 --- 326 327 ### Phase 2: Add Correlation Service (Minimal New Code) 328 329 **New File**: `src/services/correlation/mapper.ts` 330 331 ```typescript 332 // src/services/correlation/mapper.ts 333 334 import { getCaffNodeClient, type EthereumTransaction } from '../caff/client'; 335 import { getBlock, getNamespaceTransactions } from '../api/main'; 336 import { getLatestBlockHeight } from '../api/discovery'; 337 import type { EspressoTransaction } from '@/types/espresso'; 338 339 interface CorrelationMatch { 340 espresso_tx: string; 341 espresso_block: number; 342 l2_tx: string; 343 l2_block: number; 344 confidence: number; 345 timestamp_diff: number; 346 factors: { 347 timestamp_match: boolean; 348 index_match: boolean; 349 size_match: boolean; 350 }; 351 } 352 353 interface CorrelationResult { 354 found: boolean; 355 matches: CorrelationMatch[]; 356 search_range: { 357 from_block: number; 358 to_block: number; 359 blocks_searched: number; 360 }; 361 } 362 363 /** 364 * Find Espresso transaction(s) corresponding to L2 transaction 365 */ 366 export async function correlateL2ToEspresso( 367 l2TxHash: string, 368 namespace: number = 1380012617 // Default: RARI 369 ): Promise<CorrelationResult> { 370 const caffClient = getCaffNodeClient(); 371 372 // 1. Get L2 transaction details 373 const l2Tx = await caffClient.getTransactionByHash(l2TxHash); 374 if (!l2Tx) { 375 return { found: false, matches: [], search_range: { from_block: 0, to_block: 0, blocks_searched: 0 } }; 376 } 377 378 const l2BlockNum = parseInt(l2Tx.blockNumber, 16); 379 const l2Block = await caffClient.getBlockByNumber(l2BlockNum); 380 const l2Timestamp = parseInt(l2Block.timestamp, 16); 381 382 // 2. Calculate Espresso search range (ยฑ30 minutes) 383 const timeWindow = 30 * 60; // 30 minutes in seconds 384 const searchStart = l2Timestamp - timeWindow; 385 const searchEnd = l2Timestamp + timeWindow; 386 387 // 3. Get current Espresso block height 388 const currentHeight = await getLatestBlockHeight(); 389 390 // 4. Binary search for Espresso blocks in time range 391 const espressoBlocks = await findBlocksInTimeRange(searchStart, searchEnd, currentHeight); 392 393 // 5. Search each block for namespace transactions 394 const matches: CorrelationMatch[] = []; 395 396 for (const espressoBlockHeight of espressoBlocks) { 397 const namespaceData = await getNamespaceTransactions(espressoBlockHeight, namespace); 398 if (!namespaceData || !namespaceData.transactions) continue; 399 400 // 6. Match transactions by characteristics 401 for (let i = 0; i < namespaceData.transactions.length; i++) { 402 const espressoTx = namespaceData.transactions[i]; 403 404 const timeDiff = Math.abs(l2Timestamp - (espressoTx.timestamp || 0)); 405 const indexMatch = parseInt(l2Tx.transactionIndex, 16) === i; 406 407 // Calculate confidence 408 let confidence = 1.0; 409 if (timeDiff > 60) confidence -= 0.2; 410 if (timeDiff > 180) confidence -= 0.3; 411 if (!indexMatch) confidence -= 0.1; 412 413 if (confidence > 0.5) { 414 matches.push({ 415 espresso_tx: espressoTx.hash, 416 espresso_block: espressoBlockHeight, 417 l2_tx: l2TxHash, 418 l2_block: l2BlockNum, 419 confidence, 420 timestamp_diff: timeDiff, 421 factors: { 422 timestamp_match: timeDiff < 60, 423 index_match: indexMatch, 424 size_match: true // Placeholder 425 } 426 }); 427 } 428 } 429 } 430 431 // Sort by confidence 432 matches.sort((a, b) => b.confidence - a.confidence); 433 434 return { 435 found: matches.length > 0, 436 matches, 437 search_range: { 438 from_block: espressoBlocks[0] || 0, 439 to_block: espressoBlocks[espressoBlocks.length - 1] || 0, 440 blocks_searched: espressoBlocks.length 441 } 442 }; 443 } 444 445 /** 446 * Find Espresso blocks within a timestamp range 447 * Uses binary search for efficiency 448 */ 449 async function findBlocksInTimeRange( 450 startTime: number, 451 endTime: number, 452 maxHeight: number 453 ): Promise<number[]> { 454 const blocks: number[] = []; 455 456 // Binary search for start block 457 let low = Math.max(1, maxHeight - 10000); // Search last 10k blocks 458 let high = maxHeight; 459 460 while (low <= high) { 461 const mid = Math.floor((low + high) / 2); 462 const block = await getBlock(mid); 463 464 if (!block) break; 465 466 if (block.timestamp < startTime) { 467 low = mid + 1; 468 } else if (block.timestamp > endTime) { 469 high = mid - 1; 470 } else { 471 // Found block in range, expand search 472 blocks.push(mid); 473 474 // Search backward 475 for (let i = mid - 1; i >= low; i--) { 476 const prevBlock = await getBlock(i); 477 if (!prevBlock || prevBlock.timestamp < startTime) break; 478 blocks.unshift(i); 479 } 480 481 // Search forward 482 for (let i = mid + 1; i <= high; i++) { 483 const nextBlock = await getBlock(i); 484 if (!nextBlock || nextBlock.timestamp > endTime) break; 485 blocks.push(i); 486 } 487 488 break; 489 } 490 } 491 492 return blocks; 493 } 494 495 /** 496 * Reverse lookup: Find L2 transaction from Espresso transaction 497 */ 498 export async function correlateEspressoToL2( 499 espressoTxHash: string, 500 namespace: number = 1380012617 501 ): Promise<CorrelationResult> { 502 // Get Espresso transaction 503 const espressoTx = await getTransactionByHash(espressoTxHash); 504 if (!espressoTx) { 505 return { found: false, matches: [], search_range: { from_block: 0, to_block: 0, blocks_searched: 0 } }; 506 } 507 508 const espressoBlockHeight = espressoTx.block_height; 509 const espressoTimestamp = espressoTx.timestamp || 0; 510 511 // Get Espresso block 512 const espressoBlock = await getBlock(espressoBlockHeight); 513 if (!espressoBlock) { 514 return { found: false, matches: [], search_range: { from_block: 0, to_block: 0, blocks_searched: 0 } }; 515 } 516 517 // Calculate L2 search range 518 const timeWindow = 30 * 60; 519 const caffClient = getCaffNodeClient(); 520 const currentL2Block = await caffClient.getBlockNumber(); 521 522 // Search L2 blocks around the timestamp 523 const matches: CorrelationMatch[] = []; 524 const searchRange = 500; // Search ยฑ500 blocks 525 526 for (let i = Math.max(1, currentL2Block - searchRange); i <= currentL2Block + searchRange; i++) { 527 const l2Block = await caffClient.getBlockByNumber(i); 528 if (!l2Block) continue; 529 530 const l2Timestamp = parseInt(l2Block.timestamp, 16); 531 const timeDiff = Math.abs(espressoTimestamp - l2Timestamp); 532 533 if (timeDiff > timeWindow) continue; 534 535 // Check transactions 536 for (const l2Tx of l2Block.transactions) { 537 const txIndex = parseInt(l2Tx.transactionIndex, 16); 538 const indexMatch = txIndex === espressoTx.index; 539 540 let confidence = 1.0; 541 if (timeDiff > 60) confidence -= 0.2; 542 if (timeDiff > 180) confidence -= 0.3; 543 if (!indexMatch) confidence -= 0.1; 544 545 if (confidence > 0.5) { 546 matches.push({ 547 espresso_tx: espressoTxHash, 548 espresso_block: espressoBlockHeight, 549 l2_tx: l2Tx.hash, 550 l2_block: i, 551 confidence, 552 timestamp_diff: timeDiff, 553 factors: { 554 timestamp_match: timeDiff < 60, 555 index_match: indexMatch, 556 size_match: true 557 } 558 }); 559 } 560 } 561 } 562 563 matches.sort((a, b) => b.confidence - a.confidence); 564 565 return { 566 found: matches.length > 0, 567 matches, 568 search_range: { 569 from_block: Math.max(1, currentL2Block - searchRange), 570 to_block: currentL2Block + searchRange, 571 blocks_searched: searchRange * 2 572 } 573 }; 574 } 575 ``` 576 577 --- 578 579 ### Phase 3: Extend Existing Types (No New Files) 580 581 **Modify**: `src/types/espresso.ts` 582 583 ```typescript 584 // Add to existing file 585 586 export interface EthereumTransaction { 587 hash: string; // 0x... 588 blockNumber: string; // hex 589 blockHash: string; 590 from: string; 591 to: string; 592 value: string; 593 gas: string; 594 gasPrice: string; 595 timestamp?: number; 596 } 597 598 export interface CorrelationMatch { 599 espresso_tx: string; 600 espresso_block: number; 601 l2_tx: string; 602 l2_block: number; 603 confidence: number; 604 timestamp_diff: number; 605 factors: { 606 timestamp_match: boolean; 607 index_match: boolean; 608 size_match: boolean; 609 }; 610 } 611 612 export interface CorrelationResult { 613 found: boolean; 614 matches: CorrelationMatch[]; 615 search_range: { 616 from_block: number; 617 to_block: number; 618 blocks_searched: number; 619 }; 620 } 621 622 export type CaffNodeNetwork = 'mainnet' | 'testnet'; 623 ``` 624 625 --- 626 627 ### Phase 4: Extend Configuration (Minimal Changes) 628 629 **Modify**: `src/lib/config.ts` 630 631 ```typescript 632 // Add to existing config 633 634 const config = { 635 // ... existing config ... 636 637 // Caff Node endpoints 638 CAFF_NODE_TESTNET_URL: process.env.NEXT_PUBLIC_CAFF_NODE_TESTNET_URL || 'https://rari.caff.testnet.espresso.network', 639 CAFF_NODE_MAINNET_URL: process.env.NEXT_PUBLIC_CAFF_NODE_MAINNET_URL || 'https://rari.caff.mainnet.espresso.network', 640 641 // Default namespace for correlation 642 DEFAULT_CORRELATION_NAMESPACE: parseInt(process.env.NEXT_PUBLIC_DEFAULT_CORRELATION_NAMESPACE || '1380012617') 643 }; 644 645 export const getCaffNodeUrl = (network: 'mainnet' | 'testnet' = 'testnet'): string => { 646 return network === 'testnet' ? config.CAFF_NODE_TESTNET_URL : config.CAFF_NODE_MAINNET_URL; 647 }; 648 649 export const getDefaultCorrelationNamespace = (): number => { 650 return config.DEFAULT_CORRELATION_NAMESPACE; 651 }; 652 ``` 653 654 **Add to `env.example`**: 655 ```bash 656 # Caff Node Configuration 657 NEXT_PUBLIC_CAFF_NODE_TESTNET_URL=https://rari.caff.testnet.espresso.network 658 NEXT_PUBLIC_CAFF_NODE_MAINNET_URL=https://rari.caff.mainnet.espresso.network 659 660 # Default namespace for transaction correlation (RARI) 661 NEXT_PUBLIC_DEFAULT_CORRELATION_NAMESPACE=1380012617 662 ``` 663 664 --- 665 666 ### Phase 5: Extend Search (Modify Existing) 667 668 **Modify**: `src/services/api/main.ts` 669 670 ```typescript 671 // Add to existing file 672 673 import { correlateL2ToEspresso, correlateEspressoToL2 } from '../correlation/mapper'; 674 675 /** 676 * Enhanced search that supports both Espresso and L2 transaction hashes 677 */ 678 export async function searchWithCorrelation( 679 query: string, 680 type: 'block' | 'transaction' | 'namespace' | 'rollup_name' 681 ): Promise<any | null> { 682 // Try standard search first 683 const standardResult = await search(query, type); 684 685 // If transaction search and starts with 0x, try correlation 686 if (type === 'transaction' && query.startsWith('0x') && !standardResult) { 687 const correlationResult = await correlateL2ToEspresso(query); 688 689 if (correlationResult.found && correlationResult.matches.length > 0) { 690 // Return best match with correlation data 691 const bestMatch = correlationResult.matches[0]; 692 const espressoTx = await getTransactionByHash(bestMatch.espresso_tx); 693 694 return { 695 type: 'transaction', 696 data: espressoTx, 697 correlation: bestMatch 698 }; 699 } 700 } 701 702 return standardResult; 703 } 704 705 /** 706 * Get correlation info for an Espresso transaction 707 */ 708 export async function getTransactionCorrelation(espressoTxHash: string): Promise<CorrelationResult | null> { 709 try { 710 return await correlateEspressoToL2(espressoTxHash); 711 } catch (error) { 712 console.error('Correlation failed:', error); 713 return null; 714 } 715 } 716 717 /** 718 * Get L2 transaction details from 0x hash 719 */ 720 export async function getL2Transaction(l2TxHash: string): Promise<EthereumTransaction | null> { 721 try { 722 const caffClient = getCaffNodeClient(); 723 return await caffClient.getTransactionByHash(l2TxHash); 724 } catch (error) { 725 console.error('L2 transaction fetch failed:', error); 726 return null; 727 } 728 } 729 ``` 730 731 --- 732 733 ### Phase 6: UI Enhancement (Extend Components) 734 735 **Option A: Add Correlation Badge to Transaction View** 736 737 ```typescript 738 // In transaction display component 739 740 {transaction && ( 741 <div className="transaction-details"> 742 <h3>Transaction {transaction.hash}</h3> 743 744 {/* Existing fields */} 745 <div>Block: {transaction.block_height}</div> 746 <div>Namespace: {transaction.namespace}</div> 747 748 {/* NEW: Correlation info */} 749 {correlation && ( 750 <div className="correlation-info"> 751 <h4>L2 Correlation</h4> 752 <div>L2 Transaction: {correlation.l2_tx}</div> 753 <div>L2 Block: {correlation.l2_block}</div> 754 <div>Confidence: {(correlation.confidence * 100).toFixed(0)}%</div> 755 <div>Time Diff: {correlation.timestamp_diff}s</div> 756 <a href={`https://mainnet.explorer.rarichain.org/tx/${correlation.l2_tx}`} target="_blank"> 757 View on Rari Explorer 758 </a> 759 </div> 760 )} 761 </div> 762 )} 763 ``` 764 765 **Option B: Add Toggle Button** 766 767 ```typescript 768 // In search interface 769 770 <button onClick={async () => { 771 const result = await getTransactionCorrelation(transaction.hash); 772 setCorrelation(result); 773 }}> 774 ๐ Find L2 Transaction 775 </button> 776 ``` 777 778 --- 779 780 ## ๐ Testing Strategy 781 782 ### Unit Tests 783 784 ```typescript 785 // src/services/caff/__tests__/client.test.ts 786 787 describe('CaffNodeClient', () => { 788 test('should fetch block by number', async () => { 789 const client = getCaffNodeClient('testnet'); 790 const block = await client.getBlockByNumber(3327456); 791 expect(block).toBeDefined(); 792 expect(block.number).toBe('0x32c9dc'); 793 }); 794 795 test('should fetch transaction by hash', async () => { 796 const client = getCaffNodeClient('testnet'); 797 const tx = await client.getTransactionByHash('0xaa6594b9...'); 798 expect(tx).toBeDefined(); 799 expect(tx.hash).toBe('0xaa6594b9...'); 800 }); 801 }); 802 ``` 803 804 ### Integration Tests 805 806 ```typescript 807 // src/services/correlation/__tests__/mapper.test.ts 808 809 describe('Transaction Correlation', () => { 810 test('should correlate L2 to Espresso', async () => { 811 const result = await correlateL2ToEspresso('0xaa6594b9...'); 812 expect(result.found).toBe(true); 813 expect(result.matches.length).toBeGreaterThan(0); 814 expect(result.matches[0].confidence).toBeGreaterThan(0.5); 815 }); 816 817 test('should handle non-existent L2 transaction', async () => { 818 const result = await correlateL2ToEspresso('0xinvalid...'); 819 expect(result.found).toBe(false); 820 }); 821 }); 822 ``` 823 824 ### E2E Tests 825 826 ```typescript 827 // src/components/__tests__/search.e2e.test.ts 828 829 describe('Search with Correlation', () => { 830 test('should find Espresso tx from 0x hash', async () => { 831 const { getByPlaceholderText, getByText } = render(<SearchInterface />); 832 833 const searchInput = getByPlaceholderText('Search...'); 834 fireEvent.change(searchInput, { target: { value: '0xaa6594b9...' } }); 835 836 await waitFor(() => { 837 expect(getByText(/TX~/)).toBeInTheDocument(); 838 expect(getByText(/L2 Correlation/)).toBeInTheDocument(); 839 }); 840 }); 841 }); 842 ``` 843 844 --- 845 846 ## ๐ Deployment Plan 847 848 ### Phase 1: Development (Week 1-2) 849 - โ Implement CaffNodeClient 850 - โ Implement correlation service 851 - โ Add unit tests 852 - โ Update types and config 853 854 ### Phase 2: Integration (Week 3) 855 - โ Extend search functionality 856 - โ Add correlation UI components 857 - โ Integration testing 858 - โ Update documentation 859 860 ### Phase 3: Testing (Week 4) 861 - โ E2E testing 862 - โ Performance testing 863 - โ Edge case handling 864 - โ User acceptance testing 865 866 ### Phase 4: Production (Week 5) 867 - โ Deploy to staging 868 - โ Monitor performance 869 - โ Deploy to production 870 - โ User feedback 871 872 --- 873 874 ## ๐ Performance Considerations 875 876 ### Optimization Strategies 877 878 1. **Caching**: Cache L2 โ Espresso mappings 879 ```typescript 880 const correlationCache = new Map<string, CorrelationResult>(); 881 ``` 882 883 2. **Parallel Searches**: Search multiple blocks concurrently 884 ```typescript 885 await Promise.all(blocks.map(b => searchBlock(b))); 886 ``` 887 888 3. **Time Window Optimization**: Start with narrow window, expand if needed 889 ```typescript 890 let window = 5 * 60; // Start with 5 minutes 891 while (matches.length === 0 && window < 30 * 60) { 892 window *= 2; 893 // Retry with larger window 894 } 895 ``` 896 897 4. **Block Range Limits**: Limit search to reasonable block range 898 ```typescript 899 const MAX_BLOCKS_TO_SEARCH = 1000; 900 ``` 901 902 --- 903 904 ## ๐ Security Considerations 905 906 1. **Input Validation**: Validate all 0x hashes 907 ```typescript 908 if (!/^0x[0-9a-fA-F]{64}$/.test(hash)) { 909 throw new Error('Invalid transaction hash'); 910 } 911 ``` 912 913 2. **Rate Limiting**: Implement client-side rate limiting 914 ```typescript 915 const rateLimiter = new RateLimiter(10, 60000); // 10 req/min 916 ``` 917 918 3. **Error Handling**: Graceful degradation 919 ```typescript 920 try { 921 return await correlate(); 922 } catch (error) { 923 console.error(error); 924 return { found: false, matches: [], search_range: { ... } }; 925 } 926 ``` 927 928 4. **Timeout Protection**: Add request timeouts 929 ```typescript 930 const controller = new AbortController(); 931 setTimeout(() => controller.abort(), 30000); 932 ``` 933 934 --- 935 936 ## ๐ Documentation Updates 937 938 ### README.md 939 940 ```markdown 941 ## Features 942 943 - Live network stats: transactions, payload size, success rate, block height 944 - Search blocks, transactions, namespaces 945 - **NEW**: L2 โ Espresso transaction correlation 946 - **NEW**: Search by 0x (Ethereum) transaction hash 947 - WebSocket updates; responsive UI (TypeScript/Next.js/Tailwind) 948 949 ## Search Examples 950 951 - Block height: `4603571` 952 - Block hash: `BLOCK~<hash>` 953 - Transaction hash: `TX~<hash>` or raw hash 954 - **NEW**: L2 transaction hash: `0x<hash>` (66 chars) 955 - Large namespace ID: long number 956 ``` 957 958 ### API Documentation 959 960 ```markdown 961 ## Correlation API 962 963 ### correlateL2ToEspresso(l2TxHash: string, namespace?: number) 964 965 Finds Espresso transaction(s) corresponding to an L2 transaction. 966 967 **Parameters**: 968 - `l2TxHash`: L2 transaction hash (0x format) 969 - `namespace`: Namespace ID (default: 1380012617 for RARI) 970 971 **Returns**: `CorrelationResult` 972 - `found`: Whether matches were found 973 - `matches`: Array of correlation matches 974 - `search_range`: Range of blocks searched 975 976 **Example**: 977 ```typescript 978 const result = await correlateL2ToEspresso('0xaa6594b9...'); 979 if (result.found) { 980 console.log('Espresso TX:', result.matches[0].espresso_tx); 981 } 982 ``` 983 ``` 984 985 --- 986 987 ## ๐ฏ Success Metrics 988 989 ### Technical Metrics 990 - โ Correlation accuracy > 90% 991 - โ Search response time < 5s 992 - โ Cache hit rate > 70% 993 - โ Test coverage > 80% 994 995 ### User Metrics 996 - โ Successful 0x searches > 80% 997 - โ User engagement with correlation feature > 30% 998 - โ Positive feedback > 90% 999 1000 --- 1001 1002 ## ๐ฎ Future Enhancements 1003 1004 ### Phase 2 Features 1005 1. **Bi-directional Real-time Sync**: WebSocket for L2 events 1006 2. **Confidence Score Display**: Visual confidence indicators 1007 3. **Multiple Chain Support**: Expand beyond Rari 1008 4. **Historical Correlation**: Pre-compute common mappings 1009 5. **Advanced Filtering**: Filter by confidence, time range 1010 1011 ### Phase 3 Features 1012 1. **Transaction Lifecycle View**: Complete L2 โ Espresso โ L1 flow 1013 2. **Proof Verification**: Verify Espresso confirmations 1014 3. **Analytics Dashboard**: Correlation statistics 1015 4. **API Endpoints**: Expose correlation as REST API 1016 5. **SDK**: JavaScript/TypeScript SDK for developers 1017 1018 --- 1019 1020 ## ๐ Summary 1021 1022 **Integration Strategy**: 1023 - โ **2 new files**: `caff/client.ts`, `correlation/mapper.ts` 1024 - โ **3 modified files**: `types/espresso.ts`, `lib/config.ts`, `api/main.ts` 1025 - โ **No breaking changes**: All existing functionality preserved 1026 - โ **Minimal UI changes**: Optional correlation display 1027 1028 **Key Benefits**: 1029 - ๐ Bi-directional transaction mapping 1030 - ๐ฏ High correlation accuracy (timestamp + metadata) 1031 - โก Efficient binary search algorithm 1032 - ๐งช Comprehensive testing strategy 1033 - ๐ Performance optimizations (caching, parallel) 1034 - ๐ Security considerations (validation, timeouts) 1035 1036 **Timeline**: 4-5 weeks from development to production 1037 1038 --- 1039 1040 *Integration Plan v1.0* 1041 *Ready for implementation*