@aixyz/cli 0.8.0 → 0.9.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/bin.ts +43 -81
- package/build/AixyzConfigPlugin.ts +1 -1
- package/build/AixyzServerPlugin.ts +10 -0
- package/package.json +3 -3
- package/register/register.test.ts +0 -9
- package/register/register.ts +39 -13
- package/register/update.test.ts +47 -0
- package/register/{set-agent-uri.ts → update.ts} +39 -40
- package/register/utils/chain.ts +4 -5
- package/register/utils/erc8004-file.ts +65 -0
- package/register/utils/prompt.ts +61 -1
- package/register/utils.test.ts +4 -77
- package/register/utils.ts +3 -20
- package/register/wallet/browser.test.ts +18 -0
- package/register/wallet/browser.ts +15 -10
- package/register/wallet/index.ts +2 -3
- package/register/wallet/keystore.test.ts +1 -1
- package/register/wallet/keystore.ts +2 -3
- package/register/wallet/privatekey.ts +8 -3
- package/register/wallet/sign.ts +8 -5
- package/register/README.md +0 -101
- package/register/set-agent-uri.test.ts +0 -156
package/bin.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { program } from "commander";
|
|
|
3
3
|
import { build } from "./build";
|
|
4
4
|
import { dev } from "./dev";
|
|
5
5
|
import { register } from "./register/register";
|
|
6
|
-
import {
|
|
7
|
-
import { CliError } from "./register/utils";
|
|
6
|
+
import { update } from "./register/update";
|
|
8
7
|
import pkg from "./package.json";
|
|
9
8
|
|
|
10
9
|
function handleAction(
|
|
@@ -14,10 +13,6 @@ function handleAction(
|
|
|
14
13
|
try {
|
|
15
14
|
await action(options);
|
|
16
15
|
} catch (error) {
|
|
17
|
-
if (error instanceof CliError) {
|
|
18
|
-
console.error(`Error: ${error.message}`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
16
|
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
22
17
|
process.exit(130);
|
|
23
18
|
}
|
|
@@ -70,12 +65,12 @@ Examples:
|
|
|
70
65
|
)
|
|
71
66
|
.action(handleAction(build));
|
|
72
67
|
|
|
73
|
-
const erc8004 = program.command("
|
|
68
|
+
const erc8004 = program.command("erc-8004").description("ERC-8004 IdentityRegistry operations");
|
|
74
69
|
|
|
75
70
|
erc8004
|
|
76
71
|
.command("register")
|
|
77
72
|
.description("Register a new agent to the ERC-8004 IdentityRegistry")
|
|
78
|
-
.option("--
|
|
73
|
+
.option("--url <url>", "Agent deployment URL (e.g., https://my-agent.example.com)")
|
|
79
74
|
.option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
|
|
80
75
|
.option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
|
|
81
76
|
.option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
|
|
@@ -87,12 +82,10 @@ erc8004
|
|
|
87
82
|
"after",
|
|
88
83
|
`
|
|
89
84
|
Option Details:
|
|
90
|
-
--
|
|
91
|
-
Agent
|
|
92
|
-
|
|
93
|
-
If
|
|
94
|
-
Otherwise, the URI is used as-is and the validity of the URI is not checked.
|
|
95
|
-
If omitted, the agent is registered without metadata.
|
|
85
|
+
--url <url>
|
|
86
|
+
Agent deployment URL (e.g., https://my-agent.example.com).
|
|
87
|
+
The registration URI will be derived as <url>/_aixyz/erc-8004.json.
|
|
88
|
+
If omitted, you will be prompted to enter the URL interactively.
|
|
96
89
|
|
|
97
90
|
--chain <chain>
|
|
98
91
|
Target chain for registration. Supported values:
|
|
@@ -101,7 +94,6 @@ Option Details:
|
|
|
101
94
|
base-sepolia Base Sepolia testnet (chain ID 84532)
|
|
102
95
|
localhost Local Foundry/Anvil node (chain ID 31337)
|
|
103
96
|
If omitted, you will be prompted to select a chain interactively.
|
|
104
|
-
Each chain has a default RPC endpoint unless overridden with --rpc-url.
|
|
105
97
|
|
|
106
98
|
--rpc-url <url>
|
|
107
99
|
Custom RPC endpoint URL. Overrides the default RPC for the selected
|
|
@@ -110,8 +102,7 @@ Option Details:
|
|
|
110
102
|
|
|
111
103
|
--registry <address>
|
|
112
104
|
Contract address of the ERC-8004 IdentityRegistry. Only required for
|
|
113
|
-
localhost, where there is no default deployment.
|
|
114
|
-
and base-sepolia the canonical registry address is used automatically.
|
|
105
|
+
localhost, where there is no default deployment.
|
|
115
106
|
|
|
116
107
|
--keystore <path>
|
|
117
108
|
Path to an Ethereum keystore (V3) JSON file. You will be prompted for
|
|
@@ -120,42 +111,36 @@ Option Details:
|
|
|
120
111
|
--browser
|
|
121
112
|
Opens a local page in your default browser for signing with any
|
|
122
113
|
EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
|
|
123
|
-
The wallet handles both signing and broadcasting the transaction.
|
|
124
|
-
Cannot be combined with --rpc-url.
|
|
125
114
|
|
|
126
115
|
--broadcast
|
|
127
116
|
Sign and broadcast the transaction on-chain. Without this flag the
|
|
128
|
-
command performs a dry-run
|
|
129
|
-
its details but does not interact with any wallet or send anything
|
|
130
|
-
to the network.
|
|
117
|
+
command performs a dry-run.
|
|
131
118
|
|
|
132
119
|
--out-dir <path>
|
|
133
|
-
Directory to write the deployment result as a JSON file.
|
|
134
|
-
|
|
120
|
+
Directory to write the deployment result as a JSON file.
|
|
121
|
+
|
|
122
|
+
Behavior:
|
|
123
|
+
If app/erc-8004.ts does not exist, you will be prompted to create it
|
|
124
|
+
(selecting supported trust mechanisms). After a successful on-chain
|
|
125
|
+
registration, the new registration entry is written back to app/erc-8004.ts.
|
|
135
126
|
|
|
136
127
|
Environment Variables:
|
|
137
|
-
PRIVATE_KEY Private key (hex, with or without 0x prefix) used for
|
|
138
|
-
signing. Detected automatically if set. Not recommended
|
|
139
|
-
for interactive use as the key may appear in shell history.
|
|
128
|
+
PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
|
|
140
129
|
|
|
141
130
|
Examples:
|
|
142
|
-
# Dry-run (default)
|
|
143
|
-
$ aixyz
|
|
131
|
+
# Dry-run (default)
|
|
132
|
+
$ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia
|
|
144
133
|
|
|
145
134
|
# Sign and broadcast
|
|
146
|
-
$ aixyz
|
|
147
|
-
$
|
|
148
|
-
$ aixyz erc8004 register --chain localhost --registry 0x5FbDB2315678afecb367f032d93F642f64180aa3 --uri "./metadata.json" --broadcast
|
|
149
|
-
$ aixyz erc8004 register --uri "./metadata.json" --chain sepolia --browser --broadcast`,
|
|
135
|
+
$ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
|
|
136
|
+
$ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --browser --broadcast`,
|
|
150
137
|
)
|
|
151
138
|
.action(handleAction(register));
|
|
152
139
|
|
|
153
140
|
erc8004
|
|
154
|
-
.command("
|
|
141
|
+
.command("update")
|
|
155
142
|
.description("Update the metadata URI of a registered agent")
|
|
156
|
-
.option("--
|
|
157
|
-
.option("--uri <uri>", "New agent metadata URI or path to .json file")
|
|
158
|
-
.option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
|
|
143
|
+
.option("--url <url>", "New agent deployment URL (e.g., https://my-agent.example.com)")
|
|
159
144
|
.option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
|
|
160
145
|
.option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
|
|
161
146
|
.option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
|
|
@@ -166,73 +151,50 @@ erc8004
|
|
|
166
151
|
"after",
|
|
167
152
|
`
|
|
168
153
|
Option Details:
|
|
169
|
-
--
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
If omitted, you will be prompted to enter the agent ID interactively.
|
|
174
|
-
|
|
175
|
-
--uri <uri>
|
|
176
|
-
New agent metadata as a URI or local file path. Accepts http://, https://,
|
|
177
|
-
ipfs://, and data: URIs directly.
|
|
178
|
-
If a .json file path is given, it is read and converted to a base64 data URI automatically.
|
|
179
|
-
Otherwise, the URI is used as-is and the validity of the URI is not checked.
|
|
180
|
-
If omitted, you will be prompted to enter the URI interactively.
|
|
181
|
-
|
|
182
|
-
--chain <chain>
|
|
183
|
-
Target chain. Supported values:
|
|
184
|
-
mainnet Ethereum mainnet (chain ID 1)
|
|
185
|
-
sepolia Ethereum Sepolia testnet (chain ID 11155111)
|
|
186
|
-
base-sepolia Base Sepolia testnet (chain ID 84532)
|
|
187
|
-
localhost Local Foundry/Anvil node (chain ID 31337)
|
|
188
|
-
If omitted, you will be prompted to select a chain interactively.
|
|
189
|
-
Each chain has a default RPC endpoint unless overridden with --rpc-url.
|
|
154
|
+
--url <url>
|
|
155
|
+
New agent deployment URL (e.g., https://my-agent.example.com).
|
|
156
|
+
The URI will be derived as <url>/_aixyz/erc-8004.json.
|
|
157
|
+
If omitted, you will be prompted to enter the URL interactively.
|
|
190
158
|
|
|
191
159
|
--rpc-url <url>
|
|
192
160
|
Custom RPC endpoint URL. Overrides the default RPC for the selected
|
|
193
|
-
chain. Cannot be used with --browser
|
|
194
|
-
its own RPC connection.
|
|
161
|
+
chain. Cannot be used with --browser.
|
|
195
162
|
|
|
196
163
|
--registry <address>
|
|
197
164
|
Contract address of the ERC-8004 IdentityRegistry. Only required for
|
|
198
|
-
localhost, where there is no default deployment.
|
|
199
|
-
and base-sepolia the canonical registry address is used automatically.
|
|
165
|
+
localhost, where there is no default deployment.
|
|
200
166
|
|
|
201
167
|
--keystore <path>
|
|
202
|
-
Path to an Ethereum keystore (V3) JSON file.
|
|
203
|
-
the keystore password to decrypt the private key for signing.
|
|
168
|
+
Path to an Ethereum keystore (V3) JSON file.
|
|
204
169
|
|
|
205
170
|
--browser
|
|
206
171
|
Opens a local page in your default browser for signing with any
|
|
207
172
|
EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
|
|
208
|
-
The wallet handles both signing and broadcasting the transaction.
|
|
209
|
-
Cannot be combined with --rpc-url.
|
|
210
173
|
|
|
211
174
|
--broadcast
|
|
212
175
|
Sign and broadcast the transaction on-chain. Without this flag the
|
|
213
|
-
command performs a dry-run
|
|
214
|
-
its details but does not interact with any wallet or send anything
|
|
215
|
-
to the network.
|
|
176
|
+
command performs a dry-run.
|
|
216
177
|
|
|
217
178
|
--out-dir <path>
|
|
218
|
-
Directory to write the result as a JSON file.
|
|
219
|
-
|
|
179
|
+
Directory to write the result as a JSON file.
|
|
180
|
+
|
|
181
|
+
Behavior:
|
|
182
|
+
Reads existing registrations from app/erc-8004.ts. If there is one
|
|
183
|
+
registration, confirms it. If multiple, prompts you to select which
|
|
184
|
+
one to update. The chain and registry address are derived from the
|
|
185
|
+
selected registration's agentRegistry field.
|
|
220
186
|
|
|
221
187
|
Environment Variables:
|
|
222
|
-
PRIVATE_KEY Private key (hex, with or without 0x prefix) used for
|
|
223
|
-
signing. Detected automatically if set. Not recommended
|
|
224
|
-
for interactive use as the key may appear in shell history.
|
|
188
|
+
PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
|
|
225
189
|
|
|
226
190
|
Examples:
|
|
227
|
-
# Dry-run (default)
|
|
228
|
-
$ aixyz
|
|
191
|
+
# Dry-run (default)
|
|
192
|
+
$ aixyz erc-8004 update --url "https://new-domain.example.com"
|
|
229
193
|
|
|
230
194
|
# Sign and broadcast
|
|
231
|
-
$ aixyz
|
|
232
|
-
$
|
|
233
|
-
$ aixyz erc8004 set-agent-uri --agent-id 1 --uri "./metadata.json" --chain localhost --registry 0x5FbDB2315678afecb367f032d93F642f64180aa3 --broadcast
|
|
234
|
-
$ aixyz erc8004 set-agent-uri --agent-id 1 --uri "./metadata.json" --chain sepolia --browser --broadcast`,
|
|
195
|
+
$ aixyz erc-8004 update --url "https://new-domain.example.com" --keystore ~/.foundry/keystores/default --broadcast
|
|
196
|
+
$ aixyz erc-8004 update --url "https://new-domain.example.com" --browser --broadcast`,
|
|
235
197
|
)
|
|
236
|
-
.action(handleAction(
|
|
198
|
+
.action(handleAction(update));
|
|
237
199
|
|
|
238
200
|
program.parse();
|
|
@@ -37,7 +37,7 @@ export function AixyzConfigPlugin(): BunPlugin {
|
|
|
37
37
|
return {
|
|
38
38
|
name: "aixyz-config",
|
|
39
39
|
setup(build) {
|
|
40
|
-
build.onLoad({ filter: /aixyz\/
|
|
40
|
+
build.onLoad({ filter: /aixyz[-/]config\/index\.ts$/ }, () => ({
|
|
41
41
|
contents: `
|
|
42
42
|
const config = ${JSON.stringify(materialized)};
|
|
43
43
|
export function getAixyzConfig() {
|
|
@@ -127,6 +127,16 @@ function generateServer(appDir: string, entrypointDir: string): string {
|
|
|
127
127
|
body.push("await mcp.connect();");
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// If app/erc-8004.ts exists, auto-register ERC-8004 endpoint
|
|
131
|
+
const hasErc8004 = existsSync(resolve(appDir, "erc-8004.ts"));
|
|
132
|
+
if (hasErc8004) {
|
|
133
|
+
imports.push('import { useERC8004 } from "aixyz/server/adapters/erc-8004";');
|
|
134
|
+
imports.push(`import * as erc8004 from "${importPrefix}/erc-8004";`);
|
|
135
|
+
body.push(
|
|
136
|
+
`useERC8004(server, { default: erc8004.default, options: { mcp: ${tools.length > 0}, a2a: ${hasAgent} } });`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
130
140
|
body.push("export default server;");
|
|
131
141
|
|
|
132
142
|
return [...imports, "", ...body].join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aixyz/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.
|
|
31
|
-
"@aixyz/erc-8004": "0.
|
|
30
|
+
"@aixyz/config": "0.9.0",
|
|
31
|
+
"@aixyz/erc-8004": "0.9.0",
|
|
32
32
|
"@inquirer/prompts": "^8.3.0",
|
|
33
33
|
"@next/env": "^16.1.6",
|
|
34
34
|
"boxen": "^8.0.1",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { CHAIN_ID, getIdentityRegistryAddress } from "@aixyz/erc-8004";
|
|
3
|
-
import { register } from "./register";
|
|
4
3
|
|
|
5
4
|
describe("register command chain configuration", () => {
|
|
6
5
|
test("sepolia chain ID is correct", () => {
|
|
@@ -64,12 +63,4 @@ describe("register command validation", () => {
|
|
|
64
63
|
};
|
|
65
64
|
expect(CHAINS["mainnet"]).toBeUndefined();
|
|
66
65
|
});
|
|
67
|
-
|
|
68
|
-
test("localhost requires --registry flag", async () => {
|
|
69
|
-
await expect(register({ chain: "localhost" })).rejects.toThrow("--registry is required for localhost");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("dry-run completes without wallet interaction when --broadcast is not set", async () => {
|
|
73
|
-
await expect(register({ chain: "sepolia", uri: "https://example.com/agent.json" })).resolves.toBeUndefined();
|
|
74
|
-
});
|
|
75
66
|
});
|
package/register/register.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { encodeFunctionData, formatEther, parseEventLogs, type Chain, type Log }
|
|
|
2
2
|
import { IdentityRegistryAbi } from "@aixyz/erc-8004";
|
|
3
3
|
import { selectWalletMethod, type WalletOptions } from "./wallet";
|
|
4
4
|
import { signTransaction } from "./wallet/sign";
|
|
5
|
-
import { resolveUri } from "./utils";
|
|
6
5
|
import {
|
|
7
6
|
resolveChainConfig,
|
|
8
7
|
selectChain,
|
|
@@ -12,30 +11,51 @@ import {
|
|
|
12
11
|
} from "./utils/chain";
|
|
13
12
|
import { writeResultJson } from "./utils/result";
|
|
14
13
|
import { label, truncateUri, broadcastAndConfirm, logSignResult } from "./utils/transaction";
|
|
14
|
+
import { promptAgentUrl, promptSupportedTrust, deriveAgentUri } from "./utils/prompt";
|
|
15
|
+
import { hasErc8004File, createErc8004File, writeRegistrationEntry } from "./utils/erc8004-file";
|
|
16
|
+
import { confirm } from "@inquirer/prompts";
|
|
15
17
|
import chalk from "chalk";
|
|
16
18
|
import boxen from "boxen";
|
|
17
19
|
import type { BaseOptions } from "./index";
|
|
18
20
|
|
|
19
21
|
export interface RegisterOptions extends BaseOptions {
|
|
20
|
-
|
|
22
|
+
url?: string;
|
|
21
23
|
chain?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export async function register(options: RegisterOptions): Promise<void> {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// Step 1: Ensure app/erc-8004.ts exists
|
|
28
|
+
if (!hasErc8004File()) {
|
|
29
|
+
console.log(chalk.yellow("No app/erc-8004.ts found. Let's create one."));
|
|
30
|
+
console.log("");
|
|
31
|
+
const supportedTrust = await promptSupportedTrust();
|
|
32
|
+
createErc8004File(supportedTrust);
|
|
33
|
+
console.log(chalk.green("Created app/erc-8004.ts"));
|
|
34
|
+
console.log("");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Step 2: Get agent URL and derive URI
|
|
38
|
+
const agentUrl = options.url ?? (await promptAgentUrl());
|
|
39
|
+
const resolvedUri = deriveAgentUri(agentUrl);
|
|
27
40
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
const yes = await confirm({
|
|
42
|
+
message: `Will register URI as: ${chalk.cyan(resolvedUri)} — confirm?`,
|
|
43
|
+
default: true,
|
|
44
|
+
});
|
|
45
|
+
if (!yes) {
|
|
46
|
+
throw new Error("Aborted.");
|
|
31
47
|
}
|
|
32
48
|
|
|
49
|
+
// Step 3: Select chain
|
|
50
|
+
const chainName = options.chain ?? (await selectChain());
|
|
51
|
+
const chainConfig = resolveChainConfig(chainName);
|
|
33
52
|
const registryAddress = resolveRegistryAddress(chainName, chainConfig.chainId, options.registry);
|
|
34
53
|
|
|
54
|
+
// Step 4: Encode transaction
|
|
35
55
|
const data = encodeFunctionData({
|
|
36
56
|
abi: IdentityRegistryAbi,
|
|
37
57
|
functionName: "register",
|
|
38
|
-
args:
|
|
58
|
+
args: [resolvedUri],
|
|
39
59
|
});
|
|
40
60
|
|
|
41
61
|
const printTxDetails = (header: string) => {
|
|
@@ -44,10 +64,8 @@ export async function register(options: RegisterOptions): Promise<void> {
|
|
|
44
64
|
console.log(` ${label("To")}${registryAddress}`);
|
|
45
65
|
console.log(` ${label("Data")}${data.slice(0, 10)}${chalk.dim("\u2026" + (data.length - 2) / 2 + " bytes")}`);
|
|
46
66
|
console.log(` ${label("Chain")}${chainName}`);
|
|
47
|
-
console.log(` ${label("Function")}
|
|
48
|
-
|
|
49
|
-
console.log(` ${label("URI")}${truncateUri(resolvedUri)}`);
|
|
50
|
-
}
|
|
67
|
+
console.log(` ${label("Function")}register(string memory agentURI)`);
|
|
68
|
+
console.log(` ${label("URI")}${truncateUri(resolvedUri)}`);
|
|
51
69
|
console.log("");
|
|
52
70
|
};
|
|
53
71
|
|
|
@@ -73,7 +91,7 @@ export async function register(options: RegisterOptions): Promise<void> {
|
|
|
73
91
|
chain: chainConfig.chain,
|
|
74
92
|
rpcUrl: options.rpcUrl,
|
|
75
93
|
options: {
|
|
76
|
-
browser: { chainId: chainConfig.chainId, chainName, uri: resolvedUri },
|
|
94
|
+
browser: { chainId: chainConfig.chainId, chainName, uri: resolvedUri, mode: "register" },
|
|
77
95
|
},
|
|
78
96
|
});
|
|
79
97
|
logSignResult(walletMethod.type, result);
|
|
@@ -86,6 +104,14 @@ export async function register(options: RegisterOptions): Promise<void> {
|
|
|
86
104
|
|
|
87
105
|
const resultData = printResult(receipt, timestamp, chainConfig.chain, chainConfig.chainId, hash);
|
|
88
106
|
|
|
107
|
+
// Step 5: Write registration entry back to app/erc-8004.ts
|
|
108
|
+
if (resultData.agentId !== undefined) {
|
|
109
|
+
const agentRegistry = `eip155:${chainConfig.chainId}:${registryAddress}`;
|
|
110
|
+
writeRegistrationEntry({ agentId: Number(resultData.agentId), agentRegistry });
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(chalk.green(`Updated app/erc-8004.ts with registration (agentId: ${resultData.agentId})`));
|
|
113
|
+
}
|
|
114
|
+
|
|
89
115
|
if (options.outDir) {
|
|
90
116
|
writeResultJson(options.outDir, "registration", resultData);
|
|
91
117
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { CHAIN_ID, getIdentityRegistryAddress } from "@aixyz/erc-8004";
|
|
3
|
+
import { deriveAgentUri } from "./utils/prompt";
|
|
4
|
+
|
|
5
|
+
describe("update command chain configuration", () => {
|
|
6
|
+
test("sepolia chain ID is correct", () => {
|
|
7
|
+
expect(CHAIN_ID.SEPOLIA).toStrictEqual(11155111);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("base-sepolia chain ID is correct", () => {
|
|
11
|
+
expect(CHAIN_ID.BASE_SEPOLIA).toStrictEqual(84532);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("identity registry address is returned for sepolia", () => {
|
|
15
|
+
const address = getIdentityRegistryAddress(CHAIN_ID.SEPOLIA);
|
|
16
|
+
expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("identity registry address is returned for base-sepolia", () => {
|
|
20
|
+
const address = getIdentityRegistryAddress(CHAIN_ID.BASE_SEPOLIA);
|
|
21
|
+
expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("throws for unsupported chain ID", () => {
|
|
25
|
+
expect(() => getIdentityRegistryAddress(999999)).toThrow("Unsupported chain ID");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("deriveAgentUri", () => {
|
|
30
|
+
test("appends /_aixyz/erc-8004.json to base URL", () => {
|
|
31
|
+
expect(deriveAgentUri("https://my-agent.example.com")).toBe("https://my-agent.example.com/_aixyz/erc-8004.json");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("strips trailing slash before appending", () => {
|
|
35
|
+
expect(deriveAgentUri("https://my-agent.example.com/")).toBe("https://my-agent.example.com/_aixyz/erc-8004.json");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("strips multiple trailing slashes", () => {
|
|
39
|
+
expect(deriveAgentUri("https://my-agent.example.com///")).toBe("https://my-agent.example.com/_aixyz/erc-8004.json");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("preserves path segments", () => {
|
|
43
|
+
expect(deriveAgentUri("https://example.com/agents/my-agent")).toBe(
|
|
44
|
+
"https://example.com/agents/my-agent/_aixyz/erc-8004.json",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -2,66 +2,65 @@ 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 { CliError, resolveUri } from "./utils";
|
|
6
5
|
import {
|
|
7
6
|
resolveChainConfig,
|
|
8
|
-
selectChain,
|
|
9
7
|
resolveRegistryAddress,
|
|
10
8
|
validateBrowserRpcConflict,
|
|
11
9
|
getExplorerUrl,
|
|
10
|
+
CHAINS,
|
|
12
11
|
} from "./utils/chain";
|
|
13
12
|
import { writeResultJson } from "./utils/result";
|
|
14
13
|
import { label, truncateUri, broadcastAndConfirm, logSignResult } from "./utils/transaction";
|
|
15
|
-
import {
|
|
14
|
+
import { promptAgentUrl, promptSelectRegistration, deriveAgentUri } from "./utils/prompt";
|
|
15
|
+
import { readRegistrations } from "./utils/erc8004-file";
|
|
16
|
+
import { confirm } from "@inquirer/prompts";
|
|
16
17
|
import chalk from "chalk";
|
|
17
18
|
import boxen from "boxen";
|
|
18
19
|
import type { BaseOptions } from "./index";
|
|
19
|
-
import { promptAgentId, promptUri } from "./utils/prompt";
|
|
20
20
|
|
|
21
|
-
export interface
|
|
22
|
-
|
|
23
|
-
uri?: string;
|
|
21
|
+
export interface UpdateOptions extends BaseOptions {
|
|
22
|
+
url?: string;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
async function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throw new CliError("Aborted.");
|
|
25
|
+
export async function update(options: UpdateOptions): Promise<void> {
|
|
26
|
+
// Step 1: Read registrations from app/erc-8004.ts
|
|
27
|
+
const registrations = await readRegistrations();
|
|
28
|
+
|
|
29
|
+
if (registrations.length === 0) {
|
|
30
|
+
throw new Error("No registrations found in app/erc-8004.ts. Run `aixyz erc-8004 register` first.");
|
|
33
31
|
}
|
|
34
|
-
}
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
// Step 2: Select which registration to update
|
|
34
|
+
const selected = await promptSelectRegistration(registrations);
|
|
35
|
+
|
|
36
|
+
// Step 3: Derive chain info from agentRegistry (eip155:<chainId>:<address>)
|
|
37
|
+
const parts = selected.agentRegistry.split(":");
|
|
38
|
+
if (parts.length < 3 || parts[0] !== "eip155") {
|
|
39
|
+
throw new Error(`Invalid agentRegistry format: ${selected.agentRegistry}. Expected eip155:<chainId>:<address>`);
|
|
40
40
|
}
|
|
41
|
-
}
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
const
|
|
42
|
+
const chainId = Number(parts[1]);
|
|
43
|
+
const registryAddress = parts.slice(2).join(":") as `0x${string}`;
|
|
44
|
+
const chainName = Object.entries(CHAINS).find(([, config]) => config.chainId === chainId)?.[0] ?? `chain-${chainId}`;
|
|
45
45
|
const chainConfig = resolveChainConfig(chainName);
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Step 4: Get new agent URL and derive URI
|
|
48
|
+
const agentUrl = options.url ?? (await promptAgentUrl());
|
|
49
|
+
const resolvedUri = deriveAgentUri(agentUrl);
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(`Resolved ${uri} to data URI (${resolvedUri.length} chars)`);
|
|
51
|
+
const yes = await confirm({
|
|
52
|
+
message: `Will update URI to: ${chalk.cyan(resolvedUri)} — confirm?`,
|
|
53
|
+
default: true,
|
|
54
|
+
});
|
|
55
|
+
if (!yes) {
|
|
56
|
+
throw new Error("Aborted.");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// Step 5: Encode transaction
|
|
61
60
|
const data = encodeFunctionData({
|
|
62
61
|
abi: IdentityRegistryAbi,
|
|
63
62
|
functionName: "setAgentURI",
|
|
64
|
-
args: [BigInt(agentId), resolvedUri],
|
|
63
|
+
args: [BigInt(selected.agentId), resolvedUri],
|
|
65
64
|
});
|
|
66
65
|
|
|
67
66
|
const printTxDetails = (header: string) => {
|
|
@@ -71,7 +70,7 @@ export async function setAgentUri(options: SetAgentUriOptions): Promise<void> {
|
|
|
71
70
|
console.log(` ${label("Data")}${data.slice(0, 10)}${chalk.dim("\u2026" + (data.length - 2) / 2 + " bytes")}`);
|
|
72
71
|
console.log(` ${label("Chain")}${chainName}`);
|
|
73
72
|
console.log(` ${label("Function")}setAgentURI(uint256 agentId, string calldata newURI)`);
|
|
74
|
-
console.log(` ${label("Agent ID")}${agentId}`);
|
|
73
|
+
console.log(` ${label("Agent ID")}${selected.agentId}`);
|
|
75
74
|
console.log(` ${label("URI")}${truncateUri(resolvedUri)}`);
|
|
76
75
|
console.log("");
|
|
77
76
|
};
|
|
@@ -98,7 +97,7 @@ export async function setAgentUri(options: SetAgentUriOptions): Promise<void> {
|
|
|
98
97
|
chain: chainConfig.chain,
|
|
99
98
|
rpcUrl: options.rpcUrl,
|
|
100
99
|
options: {
|
|
101
|
-
browser: { chainId: chainConfig.chainId, chainName, uri: resolvedUri },
|
|
100
|
+
browser: { chainId: chainConfig.chainId, chainName, uri: resolvedUri, mode: "update" },
|
|
102
101
|
},
|
|
103
102
|
});
|
|
104
103
|
logSignResult(walletMethod.type, result);
|
|
@@ -112,11 +111,11 @@ export async function setAgentUri(options: SetAgentUriOptions): Promise<void> {
|
|
|
112
111
|
const resultData = printResult(receipt, timestamp, chainConfig.chain, chainConfig.chainId, hash);
|
|
113
112
|
|
|
114
113
|
if (options.outDir) {
|
|
115
|
-
writeResultJson(options.outDir, "
|
|
114
|
+
writeResultJson(options.outDir, "update", resultData);
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
interface
|
|
118
|
+
interface UpdateResult {
|
|
120
119
|
agentId?: string;
|
|
121
120
|
newUri?: string;
|
|
122
121
|
updatedBy?: `0x${string}`;
|
|
@@ -135,12 +134,12 @@ function printResult(
|
|
|
135
134
|
chain: Chain,
|
|
136
135
|
chainId: number,
|
|
137
136
|
hash: `0x${string}`,
|
|
138
|
-
):
|
|
137
|
+
): UpdateResult {
|
|
139
138
|
const events = parseEventLogs({ abi: IdentityRegistryAbi, logs: receipt.logs as Log[] });
|
|
140
139
|
const uriUpdated = events.find((e) => e.eventName === "URIUpdated");
|
|
141
140
|
|
|
142
141
|
const lines: string[] = [];
|
|
143
|
-
const result:
|
|
142
|
+
const result: UpdateResult = {
|
|
144
143
|
chainId,
|
|
145
144
|
block: receipt.blockNumber.toString(),
|
|
146
145
|
timestamp: new Date(Number(timestamp) * 1000).toUTCString(),
|
package/register/utils/chain.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { isAddress, type Chain } from "viem";
|
|
|
2
2
|
import { mainnet, sepolia, baseSepolia, foundry } from "viem/chains";
|
|
3
3
|
import { CHAIN_ID, getIdentityRegistryAddress } from "@aixyz/erc-8004";
|
|
4
4
|
import { select } from "@inquirer/prompts";
|
|
5
|
-
import { CliError } from "../utils";
|
|
6
5
|
|
|
7
6
|
export interface ChainConfig {
|
|
8
7
|
chain: Chain;
|
|
@@ -19,7 +18,7 @@ export const CHAINS: Record<string, ChainConfig> = {
|
|
|
19
18
|
export function resolveChainConfig(chainName: string): ChainConfig {
|
|
20
19
|
const config = CHAINS[chainName];
|
|
21
20
|
if (!config) {
|
|
22
|
-
throw new
|
|
21
|
+
throw new Error(`Unsupported chain: ${chainName}. Supported chains: ${Object.keys(CHAINS).join(", ")}`);
|
|
23
22
|
}
|
|
24
23
|
return config;
|
|
25
24
|
}
|
|
@@ -34,19 +33,19 @@ export async function selectChain(): Promise<string> {
|
|
|
34
33
|
export function resolveRegistryAddress(chainName: string, chainId: number, registry?: string): `0x${string}` {
|
|
35
34
|
if (registry) {
|
|
36
35
|
if (!isAddress(registry)) {
|
|
37
|
-
throw new
|
|
36
|
+
throw new Error(`Invalid registry address: ${registry}`);
|
|
38
37
|
}
|
|
39
38
|
return registry as `0x${string}`;
|
|
40
39
|
}
|
|
41
40
|
if (chainName === "localhost") {
|
|
42
|
-
throw new
|
|
41
|
+
throw new Error("--registry is required for localhost (no default contract deployment)");
|
|
43
42
|
}
|
|
44
43
|
return getIdentityRegistryAddress(chainId) as `0x${string}`;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export function validateBrowserRpcConflict(browser: boolean | undefined, rpcUrl: string | undefined): void {
|
|
48
47
|
if (browser && rpcUrl) {
|
|
49
|
-
throw new
|
|
48
|
+
throw new Error("--rpc-url cannot be used with browser wallet. The browser wallet uses its own RPC endpoint.");
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|