easctl 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easctl",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "CLI tool for the Ethereum Attestation Service — create, revoke, and query attestations from the command line",
5
5
  "bin": {
6
6
  "easctl": "./dist/index.js"
@@ -25,6 +25,7 @@ vi.mock('../../stdin.js', () => ({
25
25
  vi.mock('../../validation.js', () => ({
26
26
  validateAddress: vi.fn(),
27
27
  validateBytes32: vi.fn(),
28
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
28
29
  }));
29
30
 
30
31
  const mockEncodeData = vi.fn().mockReturnValue('0xencoded');
@@ -22,6 +22,10 @@ vi.mock('../../stdin.js', () => ({
22
22
  resolveInput: vi.fn((v: string) => Promise.resolve(v)),
23
23
  }));
24
24
 
25
+ vi.mock('../../popular-schemas.js', () => ({
26
+ resolveSchemaUID: vi.fn((v: string) => v),
27
+ }));
28
+
25
29
  const mockEncodeData = vi.fn().mockReturnValue('0xencoded');
26
30
 
27
31
  vi.mock('@ethereum-attestation-service/eas-sdk', () => ({
@@ -21,6 +21,10 @@ vi.mock('../../stdin.js', () => ({
21
21
  resolveInput: vi.fn((v: string) => Promise.resolve(v)),
22
22
  }));
23
23
 
24
+ vi.mock('../../popular-schemas.js', () => ({
25
+ resolveSchemaUID: vi.fn((v: string) => v),
26
+ }));
27
+
24
28
  import { multiRevokeCommand } from '../../commands/multi-revoke.js';
25
29
  import { output, handleError } from '../../output.js';
26
30
 
@@ -28,6 +28,7 @@ vi.mock('../../stdin.js', () => ({
28
28
  }));
29
29
 
30
30
  vi.mock('../../validation.js', () => ({
31
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
31
32
  validateAddress: vi.fn(),
32
33
  validateBytes32: vi.fn(),
33
34
  }));
@@ -16,6 +16,7 @@ vi.mock('../../output.js', () => ({
16
16
  vi.mock('../../validation.js', () => ({
17
17
  validateAddress: vi.fn(),
18
18
  validateBytes32: vi.fn(),
19
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
19
20
  }));
20
21
 
21
22
  import { queryAttestationsCommand } from '../../commands/query-attestations.js';
@@ -14,6 +14,7 @@ vi.mock('../../output.js', () => ({
14
14
 
15
15
  vi.mock('../../validation.js', () => ({
16
16
  validateBytes32: vi.fn(),
17
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
17
18
  }));
18
19
 
19
20
  import { querySchemaCommand } from '../../commands/query-schema.js';
@@ -19,6 +19,7 @@ vi.mock('../../output.js', () => ({
19
19
 
20
20
  vi.mock('../../validation.js', () => ({
21
21
  validateBytes32: vi.fn(),
22
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
22
23
  }));
23
24
 
24
25
  import { revokeCommand } from '../../commands/revoke.js';
@@ -21,6 +21,7 @@ vi.mock('../../output.js', () => ({
21
21
 
22
22
  vi.mock('../../validation.js', () => ({
23
23
  validateBytes32: vi.fn(),
24
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
24
25
  }));
25
26
 
26
27
  import { schemaGetCommand } from '../../commands/schema-get.js';
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ vi.mock('../../output.js', () => ({
4
+ output: vi.fn(),
5
+ handleError: vi.fn(),
6
+ }));
7
+
8
+ vi.mock('../../config.js', () => ({
9
+ getStoredPrivateKey: vi.fn(() => undefined),
10
+ }));
11
+
12
+ import { statusCommand } from '../../commands/status.js';
13
+ import { output } from '../../output.js';
14
+ import { getStoredPrivateKey } from '../../config.js';
15
+
16
+ describe('status command', () => {
17
+ const originalEnv = process.env.EAS_PRIVATE_KEY;
18
+
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ delete process.env.EAS_PRIVATE_KEY;
22
+ });
23
+
24
+ afterEach(() => {
25
+ if (originalEnv !== undefined) {
26
+ process.env.EAS_PRIVATE_KEY = originalEnv;
27
+ } else {
28
+ delete process.env.EAS_PRIVATE_KEY;
29
+ }
30
+ });
31
+
32
+ async function runCommand(args: string[] = []) {
33
+ await statusCommand.parseAsync(['node', 'test', ...args]);
34
+ }
35
+
36
+ it('shows key not set when no key is configured', async () => {
37
+ await runCommand();
38
+
39
+ expect(output).toHaveBeenCalledWith({
40
+ success: true,
41
+ data: expect.objectContaining({
42
+ wallet: { privateKey: 'not set', address: 'n/a' },
43
+ }),
44
+ });
45
+ });
46
+
47
+ it('shows address from env var with source', async () => {
48
+ process.env.EAS_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
49
+ await runCommand();
50
+
51
+ expect(output).toHaveBeenCalledWith({
52
+ success: true,
53
+ data: expect.objectContaining({
54
+ wallet: {
55
+ privateKey: 'set (env)',
56
+ address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
57
+ },
58
+ }),
59
+ });
60
+ });
61
+
62
+ it('shows address from stored key', async () => {
63
+ vi.mocked(getStoredPrivateKey).mockReturnValue('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80');
64
+ await runCommand();
65
+
66
+ const call = (output as any).mock.calls[0][0];
67
+ expect(call.data.wallet.privateKey).toBe('set (stored)');
68
+ expect(call.data.wallet.address).toBe('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266');
69
+ });
70
+
71
+ it('shows address when key has no 0x prefix', async () => {
72
+ process.env.EAS_PRIVATE_KEY = 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
73
+ await runCommand();
74
+
75
+ const call = (output as any).mock.calls[0][0];
76
+ expect(call.data.wallet.privateKey).toBe('set (env)');
77
+ expect(call.data.wallet.address).toBe('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266');
78
+ });
79
+
80
+ it('shows invalid format for a bad key', async () => {
81
+ process.env.EAS_PRIVATE_KEY = 'not-a-real-key';
82
+ await runCommand();
83
+
84
+ const call = (output as any).mock.calls[0][0];
85
+ expect(call.data.wallet.privateKey).toBe('set (invalid format)');
86
+ expect(call.data.wallet.address).toBe('n/a');
87
+ });
88
+
89
+ it('defaults to ethereum chain', async () => {
90
+ await runCommand();
91
+
92
+ const call = (output as any).mock.calls[0][0];
93
+ expect(call.data.chain.name).toBe('ethereum');
94
+ expect(call.data.chain.chainId).toBe(1);
95
+ });
96
+
97
+ it('shows correct config for --chain base', async () => {
98
+ await runCommand(['--chain', 'base']);
99
+
100
+ const call = (output as any).mock.calls[0][0];
101
+ expect(call.data.chain.name).toBe('base');
102
+ expect(call.data.chain.chainId).toBe(8453);
103
+ expect(call.data.contracts.eas).toBe('0x4200000000000000000000000000000000000021');
104
+ });
105
+
106
+ it('uses custom rpc-url when provided', async () => {
107
+ await runCommand(['--rpc-url', 'https://custom.rpc']);
108
+
109
+ const call = (output as any).mock.calls[0][0];
110
+ expect(call.data.chain.rpcUrl).toBe('https://custom.rpc');
111
+ });
112
+
113
+ it('lists supported chains', async () => {
114
+ await runCommand();
115
+
116
+ const call = (output as any).mock.calls[0][0];
117
+ expect(call.data.supportedChains).toContain('ethereum');
118
+ expect(call.data.supportedChains).toContain('base');
119
+ expect(call.data.supportedChains).toContain('sepolia');
120
+ });
121
+ });
@@ -3,11 +3,11 @@ import { SchemaEncoder, NO_EXPIRATION, ZERO_BYTES32 } from '@ethereum-attestatio
3
3
  import { createEASClient } from '../client.js';
4
4
  import { output, handleError } from '../output.js';
5
5
  import { resolveInput } from '../stdin.js';
6
- import { validateAddress, validateBytes32 } from '../validation.js';
6
+ import { validateAddress, resolveAndValidateSchemaUID } from '../validation.js';
7
7
 
8
8
  export const attestCommand = new Command('attest')
9
9
  .description('Create an on-chain attestation')
10
- .requiredOption('-s, --schema <uid>', 'Schema UID to attest with')
10
+ .requiredOption('-s, --schema <uid>', 'Schema UID or popular schema name')
11
11
  .requiredOption('-d, --data <json>', 'Attestation data as JSON array: [{"name":"field","type":"uint256","value":"123"}]')
12
12
  .option('-r, --recipient <address>', 'Recipient address', '0x0000000000000000000000000000000000000000')
13
13
  .option('--ref-uid <uid>', 'Referenced attestation UID', ZERO_BYTES32)
@@ -20,7 +20,7 @@ export const attestCommand = new Command('attest')
20
20
  .option('--dry-run', 'Estimate gas without sending the transaction')
21
21
  .action(async (opts) => {
22
22
  try {
23
- validateBytes32(opts.schema, 'schema UID');
23
+ opts.schema = resolveAndValidateSchemaUID(opts.schema, 'schema UID');
24
24
  if (opts.recipient !== '0x0000000000000000000000000000000000000000') {
25
25
  validateAddress(opts.recipient, 'recipient');
26
26
  }
@@ -3,6 +3,7 @@ import { SchemaEncoder, NO_EXPIRATION, ZERO_BYTES32 } from '@ethereum-attestatio
3
3
  import { createEASClient } from '../client.js';
4
4
  import { output, handleError } from '../output.js';
5
5
  import { resolveInput } from '../stdin.js';
6
+ import { resolveSchemaUID } from '../popular-schemas.js';
6
7
 
7
8
  interface AttestationInput {
8
9
  schema: string;
@@ -34,6 +35,7 @@ export const multiAttestCommand = new Command('multi-attest')
34
35
  const grouped = new Map<string, { schema: string; data: any[] }>();
35
36
 
36
37
  for (const input of inputs) {
38
+ input.schema = resolveSchemaUID(input.schema);
37
39
  const schemaString = input.data.map((item) => `${item.type} ${item.name}`).join(', ');
38
40
  const encoder = new SchemaEncoder(schemaString);
39
41
  const encodedData = encoder.encodeData(input.data as any);
@@ -2,6 +2,7 @@ import { Command } from 'commander';
2
2
  import { createEASClient } from '../client.js';
3
3
  import { output, handleError } from '../output.js';
4
4
  import { resolveInput } from '../stdin.js';
5
+ import { resolveSchemaUID } from '../popular-schemas.js';
5
6
 
6
7
  interface RevocationInput {
7
8
  schema: string;
@@ -29,6 +30,7 @@ export const multiRevokeCommand = new Command('multi-revoke')
29
30
  const grouped = new Map<string, { schema: string; data: any[] }>();
30
31
 
31
32
  for (const input of inputs) {
33
+ input.schema = resolveSchemaUID(input.schema);
32
34
  if (!grouped.has(input.schema)) {
33
35
  grouped.set(input.schema, { schema: input.schema, data: [] });
34
36
  }
@@ -8,12 +8,12 @@ import {
8
8
  import { createEASClient } from '../client.js';
9
9
  import { output, handleError } from '../output.js';
10
10
  import { resolveInput } from '../stdin.js';
11
- import { validateAddress, validateBytes32 } from '../validation.js';
11
+ import { validateAddress, resolveAndValidateSchemaUID } from '../validation.js';
12
12
  import { EASSCAN_URLS } from '../graphql.js';
13
13
 
14
14
  export const offchainAttestCommand = new Command('offchain-attest')
15
15
  .description('Create an off-chain attestation (signed but not submitted on-chain)')
16
- .requiredOption('-s, --schema <uid>', 'Schema UID')
16
+ .requiredOption('-s, --schema <uid>', 'Schema UID or popular schema name')
17
17
  .requiredOption('-d, --data <json>', 'Attestation data as JSON array: [{"name":"field","type":"uint256","value":"123"}]')
18
18
  .option('-r, --recipient <address>', 'Recipient address', '0x0000000000000000000000000000000000000000')
19
19
  .option('--ref-uid <uid>', 'Referenced attestation UID', ZERO_BYTES32)
@@ -24,7 +24,7 @@ export const offchainAttestCommand = new Command('offchain-attest')
24
24
  .option('--rpc-url <url>', 'Custom RPC URL')
25
25
  .action(async (opts) => {
26
26
  try {
27
- validateBytes32(opts.schema, 'schema UID');
27
+ opts.schema = resolveAndValidateSchemaUID(opts.schema, 'schema UID');
28
28
  if (opts.recipient !== '0x0000000000000000000000000000000000000000') {
29
29
  validateAddress(opts.recipient, 'recipient');
30
30
  }
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import { output, isJsonMode, handleError } from '../output.js';
3
+ import { listPopularSchemas, listPopularSchemasByCategory } from '../popular-schemas.js';
4
+
5
+ export const popularSchemasCommand = new Command('popular-schemas')
6
+ .description('List popular EAS schemas with names, UIDs, and descriptions')
7
+ .option('--category <name>', 'Filter by category (general, identity, social)')
8
+ .action(async (opts) => {
9
+ try {
10
+ if (isJsonMode()) {
11
+ const schemas = opts.category
12
+ ? listPopularSchemas().filter((s) => s.category === opts.category)
13
+ : listPopularSchemas();
14
+ if (opts.category && schemas.length === 0) {
15
+ const categories = [...new Set(listPopularSchemas().map((s) => s.category))];
16
+ throw new Error(`Unknown category "${opts.category}". Available: ${categories.join(', ')}`);
17
+ }
18
+ output({ success: true, data: { count: schemas.length, schemas } as any });
19
+ return;
20
+ }
21
+
22
+ const byCategory = listPopularSchemasByCategory();
23
+ const categories = opts.category ? { [opts.category]: byCategory[opts.category] } : byCategory;
24
+
25
+ if (opts.category && !byCategory[opts.category]) {
26
+ const available = Object.keys(byCategory).join(', ');
27
+ throw new Error(`Unknown category "${opts.category}". Available: ${available}`);
28
+ }
29
+
30
+ for (const [category, schemas] of Object.entries(categories)) {
31
+ console.log(`\n=== ${category} ===\n`);
32
+ for (const s of schemas) {
33
+ console.log(` ${s.name}`);
34
+ console.log(` Schema: ${s.schema}`);
35
+ console.log(` UID: ${s.uid}`);
36
+ console.log(` Revocable: ${s.revocable ? 'yes' : 'no'}`);
37
+ console.log(` ${s.description}\n`);
38
+ }
39
+ }
40
+ } catch (err) {
41
+ handleError(err);
42
+ }
43
+ });
@@ -1,11 +1,11 @@
1
1
  import { Command } from 'commander';
2
2
  import { graphqlQuery, QUERIES } from '../graphql.js';
3
3
  import { output, handleError } from '../output.js';
4
- import { validateAddress, validateBytes32 } from '../validation.js';
4
+ import { validateAddress, resolveAndValidateSchemaUID } from '../validation.js';
5
5
 
6
6
  export const queryAttestationsCommand = new Command('query-attestations')
7
7
  .description('Query attestations by schema or attester from the EAS GraphQL API')
8
- .option('-s, --schema <uid>', 'Filter by schema UID')
8
+ .option('-s, --schema <uid>', 'Filter by schema UID or popular schema name')
9
9
  .option('-a, --attester <address>', 'Filter by attester address')
10
10
  .option('-n, --limit <number>', 'Max results to return', '10')
11
11
  .option('--skip <number>', 'Number of results to skip (for pagination)', '0')
@@ -15,7 +15,7 @@ export const queryAttestationsCommand = new Command('query-attestations')
15
15
  if (!opts.schema && !opts.attester) {
16
16
  throw new Error('Provide at least one filter: --schema or --attester');
17
17
  }
18
- if (opts.schema) validateBytes32(opts.schema, 'schema UID');
18
+ if (opts.schema) opts.schema = resolveAndValidateSchemaUID(opts.schema, 'schema UID');
19
19
  if (opts.attester) validateAddress(opts.attester, 'attester');
20
20
 
21
21
  const take = parseInt(opts.limit, 10);
@@ -1,15 +1,15 @@
1
1
  import { Command } from 'commander';
2
2
  import { graphqlQuery, QUERIES } from '../graphql.js';
3
3
  import { output, handleError } from '../output.js';
4
- import { validateBytes32 } from '../validation.js';
4
+ import { resolveAndValidateSchemaUID } from '../validation.js';
5
5
 
6
6
  export const querySchemaCommand = new Command('query-schema')
7
7
  .description('Query a schema from the EAS GraphQL API')
8
- .requiredOption('-u, --uid <uid>', 'Schema UID')
8
+ .requiredOption('-u, --uid <uid>', 'Schema UID or popular schema name')
9
9
  .option('-c, --chain <name>', 'Chain name', 'ethereum')
10
10
  .action(async (opts) => {
11
11
  try {
12
- validateBytes32(opts.uid, 'schema UID');
12
+ opts.uid = resolveAndValidateSchemaUID(opts.uid, 'schema UID');
13
13
 
14
14
  const data = await graphqlQuery(opts.chain, QUERIES.getSchema, { id: opts.uid });
15
15
 
@@ -1,11 +1,11 @@
1
1
  import { Command } from 'commander';
2
2
  import { createEASClient } from '../client.js';
3
3
  import { output, handleError } from '../output.js';
4
- import { validateBytes32 } from '../validation.js';
4
+ import { validateBytes32, resolveAndValidateSchemaUID } from '../validation.js';
5
5
 
6
6
  export const revokeCommand = new Command('revoke')
7
7
  .description('Revoke an on-chain attestation')
8
- .requiredOption('-s, --schema <uid>', 'Schema UID of the attestation')
8
+ .requiredOption('-s, --schema <uid>', 'Schema UID or popular schema name')
9
9
  .requiredOption('-u, --uid <uid>', 'Attestation UID to revoke')
10
10
  .option('--value <wei>', 'ETH value to send (in wei)', '0')
11
11
  .option('-c, --chain <name>', 'Chain name', 'ethereum')
@@ -13,7 +13,7 @@ export const revokeCommand = new Command('revoke')
13
13
  .option('--dry-run', 'Estimate gas without sending the transaction')
14
14
  .action(async (opts) => {
15
15
  try {
16
- validateBytes32(opts.schema, 'schema UID');
16
+ opts.schema = resolveAndValidateSchemaUID(opts.schema, 'schema UID');
17
17
  validateBytes32(opts.uid, 'attestation UID');
18
18
 
19
19
  const client = createEASClient(opts.chain, opts.rpcUrl);
@@ -1,16 +1,16 @@
1
1
  import { Command } from 'commander';
2
2
  import { createReadOnlyEASClient } from '../client.js';
3
3
  import { output, handleError } from '../output.js';
4
- import { validateBytes32 } from '../validation.js';
4
+ import { resolveAndValidateSchemaUID } from '../validation.js';
5
5
 
6
6
  export const schemaGetCommand = new Command('schema-get')
7
7
  .description('Get a schema by UID')
8
- .requiredOption('-u, --uid <uid>', 'Schema UID')
8
+ .requiredOption('-u, --uid <uid>', 'Schema UID or popular schema name')
9
9
  .option('-c, --chain <name>', 'Chain name', 'ethereum')
10
10
  .option('--rpc-url <url>', 'Custom RPC URL')
11
11
  .action(async (opts) => {
12
12
  try {
13
- validateBytes32(opts.uid, 'schema UID');
13
+ opts.uid = resolveAndValidateSchemaUID(opts.uid, 'schema UID');
14
14
 
15
15
  const client = createReadOnlyEASClient(opts.chain, opts.rpcUrl);
16
16
  const schema = await client.schemaRegistry.getSchema({ uid: opts.uid });
@@ -0,0 +1,49 @@
1
+ import { Command } from 'commander';
2
+ import { ethers } from 'ethers';
3
+ import { CHAIN_CONFIGS, listChains } from '../chains.js';
4
+ import { output } from '../output.js';
5
+ import { getStoredPrivateKey } from '../config.js';
6
+
7
+ export const statusCommand = new Command('status')
8
+ .description('Show current configuration: wallet, chain, and contract addresses')
9
+ .option('-c, --chain <name>', 'Chain name', 'ethereum')
10
+ .option('--rpc-url <url>', 'Custom RPC URL')
11
+ .action(async (opts) => {
12
+ const key = process.env.EAS_PRIVATE_KEY || getStoredPrivateKey();
13
+ const source = process.env.EAS_PRIVATE_KEY ? 'env' : key ? 'stored' : undefined;
14
+ let address: string | undefined;
15
+
16
+ if (key) {
17
+ try {
18
+ const normalized = key.startsWith('0x') ? key : `0x${key}`;
19
+ address = new ethers.Wallet(normalized).address;
20
+ } catch {
21
+ // invalid key format
22
+ }
23
+ }
24
+
25
+ const chainConfig = CHAIN_CONFIGS[opts.chain];
26
+ const rpcUrl = opts.rpcUrl || chainConfig?.defaultRpc || 'none';
27
+
28
+ output({
29
+ success: true,
30
+ data: {
31
+ wallet: {
32
+ privateKey: source ? (address ? `set (${source})` : `set (invalid format)`) : 'not set',
33
+ address: address || 'n/a',
34
+ },
35
+ chain: {
36
+ name: opts.chain,
37
+ chainId: chainConfig?.chainId ?? 'unknown chain',
38
+ rpcUrl,
39
+ },
40
+ contracts: chainConfig
41
+ ? {
42
+ eas: chainConfig.eas,
43
+ schemaRegistry: chainConfig.schemaRegistry,
44
+ }
45
+ : 'unknown chain',
46
+ supportedChains: listChains().join(', '),
47
+ },
48
+ });
49
+ });
package/src/index.ts CHANGED
@@ -17,6 +17,8 @@ import { multiRevokeCommand } from './commands/multi-revoke.js';
17
17
  import { multiTimestampCommand } from './commands/multi-timestamp.js';
18
18
  import { setKeyCommand } from './commands/set-key.js';
19
19
  import { clearKeyCommand } from './commands/clear-key.js';
20
+ import { popularSchemasCommand } from './commands/popular-schemas.js';
21
+ import { statusCommand } from './commands/status.js';
20
22
 
21
23
  const program = new Command();
22
24
 
@@ -42,6 +44,7 @@ program.addCommand(getAttestationCommand);
42
44
  // Schema commands
43
45
  program.addCommand(schemaRegisterCommand);
44
46
  program.addCommand(schemaGetCommand);
47
+ program.addCommand(popularSchemasCommand);
45
48
 
46
49
  // Timestamp commands
47
50
  program.addCommand(timestampCommand);
@@ -57,6 +60,9 @@ program.addCommand(querySchemasCommand);
57
60
  program.addCommand(setKeyCommand);
58
61
  program.addCommand(clearKeyCommand);
59
62
 
63
+ // Status command
64
+ program.addCommand(statusCommand);
65
+
60
66
  // Chains command
61
67
  program
62
68
  .command('chains')
@@ -0,0 +1,119 @@
1
+ export interface PopularSchema {
2
+ name: string;
3
+ schema: string;
4
+ uid: string;
5
+ description: string;
6
+ revocable: boolean;
7
+ category: string;
8
+ }
9
+
10
+ // UIDs are deterministic: keccak256(abi.encodePacked(schema, address(0), revocable))
11
+ // Same UID on every chain (no resolver).
12
+ export const POPULAR_SCHEMAS: PopularSchema[] = [
13
+ // General
14
+ {
15
+ name: 'make-a-statement',
16
+ schema: 'string statement',
17
+ uid: '0xf58b8b212ef75ee8cd7e8d803c37c03e0519890502d5e99ee2412aae1456cafe',
18
+ description: 'General-purpose text statement or note',
19
+ revocable: true,
20
+ category: 'general',
21
+ },
22
+ {
23
+ name: 'is-true',
24
+ schema: 'bool isTrue',
25
+ uid: '0x4eb603f49d68888d7f8b1fadd351b35a252f287ba465408ceb2b1e1e1efd90d5',
26
+ description: 'Simple boolean assertion',
27
+ revocable: true,
28
+ category: 'general',
29
+ },
30
+ {
31
+ name: 'score',
32
+ schema: 'uint256 score',
33
+ uid: '0xef2dbf5e8da46ea760bb4c6eb2635bf04adfc1ade6158e594263363db2a55bcf',
34
+ description: 'Numeric score or rating',
35
+ revocable: true,
36
+ category: 'general',
37
+ },
38
+ {
39
+ name: 'tag',
40
+ schema: 'bytes32 tag',
41
+ uid: '0x7d105b048bfc4c781474627045d2fb9008016018b3f2af686eef31ee6d1d5857',
42
+ description: 'Arbitrary tag or label',
43
+ revocable: true,
44
+ category: 'general',
45
+ },
46
+ // Identity
47
+ {
48
+ name: 'is-a-human',
49
+ schema: 'bool isHuman',
50
+ uid: '0x8af15e65888f2e3b487e536a4922e277dcfe85b4b18187b0cf9afdb802ba6bb6',
51
+ description: 'Humanity verification attestation',
52
+ revocable: true,
53
+ category: 'identity',
54
+ },
55
+ {
56
+ name: 'verified-account',
57
+ schema: 'string platform, string username',
58
+ uid: '0xa0ce1ea8fd393b308ec22d86a9a4451bb16e61f455f964374f2105a371077d02',
59
+ description: 'Social account verification (platform + username)',
60
+ revocable: true,
61
+ category: 'identity',
62
+ },
63
+ // Social
64
+ {
65
+ name: 'is-a-friend',
66
+ schema: 'bool isFriend',
67
+ uid: '0x27d06e3659317e9a4f8154d1e849eb53d43d91fb4f219884d1684f86d797804a',
68
+ description: 'Friendship attestation',
69
+ revocable: true,
70
+ category: 'social',
71
+ },
72
+ {
73
+ name: 'met-irl',
74
+ schema: 'bool metIRL',
75
+ uid: '0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7',
76
+ description: 'Attest that you met someone in real life',
77
+ revocable: true,
78
+ category: 'social',
79
+ },
80
+ {
81
+ name: 'vouch',
82
+ schema: 'address vouched, string context',
83
+ uid: '0xec3decee9f94f4ef4d1a95b23743ac4904b1b8687164e266564945efd435cf48',
84
+ description: 'Vouch for an address with context',
85
+ revocable: true,
86
+ category: 'social',
87
+ },
88
+ {
89
+ name: 'endorsement',
90
+ schema: 'string endorsement',
91
+ uid: '0xb7fb3a3cae206db4784b42600aaaa640d20babf3b2d4b45161c4856f2b6c6aec',
92
+ description: 'Open-ended endorsement',
93
+ revocable: true,
94
+ category: 'social',
95
+ },
96
+ ];
97
+
98
+ const byName = new Map(POPULAR_SCHEMAS.map((s) => [s.name, s]));
99
+
100
+ export function getPopularSchemaByName(name: string): PopularSchema | undefined {
101
+ return byName.get(name);
102
+ }
103
+
104
+ export function resolveSchemaUID(value: string): string {
105
+ const schema = byName.get(value);
106
+ return schema ? schema.uid : value;
107
+ }
108
+
109
+ export function listPopularSchemas(): PopularSchema[] {
110
+ return POPULAR_SCHEMAS;
111
+ }
112
+
113
+ export function listPopularSchemasByCategory(): Record<string, PopularSchema[]> {
114
+ const grouped: Record<string, PopularSchema[]> = {};
115
+ for (const s of POPULAR_SCHEMAS) {
116
+ (grouped[s.category] ??= []).push(s);
117
+ }
118
+ return grouped;
119
+ }
package/src/validation.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { resolveSchemaUID } from './popular-schemas.js';
2
+
1
3
  const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
2
4
 
3
5
  export function validateAddress(value: string, label: string): void {
@@ -13,3 +15,9 @@ export function validateBytes32(value: string, label: string): void {
13
15
  throw new Error(`Invalid ${label}: expected 0x + 64 hex characters, got "${value}"`);
14
16
  }
15
17
  }
18
+
19
+ export function resolveAndValidateSchemaUID(value: string, label: string): string {
20
+ const resolved = resolveSchemaUID(value);
21
+ validateBytes32(resolved, label);
22
+ return resolved;
23
+ }