Notarize AT Protocol records on Ethereum using EAS (experiment)
1#!/usr/bin/env node
2
3import { Command } from 'commander';
4import chalk from 'chalk';
5import ora from 'ora';
6import * as dotenv from 'dotenv';
7import { ATProtocolNotary } from './lib/index.js';
8
9dotenv.config();
10
11const program = new Command();
12
13program
14 .name('atnotary')
15 .description('Notarize AT Protocol records on Ethereum using EAS')
16 .version('0.1.0');
17
18// Initialize command
19program
20 .command('init')
21 .description('Initialize: create EAS schema (one-time setup)')
22 .option('-n, --network <network>', 'Network to use', 'sepolia')
23 .action(async (options) => {
24 try {
25 console.log(chalk.blue('🔧 Initializing EAS Schema'));
26 console.log(chalk.gray(`Network: ${options.network}\n`));
27
28 const spinner = ora(`Connecting to ${options.network}...`).start();
29
30 const notary = new ATProtocolNotary({
31 privateKey: process.env.PRIVATE_KEY!,
32 }, options.network);
33
34 const address = await notary.getAddress();
35 spinner.succeed(`Connected to ${options.network}: ${address}`);
36
37 spinner.start(`Creating schema on EAS (${options.network})...`);
38 const schemaUID = await notary.initializeSchema();
39
40 spinner.succeed('Schema created!');
41
42 console.log(chalk.green('\n✅ Schema UID:'), chalk.cyan(schemaUID));
43 console.log(chalk.yellow('\n⚠️ Add this to your .env file:'));
44 console.log(chalk.gray(`SCHEMA_UID="${schemaUID}"\n`));
45
46 } catch (error: any) {
47 console.error(chalk.red('Error:'), error.message);
48 process.exit(1);
49 }
50 });
51
52// Notarize command
53program
54 .command('notarize <recordURI>')
55 .description('Notarize an AT Protocol record')
56 .option('-n, --network <network>', 'Network to use', 'sepolia')
57 .action(async (recordURI: string, options) => {
58 try {
59 console.log(chalk.blue('🔐 AT Protocol Notary'));
60 console.log(chalk.gray(`Network: ${options.network}\n`));
61
62 const spinner = ora('Initializing...').start();
63
64 const notary = new ATProtocolNotary({
65 privateKey: process.env.PRIVATE_KEY!,
66 schemaUID: process.env.SCHEMA_UID,
67 }, options.network);
68
69 spinner.succeed('Initialized');
70
71 spinner.start('Resolving DID to PDS...');
72 const { record, pds } = await notary.fetchRecord(recordURI);
73 spinner.succeed(`Record fetched from PDS: ${pds}`);
74
75 console.log(chalk.gray('\nRecord preview:'));
76 console.log(chalk.gray(JSON.stringify(record.value, null, 2).substring(0, 200) + '...\n'));
77
78 spinner.start(`Creating attestation on ${options.network}...`);
79 const result = await notary.notarizeRecord(recordURI);
80 spinner.succeed('Attestation created!');
81
82 console.log(chalk.green('\n✅ Notarization Complete!\n'));
83 console.log(chalk.blue('Network:'), chalk.gray(options.network));
84 console.log(chalk.blue('Attestation UID:'), chalk.cyan(result.attestationUID));
85 console.log(chalk.blue('Record URI:'), chalk.gray(result.recordURI));
86 console.log(chalk.blue('CID:'), chalk.gray(result.cid));
87 console.log(chalk.blue('Content Hash:'), chalk.gray(result.contentHash));
88 console.log(chalk.blue('PDS:'), chalk.gray(result.pds));
89 console.log(chalk.blue('Transaction:'), chalk.gray(result.transactionHash));
90
91 console.log(chalk.yellow('\n📋 View on EAS Explorer:'));
92 console.log(chalk.cyan(result.explorerURL + '\n'));
93
94 console.log(chalk.yellow('🔍 Verify with:'));
95 console.log(chalk.gray(`atnotary verify ${result.attestationUID} --network ${options.network}\n`));
96
97 } catch (error: any) {
98 console.error(chalk.red('Error:'), error.message);
99 process.exit(1);
100 }
101 });
102
103// Verify command
104program
105 .command('verify <attestationUID>')
106 .description('Verify an attestation')
107 .option('-n, --network <network>', 'Network to use', 'sepolia')
108 .option('-c, --compare', 'Compare with current record state')
109 .action(async (attestationUID: string, options) => {
110 try {
111 console.log(chalk.blue('🔍 Verifying Attestation'));
112 console.log(chalk.gray(`Network: ${options.network}\n`));
113
114 const spinner = ora(`Fetching attestation from EAS (${options.network})...`).start();
115
116 const notary = new ATProtocolNotary({
117 schemaUID: process.env.SCHEMA_UID,
118 }, options.network);
119
120 const attestation = await notary.verifyAttestation(attestationUID);
121 spinner.succeed('Attestation found');
122
123 console.log(chalk.green('\n✅ Attestation Valid\n'));
124 console.log(chalk.blue('Network:'), chalk.gray(options.network));
125 console.log(chalk.blue('UID:'), chalk.cyan(attestation.uid));
126 console.log(chalk.blue('Record URI:'), chalk.gray(attestation.recordURI));
127 console.log(chalk.blue('CID:'), chalk.gray(attestation.cid));
128 console.log(chalk.blue('Content Hash:'), chalk.gray(attestation.contentHash));
129 console.log(chalk.blue('PDS:'), chalk.gray(attestation.pds));
130 console.log(chalk.blue('Timestamp:'), chalk.gray(new Date(attestation.timestamp * 1000).toISOString()));
131 console.log(chalk.blue('Attester:'), chalk.gray(attestation.attester));
132
133 // Compare with current if requested
134 if (options.compare) {
135 console.log(chalk.yellow('\n🔄 Comparing with current record...'));
136
137 try {
138 const comparison = await notary.compareWithCurrent(attestation);
139
140 if (!comparison.exists) {
141 console.log(chalk.red('⚠ Record has been deleted'));
142 } else {
143 // Check CID
144 if (comparison.cidMatches) {
145 console.log(chalk.green('✓ CID matches (content identical via AT Protocol)'));
146 } else {
147 console.log(chalk.red('✗ CID changed (content modified)'));
148 console.log(chalk.gray(` Attested CID: ${attestation.cid}`));
149 console.log(chalk.gray(` Current CID: ${comparison.currentCid}`));
150 }
151
152 // Check content hash
153 if (comparison.hashMatches) {
154 console.log(chalk.green('✓ Content hash matches'));
155 } else {
156 console.log(chalk.red('✗ Content hash changed'));
157 console.log(chalk.gray(` Attested Hash: ${attestation.contentHash.substring(0, 20)}...`));
158 console.log(chalk.gray(` Current Hash: ${comparison.currentHash!.substring(0, 20)}...`));
159 }
160
161 // Summary
162 if (comparison.cidMatches && comparison.hashMatches) {
163 console.log(chalk.green('\n✅ Record unchanged since attestation'));
164 } else {
165 console.log(chalk.yellow('\n⚠️ Record has been modified since attestation'));
166 }
167 }
168 } catch (err: any) {
169 console.log(chalk.red(`⚠ Could not fetch current record: ${err.message}`));
170 }
171 }
172
173
174 console.log(chalk.blue('\n📋 View on explorer:'));
175 console.log(chalk.cyan(attestation.explorerURL + '\n'));
176
177 } catch (error: any) {
178 console.error(chalk.red('Error:'), error.message);
179 process.exit(1);
180 }
181 });
182
183program.parse();