easctl 0.1.3 → 0.2.1

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.1",
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
  }));
@@ -5,6 +5,7 @@ vi.mock('../../graphql.js', () => ({
5
5
  QUERIES: {
6
6
  getAttestationsBySchema: 'query BySchema',
7
7
  getAttestationsByAttester: 'query ByAttester',
8
+ getAttestationsByRecipient: 'query ByRecipient',
8
9
  },
9
10
  }));
10
11
 
@@ -16,6 +17,7 @@ vi.mock('../../output.js', () => ({
16
17
  vi.mock('../../validation.js', () => ({
17
18
  validateAddress: vi.fn(),
18
19
  validateBytes32: vi.fn(),
20
+ resolveAndValidateSchemaUID: vi.fn((v: string) => v),
19
21
  }));
20
22
 
21
23
  import { queryAttestationsCommand } from '../../commands/query-attestations.js';
@@ -61,6 +63,20 @@ describe('query-attestations command', () => {
61
63
  });
62
64
  });
63
65
 
66
+ it('queries by recipient', async () => {
67
+ (graphqlQuery as any).mockResolvedValue({
68
+ attestations: [{ id: '0x1' }],
69
+ });
70
+
71
+ await runCommand(['-r', '0xRecipient']);
72
+
73
+ expect(graphqlQuery).toHaveBeenCalledWith('ethereum', QUERIES.getAttestationsByRecipient, {
74
+ recipient: '0xRecipient',
75
+ take: 10,
76
+ skip: 0,
77
+ });
78
+ });
79
+
64
80
  it('passes skip for pagination', async () => {
65
81
  (graphqlQuery as any).mockResolvedValue({ attestations: [] });
66
82
 
@@ -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
+ });
@@ -65,6 +65,16 @@ describe('GraphQL live queries (sepolia)', () => {
65
65
  expect(Array.isArray(data.attestations)).toBe(true);
66
66
  });
67
67
 
68
+ it('getAttestationsByRecipient returns attestations array', async () => {
69
+ const data = await graphqlQuery(chain, QUERIES.getAttestationsByRecipient, {
70
+ recipient: '0x0000000000000000000000000000000000000000',
71
+ take: 1,
72
+ });
73
+
74
+ expect(data.attestations).toBeDefined();
75
+ expect(Array.isArray(data.attestations)).toBe(true);
76
+ });
77
+
68
78
  it('getAttestation returns a single attestation with all expected fields', async () => {
69
79
  // First get a real attestation ID by querying recent attestations by a known schema
70
80
  // We use getSchemata to find a schema, then query its attestations
@@ -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,22 +1,24 @@
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
+ .option('-r, --recipient <address>', 'Filter by recipient address')
10
11
  .option('-n, --limit <number>', 'Max results to return', '10')
11
12
  .option('--skip <number>', 'Number of results to skip (for pagination)', '0')
12
13
  .option('-c, --chain <name>', 'Chain name', 'ethereum')
13
14
  .action(async (opts) => {
14
15
  try {
15
- if (!opts.schema && !opts.attester) {
16
- throw new Error('Provide at least one filter: --schema or --attester');
16
+ if (!opts.schema && !opts.attester && !opts.recipient) {
17
+ throw new Error('Provide at least one filter: --schema, --attester, or --recipient');
17
18
  }
18
- if (opts.schema) validateBytes32(opts.schema, 'schema UID');
19
+ if (opts.schema) opts.schema = resolveAndValidateSchemaUID(opts.schema, 'schema UID');
19
20
  if (opts.attester) validateAddress(opts.attester, 'attester');
21
+ if (opts.recipient) validateAddress(opts.recipient, 'recipient');
20
22
 
21
23
  const take = parseInt(opts.limit, 10);
22
24
  const skip = parseInt(opts.skip, 10);
@@ -30,12 +32,18 @@ export const queryAttestationsCommand = new Command('query-attestations')
30
32
  take,
31
33
  skip,
32
34
  });
33
- } else {
35
+ } else if (opts.attester) {
34
36
  data = await graphqlQuery(opts.chain, QUERIES.getAttestationsByAttester, {
35
37
  attester: opts.attester,
36
38
  take,
37
39
  skip,
38
40
  });
41
+ } else {
42
+ data = await graphqlQuery(opts.chain, QUERIES.getAttestationsByRecipient, {
43
+ recipient: opts.recipient,
44
+ take,
45
+ skip,
46
+ });
39
47
  }
40
48
 
41
49
  const attestations = data.attestations || [];
@@ -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/graphql.ts CHANGED
@@ -117,6 +117,24 @@ export const QUERIES = {
117
117
  }
118
118
  }
119
119
  `,
120
+ getAttestationsByRecipient: `
121
+ query GetAttestationsByRecipient($recipient: String!, $take: Int, $skip: Int) {
122
+ attestations(
123
+ where: { recipient: { equals: $recipient } }
124
+ take: $take
125
+ skip: $skip
126
+ orderBy: [{ time: desc }]
127
+ ) {
128
+ id
129
+ attester
130
+ schemaId
131
+ time
132
+ revoked
133
+ decodedDataJson
134
+ isOffchain
135
+ }
136
+ }
137
+ `,
120
138
  getSchemata: `
121
139
  query GetSchemata($take: Int, $skip: Int) {
122
140
  schemata(
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')