···42424343## Setup
44444545-1. Create `.env` in your working directory:
4545+1. **Create `.env` file:**
4646+4647```bash
4748PRIVATE_KEY="0x..."
4848-SCHEMA_UID=""
4949+# SCHEMA_UID is optional - default schemas are provided
4950```
50515151-2. Initialize (one-time):
5252+2. **Get testnet ETH:**
5353+ - Sepolia: https://sepoliafaucet.com/
5454+ - Base Sepolia: https://bridge.base.org/
5555+5656+**That's it!** Default schemas are provided for all networks.
5757+5858+### Custom Schema (Optional)
5959+6060+If you want to deploy your own schema:
6161+5262```bash
5363atnotary init --network sepolia
5464```
55655656-Add the outputted `SCHEMA_UID` to `.env`.
5757-5858-3. Get testnet ETH from [Sepolia faucet](https://sepoliafaucet.com/)
6666+Then add the `SCHEMA_UID` to your `.env` file.
59676068## Usage
61696262-**Notarize:**
6370```bash
7171+# notarize
6472atnotary notarize "at://did:plc:xxx/app.bsky.feed.post/abc"
6565-```
66736767-**Verify:**
6868-```bash
7474+# verify
6975atnotary verify "0xabc..." --network sepolia
7070-```
71767272-**Compare with current state:**
7373-```bash
7777+# compare with current state
7478atnotary verify "0xabc..." --compare
7579```
7680
+25-7
src/cli.ts
···114114 const spinner = ora(`Fetching attestation from EAS (${options.network})...`).start();
115115116116 const notary = new ATProtocolNotary({
117117- privateKey: process.env.PRIVATE_KEY!,
118117 schemaUID: process.env.SCHEMA_UID,
119118 }, options.network);
120119···140139141140 if (!comparison.exists) {
142141 console.log(chalk.red('⚠ Record has been deleted'));
143143- } else if (comparison.matches) {
144144- console.log(chalk.green('✓ Content matches attestation (unchanged)'));
145142 } else {
146146- console.log(chalk.yellow('⚠ Content has changed since attestation'));
147147- console.log(chalk.gray(` Attested CID: ${attestation.cid}`));
148148- console.log(chalk.gray(` Attested Hash: ${attestation.contentHash.substring(0, 20)}...`));
149149- console.log(chalk.gray(` Current Hash: ${comparison.currentHash!.substring(0, 20)}...`));
143143+ // Check CID
144144+ if (comparison.cidMatches) {
145145+ console.log(chalk.green('✓ CID matches (content identical via AT Protocol)'));
146146+ } else {
147147+ console.log(chalk.red('✗ CID changed (content modified)'));
148148+ console.log(chalk.gray(` Attested CID: ${attestation.cid}`));
149149+ console.log(chalk.gray(` Current CID: ${comparison.currentCid}`));
150150+ }
151151+152152+ // Check content hash
153153+ if (comparison.hashMatches) {
154154+ console.log(chalk.green('✓ Content hash matches'));
155155+ } else {
156156+ console.log(chalk.red('✗ Content hash changed'));
157157+ console.log(chalk.gray(` Attested Hash: ${attestation.contentHash.substring(0, 20)}...`));
158158+ console.log(chalk.gray(` Current Hash: ${comparison.currentHash!.substring(0, 20)}...`));
159159+ }
160160+161161+ // Summary
162162+ if (comparison.cidMatches && comparison.hashMatches) {
163163+ console.log(chalk.green('\n✅ Record unchanged since attestation'));
164164+ } else {
165165+ console.log(chalk.yellow('\n⚠️ Record has been modified since attestation'));
166166+ }
150167 }
151168 } catch (err: any) {
152169 console.log(chalk.red(`⚠ Could not fetch current record: ${err.message}`));
153170 }
154171 }
172172+155173156174 console.log(chalk.blue('\n📋 View on explorer:'));
157175 console.log(chalk.cyan(attestation.explorerURL + '\n'));
+55-21
src/lib/notary.ts
···66import type { NotaryConfig, NotarizationResult, AttestationData } from './types';
77import { parseRecordURI, hashContent, getExplorerURL } from './utils';
8899+// Default schemas (deployed by atnotary maintainers)
1010+const DEFAULT_SCHEMAS = {
1111+ 'sepolia': '0x2a39517604107c79acbb962fe809795a87b7e47b8682fd9fbd3f62694fcca47c',
1212+ 'base-sepolia': '0x...', // TODO: Deploy and add schema UID
1313+ 'base': '0x...', // TODO: Deploy and add schema UID
1414+};
1515+916// Chain configurations
1017const CHAIN_CONFIG = {
1118 'sepolia': {
···3643 private eas: any;
3744 private schemaRegistry: any;
38453939- constructor(config: NotaryConfig, network: string = 'sepolia') {
4646+ constructor(config: NotaryConfig = {}, network: string = 'sepolia') {
4047 this.network = network;
4148 this.chainConfig = CHAIN_CONFIG[network as keyof typeof CHAIN_CONFIG] || CHAIN_CONFIG['sepolia'];
42495050+ // Use default schema if not provided
5151+ const defaultSchemaUID = DEFAULT_SCHEMAS[network as keyof typeof DEFAULT_SCHEMAS];
5252+4353 this.config = {
4444- privateKey: config.privateKey,
5454+ privateKey: config.privateKey || '',
4555 rpcUrl: config.rpcUrl || this.chainConfig.rpcUrl,
4646- easContractAddress: config.easContractAddress || this.chainConfig.easContractAddress,
4747- schemaRegistryAddress: config.schemaRegistryAddress || this.chainConfig.schemaRegistryAddress,
4848- schemaUID: config.schemaUID || '',
5656+ easContractAddress: (config.easContractAddress || this.chainConfig.easContractAddress),
5757+ schemaRegistryAddress: (config.schemaRegistryAddress || this.chainConfig.schemaRegistryAddress),
5858+ schemaUID: config.schemaUID || defaultSchemaUID || '',
4959 };
50605151- if (!this.config.privateKey) {
5252- throw new Error('Private key is required');
5353- }
5454-5555- // Create ethers provider and signer
6161+ // Create ethers provider (always needed for reading)
5662 this.provider = new ethers.JsonRpcProvider(this.config.rpcUrl);
5757- this.signer = new ethers.Wallet(this.config.privateKey, this.provider);
5858-5959- // Initialize EAS SDK
6060- this.eas = new EAS(this.config.easContractAddress);
6161- this.eas.connect(this.signer);
6363+6464+ // Only create signer if private key provided (for writing operations)
6565+ if (this.config.privateKey) {
6666+ this.signer = new ethers.Wallet(this.config.privateKey, this.provider);
6767+6868+ // Initialize EAS SDK for writing
6969+ this.eas = new EAS(this.config.easContractAddress);
7070+ this.eas.connect(this.signer);
62716363- this.schemaRegistry = new SchemaRegistry(this.config.schemaRegistryAddress);
6464- this.schemaRegistry.connect(this.signer);
7272+ this.schemaRegistry = new SchemaRegistry(this.config.schemaRegistryAddress);
7373+ this.schemaRegistry.connect(this.signer);
7474+ } else {
7575+ // For read-only operations, connect EAS to provider
7676+ this.eas = new EAS(this.config.easContractAddress);
7777+ this.eas.connect(this.provider as any);
7878+ }
6579 }
66808181+6782 /**
6883 * Initialize: Create EAS schema (one-time setup)
6984 */
7085 async initializeSchema(): Promise<string> {
8686+ if (!this.config.privateKey) {
8787+ throw new Error('Private key required for schema initialization');
8888+ }
7189 const transaction = await this.schemaRegistry.register({
7290 schema: SCHEMA_STRING,
7391 resolverAddress: ethers.ZeroAddress,
···156174 * Notarize an AT Protocol record on Ethereum
157175 */
158176 async notarizeRecord(recordURI: string): Promise<NotarizationResult> {
177177+ if (!this.config.privateKey) {
178178+ throw new Error('Private key required for notarization');
179179+ }
159180 if (!this.config.schemaUID) {
160181 throw new Error('Schema UID not set. Run initializeSchema() first.');
161182 }
···250271 */
251272 async compareWithCurrent(attestationData: AttestationData): Promise<{
252273 exists: boolean;
253253- matches: boolean;
274274+ cidMatches: boolean;
275275+ hashMatches: boolean;
276276+ currentCid?: string;
254277 currentHash?: string;
255278 }> {
256279 try {
257257- // fetchRecord now returns { record, pds }, so we need to destructure
280280+ // fetchRecord now returns { record, pds }
258281 const { record } = await this.fetchRecord(attestationData.recordURI);
282282+ console.log(record.value)
259283 const currentHash = hashContent(record.value);
284284+ const currentCid = record.cid;
260285261286 return {
262287 exists: true,
263263- matches: currentHash === attestationData.contentHash,
288288+ cidMatches: currentCid === attestationData.cid,
289289+ hashMatches: currentHash === attestationData.contentHash,
290290+ currentCid,
264291 currentHash,
265292 };
266293 } catch (error: any) {
267294 if (error.message?.includes('RecordNotFound')) {
268268- return { exists: false, matches: false };
295295+ return {
296296+ exists: false,
297297+ cidMatches: false,
298298+ hashMatches: false
299299+ };
269300 }
270301 throw error;
271302 }
···275306 * Get signer address
276307 */
277308 async getAddress(): Promise<string> {
309309+ if (!this.signer) {
310310+ throw new Error('Private key required to get address');
311311+ }
278312 return this.signer.getAddress();
279313 }
280314}