@aixyz/cli 0.10.0 → 0.11.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/build/index.ts CHANGED
@@ -49,6 +49,8 @@ Examples:
49
49
  async function action(options: BuildOptions = {}): Promise<void> {
50
50
  const cwd = process.cwd();
51
51
  loadEnvConfig(cwd, false);
52
+ process.env.NODE_ENV = "production";
53
+ process.env.AIXYZ_ENV = "production";
52
54
  const entrypoint = getEntrypointMayGenerate(cwd, "build");
53
55
 
54
56
  // Determine output target: explicit CLI flag takes precedence, then config file, then auto-detect VERCEL env
@@ -79,6 +81,10 @@ async function buildBun(entrypoint: string): Promise<void> {
79
81
  target: "bun",
80
82
  format: "esm",
81
83
  sourcemap: "linked",
84
+ define: {
85
+ "process.env.NODE_ENV": JSON.stringify("production"),
86
+ "process.env.AIXYZ_ENV": JSON.stringify("production"),
87
+ },
82
88
  plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "standalone")],
83
89
  });
84
90
 
@@ -136,6 +142,10 @@ async function buildVercel(entrypoint: string): Promise<void> {
136
142
  target: "bun",
137
143
  format: "esm",
138
144
  sourcemap: "linked",
145
+ define: {
146
+ "process.env.NODE_ENV": JSON.stringify("production"),
147
+ "process.env.AIXYZ_ENV": JSON.stringify("production"),
148
+ },
139
149
  plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "vercel")],
140
150
  });
141
151
 
package/dev/index.ts CHANGED
@@ -15,6 +15,8 @@ async function action(options: { port?: string }): Promise<void> {
15
15
 
16
16
  // Load environment config
17
17
  const { loadedEnvFiles } = loadEnvConfig(cwd, true);
18
+ process.env.NODE_ENV = "development";
19
+ process.env.AIXYZ_ENV = "development";
18
20
  const envFileNames = loadedEnvFiles.map((f) => relative(cwd, f.path));
19
21
 
20
22
  const port = options.port || process.env.PORT || "3000";
@@ -41,7 +43,7 @@ async function action(options: { port?: string }): Promise<void> {
41
43
  cwd,
42
44
  stdout: "inherit",
43
45
  stderr: "inherit",
44
- env: process.env,
46
+ env: { ...process.env, NODE_ENV: "development", AIXYZ_ENV: "development" },
45
47
  });
