Notarize AT Protocol records on Ethereum using EAS (experiment)

update

+3 -1
.gitignore
··· 1 1 .env 2 2 .DS_Store 3 - node_modules 3 + node_modules 4 + bun.lock 5 + dist
+1 -1
package.json
··· 25 25 "dependencies": { 26 26 "@atproto/api": "^0.12.29", 27 27 "@ethereum-attestation-service/eas-sdk": "^2.9.0", 28 - "viem": "^2.38.0" 28 + "ethers": "^6.15.0" 29 29 }, 30 30 "devDependencies": { 31 31 "@types/node": "^20.19.21",
+3 -3
pnpm-lock.yaml
··· 14 14 '@ethereum-attestation-service/eas-sdk': 15 15 specifier: ^2.9.0 16 16 version: 2.9.0(typescript@5.9.3)(zod@3.25.76) 17 - viem: 18 - specifier: ^2.38.0 19 - version: 2.38.0(typescript@5.9.3)(zod@3.25.76) 17 + ethers: 18 + specifier: ^6.15.0 19 + version: 6.15.0 20 20 devDependencies: 21 21 '@types/node': 22 22 specifier: ^20.19.21
+25 -17
src/cli.ts
··· 66 66 67 67 spinner.succeed('Initialized'); 68 68 69 - spinner.start('Fetching record from AT Protocol...'); 70 - const record = await notary.fetchRecord(recordURI); 71 - spinner.succeed('Record fetched'); 69 + spinner.start('Resolving DID to PDS...'); 70 + const { record, pds } = await notary.fetchRecord(recordURI); 71 + spinner.succeed(`Record fetched from PDS: ${pds}`); 72 72 73 73 console.log(chalk.gray('\nRecord preview:')); 74 74 console.log(chalk.gray(JSON.stringify(record.value, null, 2).substring(0, 200) + '...\n')); 75 75 76 - spinner.start('Creating attestation...'); 76 + spinner.start('Creating attestation on Ethereum...'); 77 77 const result = await notary.notarizeRecord(recordURI); 78 78 spinner.succeed('Attestation created!'); 79 79 80 80 console.log(chalk.green('\n✅ Notarization Complete!\n')); 81 81 console.log(chalk.blue('Attestation UID:'), chalk.cyan(result.attestationUID)); 82 82 console.log(chalk.blue('Record URI:'), chalk.gray(result.recordURI)); 83 + console.log(chalk.blue('CID:'), chalk.gray(result.cid)); 83 84 console.log(chalk.blue('Content Hash:'), chalk.gray(result.contentHash)); 84 - console.log(chalk.blue('Lexicon:'), chalk.gray(result.lexicon)); 85 + console.log(chalk.blue('PDS:'), chalk.gray(result.pds)); 85 86 console.log(chalk.blue('Transaction:'), chalk.gray(result.transactionHash)); 86 87 87 88 console.log(chalk.yellow('\n📋 View on EAS Explorer:')); ··· 96 97 } 97 98 }); 98 99 100 + 99 101 // Verify command 100 102 program 101 103 .command('verify <attestationUID>') ··· 119 121 console.log(chalk.green('\n✅ Attestation Valid\n')); 120 122 console.log(chalk.blue('UID:'), chalk.cyan(attestation.uid)); 121 123 console.log(chalk.blue('Record URI:'), chalk.gray(attestation.recordURI)); 124 + console.log(chalk.blue('CID:'), chalk.gray(attestation.cid)); 122 125 console.log(chalk.blue('Content Hash:'), chalk.gray(attestation.contentHash)); 123 - console.log(chalk.blue('Lexicon:'), chalk.gray(attestation.lexicon)); 126 + console.log(chalk.blue('PDS:'), chalk.gray(attestation.pds)); 124 127 console.log(chalk.blue('Timestamp:'), chalk.gray(new Date(attestation.timestamp * 1000).toISOString())); 125 128 console.log(chalk.blue('Attester:'), chalk.gray(attestation.attester)); 126 - console.log(chalk.blue('Revoked:'), attestation.revoked ? chalk.red('Yes') : chalk.green('No')); 127 129 128 130 // Compare with current if requested 129 131 if (options.compare) { 130 132 console.log(chalk.yellow('\n🔄 Comparing with current record...')); 131 133 132 - const comparison = await notary.compareWithCurrent(attestation); 133 - 134 - if (!comparison.exists) { 135 - console.log(chalk.red('⚠ Record has been deleted')); 136 - } else if (comparison.matches) { 137 - console.log(chalk.green('✓ Content matches attestation (unchanged)')); 138 - } else { 139 - console.log(chalk.yellow('⚠ Content has changed since attestation')); 140 - console.log(chalk.gray(` Original: ${attestation.contentHash.substring(0, 20)}...`)); 141 - console.log(chalk.gray(` Current: ${comparison.currentHash!.substring(0, 20)}...`)); 134 + try { 135 + const comparison = await notary.compareWithCurrent(attestation); 136 + 137 + if (!comparison.exists) { 138 + console.log(chalk.red('⚠ Record has been deleted')); 139 + } else if (comparison.matches) { 140 + console.log(chalk.green('✓ Content matches attestation (unchanged)')); 141 + } else { 142 + console.log(chalk.yellow('⚠ Content has changed since attestation')); 143 + console.log(chalk.gray(` Attested CID: ${attestation.cid}`)); 144 + console.log(chalk.gray(` Attested Hash: ${attestation.contentHash.substring(0, 20)}...`)); 145 + console.log(chalk.gray(` Current Hash: ${comparison.currentHash!.substring(0, 20)}...`)); 146 + } 147 + } catch (err: any) { 148 + console.log(chalk.red(`⚠ Could not fetch current record: ${err.message}`)); 142 149 } 143 150 } 144 151 ··· 150 157 process.exit(1); 151 158 } 152 159 }); 160 + 153 161 154 162 program.parse();
+158 -248
src/lib/notary.ts
··· 1 1 import { AtpAgent } from '@atproto/api'; 2 - import { 3 - createPublicClient, 4 - createWalletClient, 5 - http, 6 - type Address, 7 - type Hash, 8 - type PublicClient, 9 - type WalletClient, 10 - zeroAddress, 11 - encodeAbiParameters, 12 - parseAbiParameters, 13 - decodeAbiParameters, 14 - keccak256, 15 - toHex, 16 - } from 'viem'; 17 - import { privateKeyToAccount } from 'viem/accounts'; 18 - import { sepolia, base, baseSepolia } from 'viem/chains'; 2 + import { ethers } from 'ethers'; 3 + import EASPackage from '@ethereum-attestation-service/eas-sdk'; 4 + const { EAS, SchemaEncoder, SchemaRegistry, NO_EXPIRATION } = EASPackage; 5 + 19 6 import type { NotaryConfig, NotarizationResult, AttestationData } from './types'; 20 7 import { parseRecordURI, hashContent, getExplorerURL } from './utils'; 21 8 22 9 // Chain configurations 23 10 const CHAIN_CONFIG = { 24 11 'sepolia': { 25 - chain: sepolia, 26 12 rpcUrl: 'https://1rpc.io/sepolia', 27 - easContractAddress: '0xC2679fBD37d54388Ce493F1DB75320D236e1815e' as Address, 28 - schemaRegistryAddress: '0x0a7E2Ff54e76B8E6659aedc9103FB21c038050D0' as Address, 13 + easContractAddress: '0xC2679fBD37d54388Ce493F1DB75320D236e1815e', 14 + schemaRegistryAddress: '0x0a7E2Ff54e76B8E6659aedc9103FB21c038050D0', 29 15 }, 30 16 'base-sepolia': { 31 - chain: baseSepolia, 32 17 rpcUrl: 'https://sepolia.base.org', 33 - easContractAddress: '0x4200000000000000000000000000000000000021' as Address, 34 - schemaRegistryAddress: '0x4200000000000000000000000000000000000020' as Address, 18 + easContractAddress: '0x4200000000000000000000000000000000000021', 19 + schemaRegistryAddress: '0x4200000000000000000000000000000000000020', 35 20 }, 36 21 'base': { 37 - chain: base, 38 22 rpcUrl: 'https://mainnet.base.org', 39 - easContractAddress: '0x4200000000000000000000000000000000000021' as Address, 40 - schemaRegistryAddress: '0x4200000000000000000000000000000000000020' as Address, 23 + easContractAddress: '0x4200000000000000000000000000000000000021', 24 + schemaRegistryAddress: '0x4200000000000000000000000000000000000020', 41 25 } 42 26 }; 43 27 44 - const SCHEMA_STRING = "string recordURI,bytes32 contentHash,string lexicon,uint256 timestamp"; 45 - 46 - // EAS Contract ABIs 47 - const SCHEMA_REGISTRY_ABI = [ 48 - { 49 - type: 'function', 50 - name: 'register', 51 - inputs: [ 52 - { name: 'schema', type: 'string' }, 53 - { name: 'resolver', type: 'address' }, 54 - { name: 'revocable', type: 'bool' } 55 - ], 56 - outputs: [{ name: 'uid', type: 'bytes32' }], 57 - stateMutability: 'nonpayable', 58 - }, 59 - ] as const; 60 - 61 - const EAS_ABI = [ 62 - { 63 - type: 'function', 64 - name: 'attest', 65 - inputs: [ 66 - { 67 - name: 'request', 68 - type: 'tuple', 69 - components: [ 70 - { name: 'schema', type: 'bytes32' }, 71 - { 72 - name: 'data', 73 - type: 'tuple', 74 - components: [ 75 - { name: 'recipient', type: 'address' }, 76 - { name: 'expirationTime', type: 'uint64' }, 77 - { name: 'revocable', type: 'bool' }, 78 - { name: 'refUID', type: 'bytes32' }, 79 - { name: 'data', type: 'bytes' }, 80 - { name: 'value', type: 'uint256' }, 81 - ], 82 - }, 83 - ], 84 - }, 85 - ], 86 - outputs: [{ name: 'uid', type: 'bytes32' }], 87 - stateMutability: 'payable', 88 - }, 89 - { 90 - type: 'function', 91 - name: 'getAttestation', 92 - inputs: [{ name: 'uid', type: 'bytes32' }], 93 - outputs: [ 94 - { 95 - name: 'attestation', 96 - type: 'tuple', 97 - components: [ 98 - { name: 'uid', type: 'bytes32' }, 99 - { name: 'schema', type: 'bytes32' }, 100 - { name: 'time', type: 'uint64' }, 101 - { name: 'expirationTime', type: 'uint64' }, 102 - { name: 'revocationTime', type: 'uint64' }, 103 - { name: 'refUID', type: 'bytes32' }, 104 - { name: 'recipient', type: 'address' }, 105 - { name: 'attester', type: 'address' }, 106 - { name: 'revocable', type: 'bool' }, 107 - { name: 'data', type: 'bytes' }, 108 - ], 109 - }, 110 - ], 111 - stateMutability: 'view', 112 - }, 113 - ] as const; 114 - 115 - // Helper to encode attestation data 116 - function encodeAttestationData(params: { 117 - recordURI: string; 118 - contentHash: Hash; 119 - lexicon: string; 120 - timestamp: number; 121 - }): Hash { 122 - return encodeAbiParameters( 123 - parseAbiParameters(SCHEMA_STRING), 124 - [params.recordURI, params.contentHash, params.lexicon, BigInt(params.timestamp)] 125 - ); 126 - } 127 - 128 - // Helper to decode attestation data 129 - function decodeAttestationData(data: Hash): { 130 - recordURI: string; 131 - contentHash: Hash; 132 - lexicon: string; 133 - timestamp: number; 134 - } { 135 - const [recordURI, contentHash, lexicon, timestamp] = decodeAbiParameters( 136 - parseAbiParameters(SCHEMA_STRING), 137 - data 138 - ); 139 - 140 - return { 141 - recordURI: recordURI as string, 142 - contentHash: contentHash as Hash, 143 - lexicon: lexicon as string, 144 - timestamp: Number(timestamp), 145 - }; 146 - } 28 + const SCHEMA_STRING = "string recordURI,string cid,bytes32 contentHash,string pds,uint256 timestamp"; 147 29 148 30 export class ATProtocolNotary { 149 31 private config: Required<NotaryConfig>; 150 - private publicClient: PublicClient; 151 - private walletClient: WalletClient; 152 - private account: ReturnType<typeof privateKeyToAccount>; 32 + private provider: ethers.JsonRpcProvider; 33 + private signer: ethers.Wallet; 153 34 private network: string; 154 35 private chainConfig: typeof CHAIN_CONFIG[keyof typeof CHAIN_CONFIG]; 36 + private eas: any; 37 + private schemaRegistry: any; 155 38 156 39 constructor(config: NotaryConfig, network: string = 'sepolia') { 157 40 this.network = network; ··· 160 43 this.config = { 161 44 privateKey: config.privateKey, 162 45 rpcUrl: config.rpcUrl || this.chainConfig.rpcUrl, 163 - easContractAddress: (config.easContractAddress || this.chainConfig.easContractAddress) as Address, 164 - schemaRegistryAddress: (config.schemaRegistryAddress || this.chainConfig.schemaRegistryAddress) as Address, 46 + easContractAddress: config.easContractAddress || this.chainConfig.easContractAddress, 47 + schemaRegistryAddress: config.schemaRegistryAddress || this.chainConfig.schemaRegistryAddress, 165 48 schemaUID: config.schemaUID || '', 166 49 }; 167 50 ··· 169 52 throw new Error('Private key is required'); 170 53 } 171 54 172 - // Create account from private key 173 - this.account = privateKeyToAccount(this.config.privateKey as Hash); 55 + // Create ethers provider and signer 56 + this.provider = new ethers.JsonRpcProvider(this.config.rpcUrl); 57 + this.signer = new ethers.Wallet(this.config.privateKey, this.provider); 174 58 175 - // Create public client (for reading) 176 - this.publicClient = createPublicClient({ 177 - chain: this.chainConfig.chain, 178 - transport: http(this.config.rpcUrl), 179 - }); 59 + // Initialize EAS SDK 60 + this.eas = new EAS(this.config.easContractAddress); 61 + this.eas.connect(this.signer); 180 62 181 - // Create wallet client (for writing) 182 - this.walletClient = createWalletClient({ 183 - account: this.account, 184 - chain: this.chainConfig.chain, 185 - transport: http(this.config.rpcUrl), 186 - }); 63 + this.schemaRegistry = new SchemaRegistry(this.config.schemaRegistryAddress); 64 + this.schemaRegistry.connect(this.signer); 187 65 } 188 66 189 67 /** 190 68 * Initialize: Create EAS schema (one-time setup) 191 69 */ 192 70 async initializeSchema(): Promise<string> { 193 - 194 - // Call register function on SchemaRegistry 195 - const hash = await this.walletClient.writeContract({ 196 - address: this.config.schemaRegistryAddress, 197 - abi: SCHEMA_REGISTRY_ABI, 198 - functionName: 'register', 199 - args: [SCHEMA_STRING, zeroAddress, true], 71 + const transaction = await this.schemaRegistry.register({ 72 + schema: SCHEMA_STRING, 73 + resolverAddress: ethers.ZeroAddress, 74 + revocable: true, 200 75 }); 201 76 202 - // Wait for transaction receipt 203 - const receipt = await this.publicClient.waitForTransactionReceipt({ hash }); 77 + await transaction.wait(); 78 + 79 + // Return the transaction hash as schema UID placeholder 80 + return transaction.tx?.hash; 81 + } 204 82 205 - // Extract schema UID from logs 206 - // The SchemaRegistered event emits the schema UID 207 - if (receipt.logs.length > 0) { 208 - // The first topic after the event signature is the schema UID 209 - const schemaUID = receipt.logs[0].topics[1]; 210 - if (schemaUID) { 211 - return schemaUID; 83 + /** 84 + * Resolve DID to PDS endpoint 85 + */ 86 + async resolveDIDtoPDS(did: string): Promise<string> { 87 + // For did:plc, resolve via PLC directory 88 + if (did.startsWith('did:plc:')) { 89 + const response = await fetch(`https://plc.directory/${did}`); 90 + if (!response.ok) { 91 + throw new Error(`Failed to resolve DID: ${did}`); 212 92 } 93 + 94 + const didDocument: any = await response.json(); 95 + 96 + // Find the PDS service endpoint 97 + const pdsService = didDocument.service?.find( 98 + (s: any) => s.id === '#atproto_pds' || s.type === 'AtprotoPersonalDataServer' 99 + ); 100 + 101 + if (!pdsService?.serviceEndpoint) { 102 + throw new Error(`No PDS endpoint found for DID: ${did}`); 103 + } 104 + 105 + return pdsService.serviceEndpoint; 213 106 } 214 - 215 - // Fallback: return transaction hash 216 - return hash; 107 + 108 + // For did:web, resolve via web 109 + if (did.startsWith('did:web:')) { 110 + const domain = did.replace('did:web:', ''); 111 + const response = await fetch(`https://${domain}/.well-known/did.json`); 112 + if (!response.ok) { 113 + throw new Error(`Failed to resolve DID: ${did}`); 114 + } 115 + 116 + const didDocument: any = await response.json(); 117 + const pdsService = didDocument.service?.find( 118 + (s: any) => s.id === '#atproto_pds' || s.type === 'AtprotoPersonalDataServer' 119 + ); 120 + 121 + if (!pdsService?.serviceEndpoint) { 122 + throw new Error(`No PDS endpoint found for DID: ${did}`); 123 + } 124 + 125 + return pdsService.serviceEndpoint; 126 + } 127 + 128 + throw new Error(`Unsupported DID method: ${did}`); 217 129 } 218 130 219 131 /** 220 - * Fetch a record from AT Protocol 132 + * Fetch a record from AT Protocol (directly from PDS) 221 133 */ 222 - async fetchRecord(recordURI: string): Promise<any> { 134 + async fetchRecord(recordURI: string): Promise<{ record: any; pds: string }> { 223 135 const { did, collection, rkey } = parseRecordURI(recordURI); 224 136 225 - const agent = new AtpAgent({ service: 'https://public.api.bsky.app' }); 137 + // Resolve DID to PDS 138 + const pds = await this.resolveDIDtoPDS(did); 139 + 140 + // Create agent pointing to user's PDS 141 + const agent = new AtpAgent({ service: pds }); 226 142 227 143 const response = await agent.com.atproto.repo.getRecord({ 228 144 repo: did, ··· 230 146 rkey: rkey 231 147 }); 232 148 233 - return response.data; 149 + return { 150 + record: response.data, 151 + pds: pds 152 + }; 234 153 } 235 154 236 - /** 237 - * Notarize an AT Protocol record on Ethereum 238 - */ 239 - async notarizeRecord(recordURI: string): Promise<NotarizationResult> { 240 - if (!this.config.schemaUID) { 241 - throw new Error('Schema UID not set. Run initializeSchema() first.'); 242 - } 155 + /** 156 + * Notarize an AT Protocol record on Ethereum 157 + */ 158 + async notarizeRecord(recordURI: string): Promise<NotarizationResult> { 159 + if (!this.config.schemaUID) { 160 + throw new Error('Schema UID not set. Run initializeSchema() first.'); 161 + } 243 162 244 - // Parse URI 245 - const { collection } = parseRecordURI(recordURI); 163 + // Parse URI 164 + const { collection } = parseRecordURI(recordURI); 246 165 247 - // Fetch record 248 - const record = await this.fetchRecord(recordURI); 166 + // Fetch record directly from PDS 167 + const { record, pds } = await this.fetchRecord(recordURI); 249 168 250 - // Generate content hash 251 - const contentHash = hashContent(record.value); 169 + // Generate content hash 170 + const contentHash = hashContent(record.value); 252 171 253 - // Encode attestation data 254 - const encodedData = encodeAttestationData({ 255 - recordURI, 256 - contentHash: contentHash as Hash, 257 - lexicon: collection, 258 - timestamp: Math.floor(Date.now() / 1000), 259 - }); 172 + // Get CID from the record response 173 + const recordCID = record.cid; 260 174 261 - // Create attestation request 262 - const attestationRequest = { 263 - schema: this.config.schemaUID as Hash, 264 - data: { 265 - recipient: zeroAddress, 266 - expirationTime: 0n, 267 - revocable: true, 268 - refUID: '0x0000000000000000000000000000000000000000000000000000000000000000' as Hash, 269 - data: encodedData, 270 - value: 0n, 271 - }, 272 - }; 175 + // Initialize SchemaEncoder with the schema string 176 + const schemaEncoder = new SchemaEncoder(SCHEMA_STRING); 177 + const encodedData = schemaEncoder.encodeData([ 178 + { name: "recordURI", value: recordURI, type: "string" }, 179 + { name: "cid", value: recordCID, type: "string" }, 180 + { name: "contentHash", value: contentHash, type: "bytes32" }, 181 + { name: "pds", value: pds, type: "string" }, 182 + { name: "timestamp", value: Math.floor(Date.now() / 1000), type: "uint256" } 183 + ]); 273 184 274 - // First simulate to get the return value (UID) 275 - const { result: uid } = await this.publicClient.simulateContract({ 276 - address: this.config.easContractAddress, 277 - abi: EAS_ABI, 278 - functionName: 'attest', 279 - args: [attestationRequest], 280 - account: this.account, 281 - }); 185 + const transaction = await this.eas.attest({ 186 + schema: this.config.schemaUID, 187 + data: { 188 + recipient: ethers.ZeroAddress, 189 + expirationTime: NO_EXPIRATION, 190 + revocable: false, 191 + data: encodedData, 192 + }, 193 + }); 282 194 283 - // Then submit the actual transaction 284 - const hash = await this.walletClient.writeContract({ 285 - address: this.config.easContractAddress, 286 - abi: EAS_ABI, 287 - functionName: 'attest', 288 - args: [attestationRequest], 289 - }); 195 + const newAttestationUID = await transaction.wait(); 290 196 291 - // Wait for transaction 292 - await this.publicClient.waitForTransactionReceipt({ hash }); 197 + return { 198 + attestationUID: newAttestationUID, 199 + recordURI, 200 + cid: recordCID, 201 + contentHash, 202 + pds, 203 + lexicon: collection, 204 + transactionHash: transaction.tx?.hash, 205 + explorerURL: getExplorerURL(newAttestationUID, this.network), 206 + }; 207 + } 293 208 294 - // Use the UID from simulation 295 - const attestationUID = uid; 296 - 297 - return { 298 - attestationUID, 299 - recordURI, 300 - contentHash, 301 - lexicon: collection, 302 - transactionHash: hash, 303 - explorerURL: getExplorerURL(attestationUID, this.network), 304 - }; 305 - } 306 209 307 210 /** 308 211 * Verify an attestation 309 212 */ 310 213 async verifyAttestation(attestationUID: string): Promise<AttestationData> { 311 - // Read attestation from contract 312 - const attestation = await this.publicClient.readContract({ 313 - address: this.config.easContractAddress, 314 - abi: EAS_ABI, 315 - functionName: 'getAttestation', 316 - args: [attestationUID as Hash], 317 - }); 214 + const attestation = await this.eas.getAttestation(attestationUID); 318 215 319 - // Check if attestation exists 320 - if (attestation.uid === '0x0000000000000000000000000000000000000000000000000000000000000000') { 216 + if (!attestation || attestation.uid === '0x0000000000000000000000000000000000000000000000000000000000000000') { 321 217 throw new Error('Attestation not found'); 322 218 } 323 219 324 220 // Decode attestation data 325 - const decodedData = decodeAttestationData(attestation.data as Hash); 221 + const schemaEncoder = new SchemaEncoder(SCHEMA_STRING); 222 + const decodedData = schemaEncoder.decodeData(attestation.data); 223 + 224 + const recordURI = decodedData.find(d => d.name === 'recordURI')?.value.value as string; 225 + const cid = decodedData.find(d => d.name === 'cid')?.value.value as string; 226 + const contentHash = decodedData.find(d => d.name === 'contentHash')?.value.value as string; 227 + const pds = decodedData.find(d => d.name === 'pds')?.value.value as string; 228 + const timestamp = Number(decodedData.find(d => d.name === 'timestamp')?.value.value); 229 + 230 + // Parse lexicon from recordURI (since it's not in schema) 231 + const { collection: lexicon } = parseRecordURI(recordURI); 326 232 327 233 return { 328 234 uid: attestationUID, 329 - recordURI: decodedData.recordURI, 330 - contentHash: decodedData.contentHash, 331 - lexicon: decodedData.lexicon, 332 - timestamp: decodedData.timestamp, 235 + recordURI, 236 + cid, 237 + contentHash, 238 + pds, 239 + lexicon, 240 + timestamp, 333 241 attester: attestation.attester, 334 242 revoked: attestation.revocationTime > 0n, 335 243 explorerURL: getExplorerURL(attestationUID, this.network), 336 244 }; 337 245 } 246 + 338 247 339 248 /** 340 249 * Compare attestation with current record state ··· 345 254 currentHash?: string; 346 255 }> { 347 256 try { 348 - const record = await this.fetchRecord(attestationData.recordURI); 257 + // fetchRecord now returns { record, pds }, so we need to destructure 258 + const { record } = await this.fetchRecord(attestationData.recordURI); 349 259 const currentHash = hashContent(record.value); 350 260 351 261 return { ··· 364 274 /** 365 275 * Get signer address 366 276 */ 367 - getAddress(): Address { 368 - return this.account.address; 277 + async getAddress(): Promise<string> { 278 + return this.signer.getAddress(); 369 279 } 370 280 }
+4
src/lib/types.ts
··· 9 9 export interface NotarizationResult { 10 10 attestationUID: string; 11 11 recordURI: string; 12 + cid: string; 12 13 contentHash: string; 14 + pds: string; 13 15 lexicon: string; 14 16 transactionHash: string; 15 17 explorerURL: string; ··· 18 20 export interface AttestationData { 19 21 uid: string; 20 22 recordURI: string; 23 + cid: string; 21 24 contentHash: string; 25 + pds: string; 22 26 lexicon: string; 23 27 timestamp: number; 24 28 attester: string;