1import * as crypto from 'crypto';
2
3export function parseRecordURI(uri: string): { did: string; collection: string; rkey: string } {
4 const match = uri.match(/^at:\/\/(did:[^\/]+)\/([^\/]+)\/(.+)$/);
5 if (!match) {
6 throw new Error('Invalid AT Protocol URI format. Expected: at://did:plc:xxx/collection/rkey');
7 }
8
9 return {
10 did: match[1]!,
11 collection: match[2]!,
12 rkey: match[3]!
13 };
14}
15
16// Recursively sort object keys for deterministic hashing
17function sortObject(obj: any): any {
18 if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
19 return obj;
20 }
21
22 const sorted: any = {};
23 Object.keys(obj)
24 .sort()
25 .forEach(key => {
26 sorted[key] = sortObject(obj[key]);
27 });
28
29 return sorted;
30}
31
32export function hashContent(content: any): string {
33 // Sort keys for deterministic hashing
34 const sortedContent = sortObject(content);
35 const jsonString = JSON.stringify(sortedContent);
36 return '0x' + crypto.createHash('sha256').update(jsonString).digest('hex');
37}
38
39export function getExplorerURL(attestationUID: string, network: string = 'sepolia'): string {
40 const explorers: Record<string, string> = {
41 'sepolia': 'https://sepolia.easscan.org',
42 'base-sepolia': 'https://base-sepolia.easscan.org',
43 'base': 'https://base.easscan.org',
44 'optimism': 'https://optimism.easscan.org',
45 'arbitrum': 'https://arbitrum.easscan.org',
46 };
47
48 const baseURL = explorers[network] || explorers['sepolia'];
49 return `${baseURL}/attestation/view/${attestationUID}`;
50}