46
48
  child.exited.then((code) => {
47
49
  if (!restarting && code !== 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aixyz/cli",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Payment-native SDK for AI Agent",
5
5
  "keywords": [
6
6
  "ai",
@@ -27,8 +27,8 @@
27
27
  "bin.ts"
28
28
  ],
29
29
  "dependencies": {
30
- "@aixyz/config": "0.10.0",
31
- "@aixyz/erc-8004": "0.10.0",
30
+ "@aixyz/config": "0.11.0",
31
+ "@aixyz/erc-8004": "0.11.0",
32
32
  "@inquirer/prompts": "^8.3.0",
33
33
  "@next/env": "^16.1.6",
34
34
  "boxen": "^8.0.1",
package/register/index.ts CHANGED
@@ -4,7 +4,6 @@ import { update } from "./update";
4
4
  import type { WalletOptions } from "./wallet";
5
5
 
6
6
  export interface BaseOptions extends WalletOptions {
7
- chain?: string;
8
7
  rpcUrl?: string;
9
8
  registry?: string;
10
9
  outDir?: string;
@@ -16,9 +15,9 @@ erc8004Command
16
15
  .command("register")
17
16
  .description("Register a new agent to the ERC-8004 IdentityRegistry")
18
17
  .option("--url <url>", "Agent deployment URL (e.g., https://my-agent.example.com)")
19
- .option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
18
+ .option("--chain-id <chainId>", "Target chain by numeric chain ID", parseInt)
20
19
  .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
21
- .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
20
+ .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost and custom chains)")
22
21
  .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
23
22
  .option("--browser", "Use browser extension wallet (any extension)")
24
23
  .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
@@ -32,22 +31,23 @@ Option Details:
32
31
  The registration URI will be derived as <url>/_aixyz/erc-8004.json.
33
32
  If omitted, you will be prompted to enter the URL interactively.
34
33
 
35
- --chain <chain>
36
- Target chain for registration. Supported values:
37
- mainnet Ethereum mainnet (chain ID 1)
38
- sepolia Ethereum Sepolia testnet (chain ID 11155111)
39
- base-sepolia Base Sepolia testnet (chain ID 84532)
40
- localhost Local Foundry/Anvil node (chain ID 31337)
34
+ --chain-id <chainId>
35
+ Target chain by numeric chain ID. Supported chain IDs include:
36
+ 1 (mainnet), 8453 (base), 42161 (arbitrum), 10 (optimism),
37
+ 137 (polygon), 56 (bsc), 43114 (avalanche), 11155111 (sepolia),
38
+ 84532 (base-sepolia), and more (see @aixyz/erc-8004).
41
39
  If omitted, you will be prompted to select a chain interactively.
40
+ Use any custom EVM chain ID with --rpc-url for BYO chains.
42
41
 
43
42
  --rpc-url <url>
44
43
  Custom RPC endpoint URL. Overrides the default RPC for the selected
45
- chain. Cannot be used with --browser since the browser wallet manages
44
+ chain. Required when using a custom chain ID with no default deployment.
45
+ Cannot be used with --browser since the browser wallet manages
46
46
  its own RPC connection.
47
47
 
48
48
  --registry <address>
49
- Contract address of the ERC-8004 IdentityRegistry. Only required for
50
- localhost, where there is no default deployment.
49
+ Contract address of the ERC-8004 IdentityRegistry. Required for
50
+ localhost and custom chains, where there is no default deployment.
51
51
 
52
52
  --keystore <path>
53
53
  Path to an Ethereum keystore (V3) JSON file. You will be prompted for
@@ -74,11 +74,14 @@ Environment Variables:
74
74
 
75
75
  Examples:
76
76
  # Dry-run (default)
77
- $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia
77
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain-id 11155111
78
78
 
79
- # Sign and broadcast
80
- $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
81
- $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --browser --broadcast`,
79
+ # Sign and broadcast (known chain)
80
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain-id 84532 --keystore ~/.foundry/keystores/default --broadcast
81
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain-id 84532 --browser --broadcast
82
+
83
+ # BYO: register on any custom EVM chain
84
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain-id 999999 --rpc-url https://my-rpc.example.com --registry 0xABCD... --broadcast`,
82
85
  )
83
86
  .action(register);
84
87
 
@@ -3,15 +3,16 @@ import { IdentityRegistryAbi } from "@aixyz/erc-8004";
3
3
  import { selectWalletMethod } from "./wallet";
4
4
  import { signTransaction } from "./wallet/sign";
5
5
  import {
6
- resolveChainConfig,
6
+ resolveChainConfigById,
7
7
  selectChain,
8
8
  resolveRegistryAddress,
9
9
  validateBrowserRpcConflict,
10
10
  getExplorerUrl,
11
+ CHAINS,
11
12
  } from "./utils/chain";
12
13
  import { writeResultJson } from "./utils/result";
13
14
  import { label, truncateUri, broadcastAndConfirm, logSignResult } from "./utils/transaction";
14
- import { promptAgentUrl, promptSupportedTrust, deriveAgentUri } from "./utils/prompt";
15
+ import { promptAgentUrl, promptSupportedTrust, promptRegistryAddress, deriveAgentUri } from "./utils/prompt";
15
16
  import { hasErc8004File, createErc8004File, writeRegistrationEntry } from "./utils/erc8004-file";
16
17
  import { confirm } from "@inquirer/prompts";
17
18
  import chalk from "chalk";
@@ -20,7 +21,7 @@ import type { BaseOptions } from "./index";
20
21
 
21
22
  export interface RegisterOptions extends BaseOptions {
22
23
  url?: string;
23
- chain?: string;
24
+ chainId?: number;
24
25
  }
25
26
 
26
27
  export async function register(options: RegisterOptions): Promise<void> {
@@ -47,9 +48,10 @@ export async function register(options: RegisterOptions): Promise<void> {
47
48
  }
48
49
 
49
50
  // Step 3: Select chain
50
- const chainName = options.chain ?? (await selectChain());
51
- const chainConfig = resolveChainConfig(chainName);
52
- const registryAddress = resolveRegistryAddress(chainName, chainConfig.chainId, options.registry);
51
+ const chainId = options.chainId ?? (await selectChain());
52
+ const chainConfig = resolveChainConfigById(chainId, options.rpcUrl);
53
+ const chainName = Object.entries(CHAINS).find(([, c]) => c.chainId === chainId)?.[0] ?? `chain-${chainId}`;
54
+ const registryAddress = resolveRegistryAddress(chainId, options.registry) ?? (await promptRegistryAddress());
53
55
 
54
56
  // Step 4: Encode transaction
55
57
  const data = encodeFunctionData({
@@ -2,7 +2,7 @@ import { encodeFunctionData, formatEther, parseEventLogs, type Chain, type Log }
2
2
  import { IdentityRegistryAbi } from "@aixyz/erc-8004";
3
3
  import { selectWalletMethod } from "./wallet";
4
4
  import { signTransaction } from "./wallet/sign";
5
- import { resolveChainConfig, validateBrowserRpcConflict, getExplorerUrl, CHAINS } from "./utils/chain";
5
+ import { resolveChainConfigById, validateBrowserRpcConflict, getExplorerUrl, CHAINS } from "./utils/chain";
6
6
  import { writeResultJson } from "./utils/result";
7
7
  import { label, truncateUri, broadcastAndConfirm, logSignResult } from "./utils/transaction";
8
8
  import { promptAgentUrl, promptSelectRegistration, deriveAgentUri } from "./utils/prompt";
@@ -36,7 +36,7 @@ export async function update(options: UpdateOptions): Promise<void> {
36
36
  const chainId = Number(parts[1]);
37
37
  const registryAddress = parts.slice(2).join(":") as `0x${string}`;
38
38
  const chainName = Object.entries(CHAINS).find(([, config]) => config.chainId === chainId)?.[0] ?? `chain-${chainId}`;
39
- const chainConfig = resolveChainConfig(chainName);
39
+ const chainConfig = resolveChainConfigById(chainId, options.rpcUrl);
40
40
 
41
41
  // Step 4: Get new agent URL and derive URI
42
42
  const agentUrl = options.url ?? (await promptAgentUrl());
@@ -1,20 +1,128 @@
1
- import { isAddress, type Chain } from "viem";
2
- import { mainnet, sepolia, baseSepolia, foundry } from "viem/chains";
1
+ import { isAddress, defineChain, type Chain } from "viem";
2
+ import {
3
+ abstract,
4
+ abstractTestnet,
5
+ arbitrum,
6
+ arbitrumSepolia,
7
+ avalanche,
8
+ avalancheFuji,
9
+ base,
10
+ baseSepolia,
11
+ bsc,
12
+ bscTestnet,
13
+ celo,
14
+ celoSepolia,
15
+ foundry,
16
+ gnosis,
17
+ linea,
18
+ lineaSepolia,
19
+ mainnet,
20
+ mantle,
21
+ mantleSepoliaTestnet,
22
+ megaeth,
23
+ monad,
24
+ monadTestnet,
25
+ optimism,
26
+ optimismSepolia,
27
+ polygon,
28
+ polygonAmoy,
29
+ scroll,
30
+ scrollSepolia,
31
+ sepolia,
32
+ taiko,
33
+ } from "viem/chains";
3
34
  import { CHAIN_ID, getIdentityRegistryAddress } from "@aixyz/erc-8004";
4
- import { select } from "@inquirer/prompts";
35
+ import { input, select } from "@inquirer/prompts";
5
36
 
6
37
  export interface ChainConfig {
7
38
  chain: Chain;
8
39
  chainId: number;
9
40
  }
10
41
 
42
+ // Maps supported chain IDs to viem Chain objects (derived from @aixyz/erc-8004 CHAIN_ID)
43
+ const VIEM_CHAIN_BY_ID: Record<number, Chain> = {
44
+ [CHAIN_ID.ABSTRACT]: abstract,
45
+ [CHAIN_ID.ARBITRUM]: arbitrum,
46
+ [CHAIN_ID.AVALANCHE]: avalanche,
47
+ [CHAIN_ID.BASE]: base,
48
+ [CHAIN_ID.BSC]: bsc,
49
+ [CHAIN_ID.CELO]: celo,
50
+ [CHAIN_ID.GNOSIS]: gnosis,
51
+ [CHAIN_ID.LINEA]: linea,
52
+ [CHAIN_ID.MAINNET]: mainnet,
53
+ [CHAIN_ID.MANTLE]: mantle,
54
+ [CHAIN_ID.MEGAETH]: megaeth,
55
+ [CHAIN_ID.MONAD]: monad,
56
+ [CHAIN_ID.OPTIMISM]: optimism,
57
+ [CHAIN_ID.POLYGON]: polygon,
58
+ [CHAIN_ID.SCROLL]: scroll,
59
+ [CHAIN_ID.TAIKO]: taiko,
60
+ [CHAIN_ID.ABSTRACT_TESTNET]: abstractTestnet,
61
+ [CHAIN_ID.ARBITRUM_SEPOLIA]: arbitrumSepolia,
62
+ [CHAIN_ID.AVALANCHE_FUJI]: avalancheFuji,
63
+ [CHAIN_ID.BASE_SEPOLIA]: baseSepolia,
64
+ [CHAIN_ID.BSC_TESTNET]: bscTestnet,
65
+ [CHAIN_ID.CELO_SEPOLIA]: celoSepolia,
66
+ [CHAIN_ID.LINEA_SEPOLIA]: lineaSepolia,
67
+ [CHAIN_ID.MANTLE_SEPOLIA]: mantleSepoliaTestnet,
68
+ [CHAIN_ID.MONAD_TESTNET]: monadTestnet,
69
+ [CHAIN_ID.OPTIMISM_SEPOLIA]: optimismSepolia,
70
+ [CHAIN_ID.POLYGON_AMOY]: polygonAmoy,
71
+ [CHAIN_ID.SCROLL_SEPOLIA]: scrollSepolia,
72
+ [CHAIN_ID.SEPOLIA]: sepolia,
73
+ 31337: foundry,
74
+ };
75
+
76
+ // Build CHAINS from CHAIN_ID as the single source of truth from @aixyz/erc-8004.
77
+ // Chain names are derived from CHAIN_ID keys: lowercase with underscores replaced by hyphens.
11
78
  export const CHAINS: Record<string, ChainConfig> = {
12
- mainnet: { chain: mainnet, chainId: CHAIN_ID.MAINNET },
13
- sepolia: { chain: sepolia, chainId: CHAIN_ID.SEPOLIA },
14
- "base-sepolia": { chain: baseSepolia, chainId: CHAIN_ID.BASE_SEPOLIA },
79
+ ...Object.fromEntries(
80
+ (Object.entries(CHAIN_ID) as [string, number][])
81
+ .filter(([, id]) => id in VIEM_CHAIN_BY_ID)
82
+ .map(([key, id]) => [key.toLowerCase().replace(/_/g, "-"), { chain: VIEM_CHAIN_BY_ID[id]!, chainId: id }]),
83
+ ),
15
84
  localhost: { chain: foundry, chainId: 31337 },
16
85
  };
17
86
 
87
+ // Priority-ordered chain IDs for interactive selection — most popular first.
88
+ const CHAIN_SELECTION_ORDER: number[] = [
89
+ // Popular mainnets
90
+ CHAIN_ID.MAINNET,
91
+ CHAIN_ID.BASE,
92
+ CHAIN_ID.ARBITRUM,
93
+ CHAIN_ID.OPTIMISM,
94
+ CHAIN_ID.POLYGON,
95
+ CHAIN_ID.BSC,
96
+ CHAIN_ID.AVALANCHE,
97
+ CHAIN_ID.SCROLL,
98
+ CHAIN_ID.LINEA,
99
+ CHAIN_ID.CELO,
100
+ CHAIN_ID.GNOSIS,
101
+ CHAIN_ID.TAIKO,
102
+ CHAIN_ID.MANTLE,
103
+ CHAIN_ID.MONAD,
104
+ CHAIN_ID.MEGAETH,
105
+ CHAIN_ID.ABSTRACT,
106
+ // Popular testnets
107
+ CHAIN_ID.SEPOLIA,
108
+ CHAIN_ID.BASE_SEPOLIA,
109
+ CHAIN_ID.ARBITRUM_SEPOLIA,
110
+ CHAIN_ID.OPTIMISM_SEPOLIA,
111
+ CHAIN_ID.POLYGON_AMOY,
112
+ CHAIN_ID.AVALANCHE_FUJI,
113
+ CHAIN_ID.BSC_TESTNET,
114
+ CHAIN_ID.SCROLL_SEPOLIA,
115
+ CHAIN_ID.LINEA_SEPOLIA,
116
+ CHAIN_ID.CELO_SEPOLIA,
117
+ CHAIN_ID.MANTLE_SEPOLIA,
118
+ CHAIN_ID.MONAD_TESTNET,
119
+ CHAIN_ID.ABSTRACT_TESTNET,
120
+ // Special
121
+ 31337,
122
+ ];
123
+
124
+ const OTHER_CHAIN_ID = -1;
125
+
18
126
  export function resolveChainConfig(chainName: string): ChainConfig {
19
127
  const config = CHAINS[chainName];
20
128
  if (!config) {
@@ -23,24 +131,73 @@ export function resolveChainConfig(chainName: string): ChainConfig {
23
131
  return config;
24
132
  }
25
133
 
26
- export async function selectChain(): Promise<string> {
27
- return select({
28
- message: "Select target chain:",
29
- choices: Object.keys(CHAINS).map((name) => ({ name, value: name })),
30
- });
134
+ // Resolve chain config by numeric chain ID, supporting BYO chains via rpcUrl.
135
+ // For known chain IDs the viem chain object is used directly.
136
+ // For unknown chain IDs, a minimal chain is constructed using defineChain (requires rpcUrl).
137
+ export function resolveChainConfigById(chainId: number, rpcUrl?: string): ChainConfig {
138
+ const chain = VIEM_CHAIN_BY_ID[chainId];
139
+ if (chain) {
140
+ return { chain, chainId };
141
+ }
142
+ if (!rpcUrl) {
143
+ throw new Error(`Unknown chain ID ${chainId}. Provide --rpc-url to register on a custom chain.`);
144
+ }
145
+ return {
146
+ chain: defineChain({
147
+ id: chainId,
148
+ name: `chain-${chainId}`,
149
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
150
+ rpcUrls: { default: { http: [rpcUrl] } },
151
+ }),
152
+ chainId,
153
+ };
154
+ }
155
+
156
+ // Prompt user to select a chain interactively, returning the numeric chain ID.
157
+ // Chains are sorted by popularity. "Other" allows entering any custom chain ID.
158
+ export async function selectChain(): Promise<number> {
159
+ const chainById = new Map(Object.values(CHAINS).map((c) => [c.chainId, c]));
160
+ const nameById = new Map(Object.entries(CHAINS).map(([name, c]) => [c.chainId, name]));
161
+
162
+ const choices = CHAIN_SELECTION_ORDER.filter((id) => chainById.has(id)).map((id) => ({
163
+ name: `${nameById.get(id) ?? `chain-${id}`} (${id})`,
164
+ value: id,
165
+ }));
166
+ choices.push({ name: "Other (enter chain ID)", value: OTHER_CHAIN_ID });
167
+
168
+ const selected = await select({ message: "Select target chain:", choices });
169
+
170
+ if (selected === OTHER_CHAIN_ID) {
171
+ const raw = await input({
172
+ message: "Enter chain ID:",
173
+ validate: (v) => {
174
+ const n = parseInt(v, 10);
175
+ return Number.isInteger(n) && n > 0 ? true : "Must be a positive integer";
176
+ },
177
+ });
178
+ return parseInt(raw, 10);
179
+ }
180
+
181
+ return selected;
31
182
  }
32
183
 
33
- export function resolveRegistryAddress(chainName: string, chainId: number, registry?: string): `0x${string}` {
184
+ // Returns the registry address if known, or null if no default exists for the chain (requires interactive prompt).
185
+ // Throws only for an explicitly invalid registry address.
186
+ export function resolveRegistryAddress(chainId: number, registry?: string): `0x${string}` | null {
34
187
  if (registry) {
35
188
  if (!isAddress(registry)) {
36
189
  throw new Error(`Invalid registry address: ${registry}`);
37
190
  }
38
191
  return registry as `0x${string}`;
39
192
  }
40
- if (chainName === "localhost") {
41
- throw new Error("--registry is required for localhost (no default contract deployment)");
193
+ if (chainId === 31337) {
194
+ return null;
195
+ }
196
+ try {
197
+ return getIdentityRegistryAddress(chainId) as `0x${string}`;
198
+ } catch {
199
+ return null;
42
200
  }
43
- return getIdentityRegistryAddress(chainId) as `0x${string}`;
44
201
  }
45
202
 
46
203
  export function validateBrowserRpcConflict(browser: boolean | undefined, rpcUrl: string | undefined): void {
@@ -1,4 +1,5 @@
1
1
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
2
+ import { isAddress } from "viem";
2
3
  import type { RegistrationEntry } from "@aixyz/erc-8004/schemas/registration";
3
4
 
4
5
  export async function promptAgentUrl(): Promise<string> {
@@ -54,6 +55,14 @@ export async function promptSelectRegistration(registrations: RegistrationEntry[
54
55
  });
55
56
  }
56
57
 
58
+ export async function promptRegistryAddress(): Promise<`0x${string}`> {
59
+ const value = await input({
60
+ message: "IdentityRegistry contract address (no default for this chain):",
61
+ validate: (v) => (isAddress(v) ? true : "Must be a valid Ethereum address (0x…)"),
62
+ });
63
+ return value as `0x${string}`;
64
+ }
65
+
57
66
  export function deriveAgentUri(url: string): string {
58
67
  // Ensure no trailing slash before appending path
59
68
  const base = url.replace(/\/+$/, "");