+2
-1
.gitignore
+2
-1
.gitignore
+14
-21
README.md
+14
-21
README.md
···
10
- **Infrastructure** - explorers, indexers, and tooling already exist
11
- **Multi-chain** - works on Ethereum, Base, Optimism, Arbitrum
12
13
-
**Why AT Protocol:**
14
-
Unlike Twitter, AT Protocol is federated and open. Users own their data, can switch servers, and the protocol is public. This makes it possible to build open infrastructure for accountability that isn't controlled by any company.
15
-
16
-
**Principles:**
17
-
- Transparency without judgment - just preserves facts, doesn't label "good" or "bad"
18
-
- Decentralized - fetches directly from user's PDS, not centralized APIs
19
-
- User choice - opt-in system, you choose what to notarize
20
-
- Open infrastructure - anyone can verify, build on, or run their own instance
21
-
22
-
Think of it as combining two open protocols: **AT Protocol** (decentralized social) + **EAS** (decentralized attestations) = transparent, accountable social web.
23
-
24
## What Gets Attested
25
26
- `recordURI` - Full AT Protocol URI
27
- `cid` - AT Protocol's content identifier
28
-
- `contentHash` - SHA-256 hash
29
- `pds` - Personal Data Server URL
30
- `timestamp` - When attested
31
···
37
38
## Setup
39
40
-
1. **Create `.env` file:**
41
42
```bash
43
-
PRIVATE_KEY="0x..."
44
-
# SCHEMA_UID is optional - default schemas are provided
45
```
46
47
-
2. **Get testnet ETH:**
48
- Sepolia: https://sepoliafaucet.com/
49
- Base Sepolia: https://bridge.base.org/
50
···
58
atnotary init --network sepolia
59
```
60
61
-
Then add the `SCHEMA_UID` to your `.env` file.
62
63
## Usage
64
···
79
import { ATProtocolNotary } from 'atnotary';
80
81
const notary = new ATProtocolNotary({
82
-
privateKey: process.env.PRIVATE_KEY!,
83
-
schemaUID: process.env.SCHEMA_UID!,
84
}, 'sepolia');
85
86
const result = await notary.notarizeRecord('at://...');
···
104
105
## License
106
107
-
MIT
108
-
···
10
- **Infrastructure** - explorers, indexers, and tooling already exist
11
- **Multi-chain** - works on Ethereum, Base, Optimism, Arbitrum
12
13
## What Gets Attested
14
15
- `recordURI` - Full AT Protocol URI
16
- `cid` - AT Protocol's content identifier
17
+
- `contentHash` - DAG-CBOR hash
18
- `pds` - Personal Data Server URL
19
- `timestamp` - When attested
20
···
26
27
## Setup
28
29
+
1. **Create config file:**
30
31
```bash
32
+
atnotary config
33
+
```
34
+
35
+
2. **Edit `.atnotary.yaml`:**
36
+
37
+
```yaml
38
+
privateKey: "0x..." # private key for writing
39
+
network: base-sepolia # default network
40
```
41
42
+
3. **Get testnet ETH:**
43
- Sepolia: https://sepoliafaucet.com/
44
- Base Sepolia: https://bridge.base.org/
45
···
53
atnotary init --network sepolia
54
```
55
56
+
Then add the `schemaUID` to your `.atnotary.yaml` file.
57
58
## Usage
59
···
74
import { ATProtocolNotary } from 'atnotary';
75
76
const notary = new ATProtocolNotary({
77
+
privateKey: "0x...", // optional, just for writing
78
}, 'sepolia');
79
80
const result = await notary.notarizeRecord('at://...');
···
98
99
## License
100
101
+
MIT
+3
-2
package.json
+3
-2
package.json
···
30
"@atproto/api": "^0.12.29",
31
"@ethereum-attestation-service/eas-sdk": "^2.9.0",
32
"@ipld/dag-cbor": "^9.2.5",
33
-
"ethers": "^6.15.0"
34
},
35
"devDependencies": {
36
"@types/node": "^20.19.21",
37
"@vitest/coverage-v8": "^3.2.4",
38
"chalk": "^5.6.2",
39
"commander": "^11.1.0",
40
-
"dotenv": "^16.6.1",
41
"ora": "^8.2.0",
42
"tsx": "^4.20.6",
43
"typescript": "^5.9.3",
···
30
"@atproto/api": "^0.12.29",
31
"@ethereum-attestation-service/eas-sdk": "^2.9.0",
32
"@ipld/dag-cbor": "^9.2.5",
33
+
"ethers": "^6.15.0",
34
+
"js-yaml": "^4.1.0"
35
},
36
"devDependencies": {
37
+
"@types/js-yaml": "^4.0.9",
38
"@types/node": "^20.19.21",
39
"@vitest/coverage-v8": "^3.2.4",
40
"chalk": "^5.6.2",
41
"commander": "^11.1.0",
42
"ora": "^8.2.0",
43
"tsx": "^4.20.6",
44
"typescript": "^5.9.3",
+11
-9
pnpm-lock.yaml
+11
-9
pnpm-lock.yaml
···
20
ethers:
21
specifier: ^6.15.0
22
version: 6.15.0
23
devDependencies:
24
'@types/node':
25
specifier: ^20.19.21
26
version: 20.19.21
···
33
commander:
34
specifier: ^11.1.0
35
version: 11.1.0
36
-
dotenv:
37
-
specifier: ^16.6.1
38
-
version: 16.6.1
39
ora:
40
specifier: ^8.2.0
41
version: 8.2.0
···
693
'@types/estree@1.0.8':
694
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
695
696
'@types/lru-cache@5.1.1':
697
resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==}
698
···
1035
diff@5.2.0:
1036
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
1037
engines: {node: '>=0.3.1'}
1038
-
1039
-
dotenv@16.6.1:
1040
-
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
1041
-
engines: {node: '>=12'}
1042
1043
dunder-proto@1.0.1:
1044
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
···
2830
2831
'@types/estree@1.0.8': {}
2832
2833
'@types/lru-cache@5.1.1': {}
2834
2835
'@types/ms@2.1.0': {}
···
3193
depd@2.0.0: {}
3194
3195
diff@5.2.0: {}
3196
-
3197
-
dotenv@16.6.1: {}
3198
3199
dunder-proto@1.0.1:
3200
dependencies:
···
20
ethers:
21
specifier: ^6.15.0
22
version: 6.15.0
23
+
js-yaml:
24
+
specifier: ^4.1.0
25
+
version: 4.1.0
26
devDependencies:
27
+
'@types/js-yaml':
28
+
specifier: ^4.0.9
29
+
version: 4.0.9
30
'@types/node':
31
specifier: ^20.19.21
32
version: 20.19.21
···
39
commander:
40
specifier: ^11.1.0
41
version: 11.1.0
42
ora:
43
specifier: ^8.2.0
44
version: 8.2.0
···
696
'@types/estree@1.0.8':
697
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
698
699
+
'@types/js-yaml@4.0.9':
700
+
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
701
+
702
'@types/lru-cache@5.1.1':
703
resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==}
704
···
1041
diff@5.2.0:
1042
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
1043
engines: {node: '>=0.3.1'}
1044
1045
dunder-proto@1.0.1:
1046
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
···
2832
2833
'@types/estree@1.0.8': {}
2834
2835
+
'@types/js-yaml@4.0.9': {}
2836
+
2837
'@types/lru-cache@5.1.1': {}
2838
2839
'@types/ms@2.1.0': {}
···
3197
depd@2.0.0: {}
3198
3199
diff@5.2.0: {}
3200
3201
dunder-proto@1.0.1:
3202
dependencies:
+64
-33
src/cli.ts
+64
-33
src/cli.ts
···
3
import { Command } from 'commander';
4
import chalk from 'chalk';
5
import ora from 'ora';
6
-
import * as dotenv from 'dotenv';
7
import { ATProtocolNotary } from './lib/index.js';
8
-
9
-
dotenv.config();
10
11
const program = new Command();
12
13
program
14
.name('atnotary')
15
.description('Notarize AT Protocol records on Ethereum using EAS')
16
-
.version('0.1.0');
17
18
// Initialize command
19
program
···
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();
···
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);
···
53
program
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);
···
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));
···
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
104
program
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));
···
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
···
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 {
···
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'));
···
3
import { Command } from 'commander';
4
import chalk from 'chalk';
5
import ora from 'ora';
6
+
import * as path from 'path';
7
+
import * as os from 'os';
8
+
import * as fs from 'node:fs';
9
import { ATProtocolNotary } from './lib/index.js';
10
+
import { loadConfig, getSchemaUID, createConfigTemplate } from './lib/config.js';
11
12
const program = new Command();
13
14
program
15
.name('atnotary')
16
.description('Notarize AT Protocol records on Ethereum using EAS')
17
+
.version('0.1.0')
18
+
.option('-c, --config <path>', 'Path to config file');
19
+
20
+
// Config command
21
+
program
22
+
.command('config')
23
+
.description('Create config template')
24
+
.option('-g, --global', 'Create in home directory')
25
+
.option('-f, --force', 'Overwrite existing config file')
26
+
.action((options) => {
27
+
const configPath = options.global
28
+
? path.join(os.homedir(), '.atnotary.yaml')
29
+
: path.join(process.cwd(), '.atnotary.yaml');
30
+
31
+
// Check if file exists
32
+
if (fs.existsSync(configPath) && !options.force) {
33
+
console.log(chalk.yellow(`\n⚠️ Config file already exists: ${configPath}`));
34
+
console.log(chalk.gray('Use --force to overwrite\n'));
35
+
process.exit(1);
36
+
}
37
+
38
+
createConfigTemplate(configPath);
39
+
console.log(chalk.green(`\n✅ Config template created: ${configPath}`));
40
+
console.log(chalk.yellow('\nEdit the file and add your private key and schema UIDs.\n'));
41
+
});
42
43
// Initialize command
44
program
···
47
.option('-n, --network <network>', 'Network to use', 'sepolia')
48
.action(async (options) => {
49
try {
50
+
const config = loadConfig(program.opts().config);
51
+
52
console.log(chalk.blue('🔧 Initializing EAS Schema'));
53
console.log(chalk.gray(`Network: ${options.network}\n`));
54
55
const spinner = ora(`Connecting to ${options.network}...`).start();
56
57
const notary = new ATProtocolNotary({
58
+
privateKey: config.privateKey,
59
}, options.network);
60
61
const address = await notary.getAddress();
62
spinner.succeed(`Connected to ${options.network}: ${address}`);
63
+
console.log(chalk.blue('Account:'), chalk.cyan(address));
64
65
spinner.start(`Creating schema on EAS (${options.network})...`);
66
const schemaUID = await notary.initializeSchema();
···
68
spinner.succeed('Schema created!');
69
70
console.log(chalk.green('\n✅ Schema UID:'), chalk.cyan(schemaUID));
71
+
console.log(chalk.yellow('\n⚠️ Add this to your .atnotary.json:'));
72
+
console.log(chalk.gray(`\n"networks": {\n "${options.network}": {\n "schemaUID": "${schemaUID}"\n }\n}\n`));
73
74
} catch (error: any) {
75
console.error(chalk.red('Error:'), error.message);
···
81
program
82
.command('notarize <recordURI>')
83
.description('Notarize an AT Protocol record')
84
+
.option('-n, --network <network>', 'Network to use')
85
.action(async (recordURI: string, options) => {
86
try {
87
+
const config = loadConfig(program.opts().config);
88
+
const network = options.network || config.network || 'sepolia';
89
+
90
console.log(chalk.blue('🔐 AT Protocol Notary'));
91
+
console.log(chalk.gray(`Network: ${network}\n`));
92
93
const spinner = ora('Initializing...').start();
94
95
const notary = new ATProtocolNotary({
96
+
privateKey: config.privateKey,
97
+
schemaUID: getSchemaUID(config, network),
98
+
}, network);
99
100
+
// Show account info
101
+
const address = await notary.getAddress();
102
+
spinner.succeed(`Initialized with account: ${chalk.cyan(address)}`);
103
104
spinner.start('Resolving DID to PDS...');
105
const { record, pds } = await notary.fetchRecord(recordURI);
···
108
console.log(chalk.gray('\nRecord preview:'));
109
console.log(chalk.gray(JSON.stringify(record.value, null, 2).substring(0, 200) + '...\n'));
110
111
+
spinner.start(`Creating attestation on ${network}...`);
112
const result = await notary.notarizeRecord(recordURI);
113
spinner.succeed('Attestation created!');
114
115
console.log(chalk.green('\n✅ Notarization Complete!\n'));
116
+
console.log(chalk.blue('Network:'), chalk.gray(network));
117
+
console.log(chalk.blue('Attester:'), chalk.cyan(address));
118
console.log(chalk.blue('Attestation UID:'), chalk.cyan(result.attestationUID));
119
console.log(chalk.blue('Record URI:'), chalk.gray(result.recordURI));
120
console.log(chalk.blue('CID:'), chalk.gray(result.cid));
···
126
console.log(chalk.cyan(result.explorerURL + '\n'));
127
128
console.log(chalk.yellow('🔍 Verify with:'));
129
+
console.log(chalk.gray(`atnotary verify ${result.attestationUID} --network ${network}\n`));
130
131
} catch (error: any) {
132
console.error(chalk.red('Error:'), error.message);
133
process.exit(1);
134
}
135
});
136
+
137
138
// Verify command
139
program
140
.command('verify <attestationUID>')
141
.description('Verify an attestation')
142
+
.option('-n, --network <network>', 'Network to use')
143
+
.option('--compare', 'Compare with current record state')
144
.action(async (attestationUID: string, options) => {
145
try {
146
+
const config = loadConfig(program.opts().config);
147
+
const network = options.network || config.network || 'sepolia';
148
+
149
console.log(chalk.blue('🔍 Verifying Attestation'));
150
+
console.log(chalk.gray(`Network: ${network}\n`));
151
152
+
const spinner = ora(`Fetching attestation from EAS (${network})...`).start();
153
154
const notary = new ATProtocolNotary({
155
+
schemaUID: getSchemaUID(config, network),
156
+
}, network);
157
158
const attestation = await notary.verifyAttestation(attestationUID);
159
spinner.succeed('Attestation found');
160
161
console.log(chalk.green('\n✅ Attestation Valid\n'));
162
+
console.log(chalk.blue('Network:'), chalk.gray(network));
163
console.log(chalk.blue('UID:'), chalk.cyan(attestation.uid));
164
console.log(chalk.blue('Record URI:'), chalk.gray(attestation.recordURI));
165
console.log(chalk.blue('CID:'), chalk.gray(attestation.cid));
···
168
console.log(chalk.blue('Timestamp:'), chalk.gray(new Date(attestation.timestamp * 1000).toISOString()));
169
console.log(chalk.blue('Attester:'), chalk.gray(attestation.attester));
170
171
if (options.compare) {
172
console.log(chalk.yellow('\n🔄 Comparing with current record...'));
173
···
177
if (!comparison.exists) {
178
console.log(chalk.red('⚠ Record has been deleted'));
179
} else {
180
if (comparison.cidMatches) {
181
+
console.log(chalk.green('✓ CID matches (content identical)'));
182
} else {
183
console.log(chalk.red('✗ CID changed (content modified)'));
184
+
console.log(chalk.gray(` Attested: ${attestation.cid}`));
185
+
console.log(chalk.gray(` Current: ${comparison.currentCid}`));
186
}
187
188
if (comparison.hashMatches) {
189
console.log(chalk.green('✓ Content hash matches'));
190
} else {
191
console.log(chalk.red('✗ Content hash changed'));
192
}
193
194
if (comparison.cidMatches && comparison.hashMatches) {
195
console.log(chalk.green('\n✅ Record unchanged since attestation'));
196
} else {
···
201
console.log(chalk.red(`⚠ Could not fetch current record: ${err.message}`));
202
}
203
}
204
205
console.log(chalk.blue('\n📋 View on explorer:'));
206
console.log(chalk.cyan(attestation.explorerURL + '\n'));
+101
src/lib/config.ts
+101
src/lib/config.ts
···
···
1
+
import * as fs from 'fs';
2
+
import * as path from 'path';
3
+
import * as os from 'os';
4
+
import yaml from 'js-yaml';
5
+
6
+
export interface Config {
7
+
privateKey?: string;
8
+
network?: string;
9
+
networks?: {
10
+
[network: string]: {
11
+
schemaUID?: string;
12
+
rpcUrl?: string;
13
+
easContractAddress?: string;
14
+
schemaRegistryAddress?: string;
15
+
};
16
+
};
17
+
}
18
+
19
+
/**
20
+
* Load config from multiple sources (priority order):
21
+
* 1. Custom config path (--config flag)
22
+
* 2. ./.atnotary.yaml (current directory)
23
+
* 3. ~/.atnotary.yaml (home directory)
24
+
* 4. .env file (fallback)
25
+
*/
26
+
export function loadConfig(customPath?: string): Config {
27
+
let config: Config = {};
28
+
29
+
// Try custom path first
30
+
if (customPath) {
31
+
if (fs.existsSync(customPath)) {
32
+
config = loadYamlConfig(customPath);
33
+
return config;
34
+
} else {
35
+
throw new Error(`Config file not found: ${customPath}`);
36
+
}
37
+
}
38
+
39
+
// Try current directory
40
+
const localConfig = path.join(process.cwd(), '.atnotary.yaml');
41
+
if (fs.existsSync(localConfig)) {
42
+
config = loadYamlConfig(localConfig);
43
+
return config;
44
+
}
45
+
46
+
// Try home directory
47
+
const homeConfig = path.join(os.homedir(), '.atnotary.yaml');
48
+
if (fs.existsSync(homeConfig)) {
49
+
config = loadYamlConfig(homeConfig);
50
+
return config;
51
+
}
52
+
53
+
return config;
54
+
}
55
+
56
+
function loadYamlConfig(filePath: string): Config {
57
+
try {
58
+
const fileContents = fs.readFileSync(filePath, 'utf8');
59
+
const config = yaml.load(fileContents) as Config;
60
+
return config;
61
+
} catch (error: any) {
62
+
throw new Error(`Failed to load config from ${filePath}: ${error.message}`);
63
+
}
64
+
}
65
+
66
+
/**
67
+
* Get schema UID for specific network
68
+
*/
69
+
export function getSchemaUID(config: Config, network: string): string | undefined {
70
+
return config.networks?.[network]?.schemaUID;
71
+
}
72
+
73
+
/**
74
+
* Create default config template
75
+
*/
76
+
export function createConfigTemplate(outputPath: string): void {
77
+
const template = `# atnotary configuration
78
+
# Private key for signing transactions (required for notarization)
79
+
privateKey: "0x..."
80
+
81
+
# Default network (sepolia, base-sepolia, base)
82
+
network: sepolia
83
+
84
+
# Network-specific configuration
85
+
networks:
86
+
sepolia:
87
+
schemaUID: "0x..."
88
+
# Optional: custom RPC URL
89
+
# rpcUrl: "https://..."
90
+
91
+
base-sepolia:
92
+
schemaUID: "0x..."
93
+
# rpcUrl: "https://sepolia.base.org"
94
+
95
+
base:
96
+
schemaUID: "0x..."
97
+
# rpcUrl: "https://mainnet.base.org"
98
+
`;
99
+
100
+
fs.writeFileSync(outputPath, template, 'utf8');
101
+
}
+3
-5
src/lib/notary.ts
+3
-5
src/lib/notary.ts
···
9
// Default schemas (deployed by atnotary maintainers)
10
const DEFAULT_SCHEMAS = {
11
'sepolia': '0x2a39517604107c79acbb962fe809795a87b7e47b8682fd9fbd3f62694fcca47c',
12
-
'base-sepolia': '0x...', // TODO: Deploy and add schema UID
13
'base': '0x...', // TODO: Deploy and add schema UID
14
};
15
···
37
export class ATProtocolNotary {
38
private config: Required<NotaryConfig>;
39
private provider: ethers.JsonRpcProvider;
40
-
private signer: ethers.Wallet;
41
private network: string;
42
private chainConfig: typeof CHAIN_CONFIG[keyof typeof CHAIN_CONFIG];
43
private eas: any;
44
-
private schemaRegistry: any;
45
46
constructor(config: NotaryConfig = {}, network: string = 'sepolia') {
47
this.network = network;
···
247
const contentHash = decodedData.find(d => d.name === 'contentHash')?.value.value as string;
248
const pds = decodedData.find(d => d.name === 'pds')?.value.value as string;
249
const timestamp = Number(decodedData.find(d => d.name === 'timestamp')?.value.value);
250
-
251
-
console.log({ extracted: extractHashFromCID(cid), real: contentHash })
252
253
// Parse lexicon from recordURI (since it's not in schema)
254
const { collection: lexicon } = parseRecordURI(recordURI);
···
9
// Default schemas (deployed by atnotary maintainers)
10
const DEFAULT_SCHEMAS = {
11
'sepolia': '0x2a39517604107c79acbb962fe809795a87b7e47b8682fd9fbd3f62694fcca47c',
12
+
'base-sepolia': '0x2a39517604107c79acbb962fe809795a87b7e47b8682fd9fbd3f62694fcca47c',
13
'base': '0x...', // TODO: Deploy and add schema UID
14
};
15
···
37
export class ATProtocolNotary {
38
private config: Required<NotaryConfig>;
39
private provider: ethers.JsonRpcProvider;
40
+
private signer?: ethers.Wallet;
41
private network: string;
42
private chainConfig: typeof CHAIN_CONFIG[keyof typeof CHAIN_CONFIG];
43
private eas: any;
44
+
private schemaRegistry?: any;
45
46
constructor(config: NotaryConfig = {}, network: string = 'sepolia') {
47
this.network = network;
···
247
const contentHash = decodedData.find(d => d.name === 'contentHash')?.value.value as string;
248
const pds = decodedData.find(d => d.name === 'pds')?.value.value as string;
249
const timestamp = Number(decodedData.find(d => d.name === 'timestamp')?.value.value);
250
251
// Parse lexicon from recordURI (since it's not in schema)
252
const { collection: lexicon } = parseRecordURI(recordURI);