a3stack 0.1.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/a3stack.js +391 -0
- package/dist/cli.js +391 -0
- package/package.json +42 -0
package/bin/a3stack.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { createPublicClient, http, getAddress } from "viem";
|
|
5
|
+
var REGISTRY_ADDRESS = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
|
|
6
|
+
var REGISTRY_ABI = [
|
|
7
|
+
{ inputs: [{ name: "agentId", type: "uint256" }], name: "ownerOf", outputs: [{ type: "address" }], stateMutability: "view", type: "function" },
|
|
8
|
+
{ inputs: [{ name: "agentId", type: "uint256" }], name: "tokenURI", outputs: [{ type: "string" }], stateMutability: "view", type: "function" },
|
|
9
|
+
{ inputs: [{ name: "owner", type: "address" }], name: "balanceOf", outputs: [{ type: "uint256" }], stateMutability: "view", type: "function" }
|
|
10
|
+
];
|
|
11
|
+
var TRANSFER_EVENT = {
|
|
12
|
+
type: "event",
|
|
13
|
+
name: "Transfer",
|
|
14
|
+
inputs: [
|
|
15
|
+
{ type: "address", indexed: true, name: "from" },
|
|
16
|
+
{ type: "address", indexed: true, name: "to" },
|
|
17
|
+
{ type: "uint256", indexed: true, name: "tokenId" }
|
|
18
|
+
]
|
|
19
|
+
};
|
|
20
|
+
var SUPPORTED_CHAINS = {
|
|
21
|
+
1: { name: "Ethereum", rpc: "https://eth.llamarpc.com" },
|
|
22
|
+
10: { name: "Optimism", rpc: "https://mainnet.optimism.io" },
|
|
23
|
+
56: { name: "BNB Chain", rpc: "https://bsc-dataseed.binance.org" },
|
|
24
|
+
100: { name: "Gnosis", rpc: "https://rpc.gnosischain.com" },
|
|
25
|
+
130: { name: "Unichain", rpc: "https://mainnet.unichain.org" },
|
|
26
|
+
137: { name: "Polygon", rpc: "https://polygon-rpc.com" },
|
|
27
|
+
196: { name: "X Layer", rpc: "https://rpc.xlayer.tech" },
|
|
28
|
+
480: { name: "World Chain", rpc: "https://worldchain-mainnet.g.alchemy.com/public" },
|
|
29
|
+
690: { name: "Redstone", rpc: "https://rpc.redstonechain.com" },
|
|
30
|
+
8453: { name: "Base", rpc: "https://mainnet.base.org" },
|
|
31
|
+
42161: { name: "Arbitrum", rpc: "https://arb1.arbitrum.io/rpc" },
|
|
32
|
+
42170: { name: "Arbitrum Nova", rpc: "https://nova.arbitrum.io/rpc" },
|
|
33
|
+
43114: { name: "Avalanche", rpc: "https://api.avax.network/ext/bc/C/rpc" },
|
|
34
|
+
57073: { name: "Ink", rpc: "https://rpc-gel.inkonchain.com" },
|
|
35
|
+
81457: { name: "Blast", rpc: "https://rpc.blast.io" },
|
|
36
|
+
534352: { name: "Scroll", rpc: "https://rpc.scroll.io" },
|
|
37
|
+
7777777: { name: "Zora", rpc: "https://rpc.zora.energy" }
|
|
38
|
+
};
|
|
39
|
+
function parseGlobalId(id) {
|
|
40
|
+
const match = id.match(/^eip155:(\d+):(0x[a-fA-F0-9]{40})#(\d+)$/);
|
|
41
|
+
if (!match) throw new Error(`Invalid global ID format: ${id}
|
|
42
|
+
Expected: eip155:<chainId>:<registry>#<agentId>`);
|
|
43
|
+
return { chainId: Number(match[1]), registry: match[2], agentId: BigInt(match[3]) };
|
|
44
|
+
}
|
|
45
|
+
function getClient(chainId) {
|
|
46
|
+
const chain = SUPPORTED_CHAINS[chainId];
|
|
47
|
+
if (!chain) throw new Error(`Unsupported chain: ${chainId}. Run 'a3stack chains' to see supported chains.`);
|
|
48
|
+
return createPublicClient({ transport: http(chain.rpc) });
|
|
49
|
+
}
|
|
50
|
+
function bold(s) {
|
|
51
|
+
return `\x1B[1m${s}\x1B[0m`;
|
|
52
|
+
}
|
|
53
|
+
function green(s) {
|
|
54
|
+
return `\x1B[32m${s}\x1B[0m`;
|
|
55
|
+
}
|
|
56
|
+
function red(s) {
|
|
57
|
+
return `\x1B[31m${s}\x1B[0m`;
|
|
58
|
+
}
|
|
59
|
+
function dim(s) {
|
|
60
|
+
return `\x1B[2m${s}\x1B[0m`;
|
|
61
|
+
}
|
|
62
|
+
function cyan(s) {
|
|
63
|
+
return `\x1B[36m${s}\x1B[0m`;
|
|
64
|
+
}
|
|
65
|
+
function yellow(s) {
|
|
66
|
+
return `\x1B[33m${s}\x1B[0m`;
|
|
67
|
+
}
|
|
68
|
+
async function verify(globalId) {
|
|
69
|
+
console.log(`
|
|
70
|
+
${bold("\u{1F50D} Verifying agent identity")}
|
|
71
|
+
`);
|
|
72
|
+
const ref = parseGlobalId(globalId);
|
|
73
|
+
const chainName = SUPPORTED_CHAINS[ref.chainId]?.name ?? `Chain ${ref.chainId}`;
|
|
74
|
+
console.log(` Chain: ${cyan(chainName)} (${ref.chainId})`);
|
|
75
|
+
console.log(` Registry: ${dim(ref.registry)}`);
|
|
76
|
+
console.log(` Agent ID: ${bold("#" + ref.agentId.toString())}
|
|
77
|
+
`);
|
|
78
|
+
const client = getClient(ref.chainId);
|
|
79
|
+
let owner;
|
|
80
|
+
try {
|
|
81
|
+
owner = await client.readContract({
|
|
82
|
+
address: ref.registry,
|
|
83
|
+
abi: REGISTRY_ABI,
|
|
84
|
+
functionName: "ownerOf",
|
|
85
|
+
args: [ref.agentId]
|
|
86
|
+
});
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.log(` ${red("\u2717")} Agent not found or reverted
|
|
89
|
+
`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
console.log(` ${green("\u2713")} ${bold("Verified on-chain")}`);
|
|
93
|
+
console.log(` Owner: ${owner}`);
|
|
94
|
+
try {
|
|
95
|
+
const uri = await client.readContract({
|
|
96
|
+
address: ref.registry,
|
|
97
|
+
abi: REGISTRY_ABI,
|
|
98
|
+
functionName: "tokenURI",
|
|
99
|
+
args: [ref.agentId]
|
|
100
|
+
});
|
|
101
|
+
if (uri.startsWith("data:application/json")) {
|
|
102
|
+
const json = JSON.parse(Buffer.from(uri.split(",")[1], "base64").toString());
|
|
103
|
+
if (json.name) console.log(` Name: ${bold(json.name)}`);
|
|
104
|
+
if (json.description) console.log(` Desc: ${json.description}`);
|
|
105
|
+
} else if (uri.startsWith("ipfs://") || uri.startsWith("http")) {
|
|
106
|
+
console.log(` URI: ${dim(uri)}`);
|
|
107
|
+
try {
|
|
108
|
+
const fetchUrl = uri.startsWith("ipfs://") ? `https://gateway.pinata.cloud/ipfs/${uri.slice(7)}` : uri;
|
|
109
|
+
const res = await fetch(fetchUrl, { signal: AbortSignal.timeout(5e3) });
|
|
110
|
+
if (res.ok) {
|
|
111
|
+
const json = await res.json();
|
|
112
|
+
if (json.name) console.log(` Name: ${bold(json.name)}`);
|
|
113
|
+
if (json.description) console.log(` Desc: ${json.description}`);
|
|
114
|
+
if (json.services?.length) {
|
|
115
|
+
console.log(`
|
|
116
|
+
${bold("Services:")}`);
|
|
117
|
+
for (const s of json.services) {
|
|
118
|
+
console.log(` ${cyan("\u2192")} ${s.name}: ${s.endpoint}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
console.log();
|
|
128
|
+
}
|
|
129
|
+
async function lookup(wallet) {
|
|
130
|
+
const addr = getAddress(wallet);
|
|
131
|
+
console.log(`
|
|
132
|
+
${bold("\u{1F310} Scanning all chains")} for ${dim(addr)}
|
|
133
|
+
`);
|
|
134
|
+
const results = [];
|
|
135
|
+
const promises = Object.entries(SUPPORTED_CHAINS).map(async ([id, chain]) => {
|
|
136
|
+
const chainId = Number(id);
|
|
137
|
+
try {
|
|
138
|
+
const client = createPublicClient({ transport: http(chain.rpc) });
|
|
139
|
+
const balance = await client.readContract({
|
|
140
|
+
address: REGISTRY_ADDRESS,
|
|
141
|
+
abi: REGISTRY_ABI,
|
|
142
|
+
functionName: "balanceOf",
|
|
143
|
+
args: [addr]
|
|
144
|
+
});
|
|
145
|
+
if (balance === 0n) return;
|
|
146
|
+
const logs = await client.getLogs({
|
|
147
|
+
address: REGISTRY_ADDRESS,
|
|
148
|
+
event: TRANSFER_EVENT,
|
|
149
|
+
args: {
|
|
150
|
+
from: "0x0000000000000000000000000000000000000000",
|
|
151
|
+
to: addr
|
|
152
|
+
},
|
|
153
|
+
fromBlock: 0n,
|
|
154
|
+
toBlock: "latest"
|
|
155
|
+
});
|
|
156
|
+
for (const log of logs) {
|
|
157
|
+
const agentId = log.args.tokenId;
|
|
158
|
+
results.push({
|
|
159
|
+
chain: chain.name,
|
|
160
|
+
chainId,
|
|
161
|
+
agentId,
|
|
162
|
+
globalId: `eip155:${chainId}:${REGISTRY_ADDRESS}#${agentId}`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
await Promise.allSettled(promises);
|
|
169
|
+
if (results.length === 0) {
|
|
170
|
+
console.log(` No registrations found.
|
|
171
|
+
`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
results.sort((a, b) => a.chainId - b.chainId);
|
|
175
|
+
console.log(` Found ${green(results.length.toString())} registration(s):
|
|
176
|
+
`);
|
|
177
|
+
for (const r of results) {
|
|
178
|
+
console.log(` ${green("\u2713")} ${r.chain.padEnd(15)} ${yellow("#" + r.agentId.toString().padEnd(6))} ${dim(r.globalId)}`);
|
|
179
|
+
}
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
async function probe(globalId) {
|
|
183
|
+
console.log(`
|
|
184
|
+
${bold("\u{1F52C} Probing agent")} ${dim(globalId)}
|
|
185
|
+
`);
|
|
186
|
+
const ref = parseGlobalId(globalId);
|
|
187
|
+
const chainName = SUPPORTED_CHAINS[ref.chainId]?.name ?? `Chain ${ref.chainId}`;
|
|
188
|
+
const client = getClient(ref.chainId);
|
|
189
|
+
let owner;
|
|
190
|
+
try {
|
|
191
|
+
owner = await client.readContract({
|
|
192
|
+
address: ref.registry,
|
|
193
|
+
abi: REGISTRY_ABI,
|
|
194
|
+
functionName: "ownerOf",
|
|
195
|
+
args: [ref.agentId]
|
|
196
|
+
});
|
|
197
|
+
} catch {
|
|
198
|
+
console.log(` ${red("\u2717")} Agent not found
|
|
199
|
+
`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
console.log(` ${green("\u2713")} Identity verified on ${cyan(chainName)}`);
|
|
203
|
+
console.log(` Owner: ${dim(owner)}`);
|
|
204
|
+
try {
|
|
205
|
+
const uri = await client.readContract({
|
|
206
|
+
address: ref.registry,
|
|
207
|
+
abi: REGISTRY_ABI,
|
|
208
|
+
functionName: "tokenURI",
|
|
209
|
+
args: [ref.agentId]
|
|
210
|
+
});
|
|
211
|
+
let metadata = null;
|
|
212
|
+
if (uri.startsWith("data:application/json")) {
|
|
213
|
+
metadata = JSON.parse(Buffer.from(uri.split(",")[1], "base64").toString());
|
|
214
|
+
} else if (uri.startsWith("ipfs://") || uri.startsWith("http")) {
|
|
215
|
+
const fetchUrl = uri.startsWith("ipfs://") ? `https://gateway.pinata.cloud/ipfs/${uri.slice(7)}` : uri;
|
|
216
|
+
const res = await fetch(fetchUrl, { signal: AbortSignal.timeout(8e3) });
|
|
217
|
+
if (res.ok) metadata = await res.json();
|
|
218
|
+
}
|
|
219
|
+
if (metadata) {
|
|
220
|
+
console.log(` Name: ${bold(metadata.name ?? "unnamed")}`);
|
|
221
|
+
if (metadata.description) console.log(` Desc: ${metadata.description}`);
|
|
222
|
+
if (metadata.active !== void 0) console.log(` Active: ${metadata.active ? green("yes") : red("no")}`);
|
|
223
|
+
if (metadata.x402Support) console.log(` x402: ${green("accepts payments")}`);
|
|
224
|
+
if (metadata.paymentWallet) console.log(` Pay to: ${dim(metadata.paymentWallet)}`);
|
|
225
|
+
if (metadata.services?.length) {
|
|
226
|
+
console.log(`
|
|
227
|
+
${bold("Endpoints:")}`);
|
|
228
|
+
for (const s of metadata.services) {
|
|
229
|
+
console.log(` ${cyan("\u2192")} ${s.name.padEnd(8)} ${s.endpoint}`);
|
|
230
|
+
if (s.name.toUpperCase() === "MCP" && metadata.x402Support) {
|
|
231
|
+
try {
|
|
232
|
+
const r = await fetch(s.endpoint, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: { "Content-Type": "application/json" },
|
|
235
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "ping", id: 1 }),
|
|
236
|
+
signal: AbortSignal.timeout(5e3)
|
|
237
|
+
});
|
|
238
|
+
if (r.status === 402) {
|
|
239
|
+
console.log(` ${yellow("$")} Payment required (HTTP 402)`);
|
|
240
|
+
const header = r.headers.get("x-payment-required");
|
|
241
|
+
if (header) {
|
|
242
|
+
try {
|
|
243
|
+
const decoded = JSON.parse(Buffer.from(header, "base64").toString());
|
|
244
|
+
const first = Array.isArray(decoded.accepts) ? decoded.accepts[0] : decoded;
|
|
245
|
+
if (first.maxAmountRequired) console.log(` ${yellow("$")} Amount: ${first.maxAmountRequired} (${first.network ?? "unknown"})`);
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} else if (r.ok) {
|
|
250
|
+
console.log(` ${green("\u2713")} Endpoint reachable (free)`);
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
console.log(` ${dim("\u26A0 Endpoint not reachable")}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (metadata.registrations?.length) {
|
|
259
|
+
console.log(`
|
|
260
|
+
${bold("Cross-chain IDs:")}`);
|
|
261
|
+
for (const reg of metadata.registrations) {
|
|
262
|
+
console.log(` ${dim("\u2022")} Agent #${reg.agentId} @ ${dim(reg.agentRegistry)}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
console.log();
|
|
269
|
+
}
|
|
270
|
+
async function showChains() {
|
|
271
|
+
console.log(`
|
|
272
|
+
${bold("\u{1F310} Supported ERC-8004 chains")}
|
|
273
|
+
`);
|
|
274
|
+
console.log(` ${dim("Chain".padEnd(16))} ${dim("ID".padEnd(10))} ${dim("Registry")}
|
|
275
|
+
`);
|
|
276
|
+
for (const [id, chain] of Object.entries(SUPPORTED_CHAINS)) {
|
|
277
|
+
console.log(` ${chain.name.padEnd(16)} ${id.padEnd(10)} ${dim(REGISTRY_ADDRESS)}`);
|
|
278
|
+
}
|
|
279
|
+
console.log(`
|
|
280
|
+
${bold(Object.keys(SUPPORTED_CHAINS).length.toString())} chains \u2014 same registry address on all.
|
|
281
|
+
`);
|
|
282
|
+
}
|
|
283
|
+
async function count(chainIdStr) {
|
|
284
|
+
const chainIds = chainIdStr ? [Number(chainIdStr)] : Object.keys(SUPPORTED_CHAINS).map(Number);
|
|
285
|
+
console.log(`
|
|
286
|
+
${bold("\u{1F4CA} Agent count")} ${dim("(scanning Transfer events)")}
|
|
287
|
+
`);
|
|
288
|
+
const promises = chainIds.map(async (chainId) => {
|
|
289
|
+
const chain = SUPPORTED_CHAINS[chainId];
|
|
290
|
+
if (!chain) return { chainId, name: `Unknown (${chainId})`, count: -1 };
|
|
291
|
+
try {
|
|
292
|
+
const client = createPublicClient({ transport: http(chain.rpc) });
|
|
293
|
+
const logs = await client.getLogs({
|
|
294
|
+
address: REGISTRY_ADDRESS,
|
|
295
|
+
event: TRANSFER_EVENT,
|
|
296
|
+
args: { from: "0x0000000000000000000000000000000000000000" },
|
|
297
|
+
fromBlock: 0n,
|
|
298
|
+
toBlock: "latest"
|
|
299
|
+
});
|
|
300
|
+
return { chainId, name: chain.name, count: logs.length };
|
|
301
|
+
} catch {
|
|
302
|
+
return { chainId, name: chain.name, count: -1 };
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const results = await Promise.all(promises);
|
|
306
|
+
results.sort((a, b) => b.count - a.count);
|
|
307
|
+
for (const r of results) {
|
|
308
|
+
if (r.count >= 0) {
|
|
309
|
+
console.log(` ${r.name.padEnd(16)} ${yellow(r.count.toString().padStart(6))} agents`);
|
|
310
|
+
} else {
|
|
311
|
+
console.log(` ${r.name.padEnd(16)} ${dim(" error")}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
console.log();
|
|
315
|
+
}
|
|
316
|
+
function scaffoldInit() {
|
|
317
|
+
console.log(`
|
|
318
|
+
${bold("\u{1F680} Scaffold a new A3Stack agent")}
|
|
319
|
+
`);
|
|
320
|
+
console.log(`${dim("Coming in v0.2.0 \u2014 for now, install the packages directly:")}
|
|
321
|
+
`);
|
|
322
|
+
console.log(` ${cyan("npm install @a3stack/core viem")}
|
|
323
|
+
`);
|
|
324
|
+
console.log(` Then in your code:
|
|
325
|
+
`);
|
|
326
|
+
console.log(` ${dim('import { A3Stack } from "@a3stack/core";')}`);
|
|
327
|
+
console.log(` ${dim("")}`);
|
|
328
|
+
console.log(` ${dim("const agent = new A3Stack({")}`);
|
|
329
|
+
console.log(` ${dim(" privateKey: process.env.PRIVATE_KEY,")}`);
|
|
330
|
+
console.log(` ${dim(" chainId: 8453, // Base")}`);
|
|
331
|
+
console.log(` ${dim("});")}`);
|
|
332
|
+
console.log(` ${dim("")}`);
|
|
333
|
+
console.log(` ${dim('await agent.register({ name: "my-agent" });')}`);
|
|
334
|
+
console.log(` ${dim('console.log("Registered:", agent.globalId);')}
|
|
335
|
+
`);
|
|
336
|
+
console.log(` Docs: ${cyan("https://a3stack.arcabot.ai")}
|
|
337
|
+
`);
|
|
338
|
+
}
|
|
339
|
+
var HELP = `
|
|
340
|
+
${bold("a3stack")} \u2014 identity, payments, and data for AI agents
|
|
341
|
+
|
|
342
|
+
${bold("Commands:")}
|
|
343
|
+
${cyan("verify")} <globalId> Verify an agent's on-chain identity
|
|
344
|
+
${cyan("lookup")} <wallet> Find all chain registrations for a wallet
|
|
345
|
+
${cyan("probe")} <globalId> Discover an agent's capabilities & endpoints
|
|
346
|
+
${cyan("chains")} List supported ERC-8004 chains
|
|
347
|
+
${cyan("count")} [chainId] Count registered agents (all chains or one)
|
|
348
|
+
${cyan("init")} Scaffold a new agent project
|
|
349
|
+
|
|
350
|
+
${bold("Examples:")}
|
|
351
|
+
npx a3stack verify "eip155:8453:${REGISTRY_ADDRESS}#2376"
|
|
352
|
+
npx a3stack lookup 0xYOUR_WALLET_ADDRESS
|
|
353
|
+
npx a3stack probe "eip155:8453:${REGISTRY_ADDRESS}#2376"
|
|
354
|
+
npx a3stack count 8453
|
|
355
|
+
|
|
356
|
+
${dim(`v0.1.0 \u2022 https://a3stack.arcabot.ai \u2022 github.com/arcabotai/a3stack`)}
|
|
357
|
+
`;
|
|
358
|
+
var [cmd, ...args] = process.argv.slice(2);
|
|
359
|
+
async function main() {
|
|
360
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
361
|
+
console.log(HELP);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
365
|
+
console.log("a3stack v0.1.0");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
switch (cmd) {
|
|
369
|
+
case "verify":
|
|
370
|
+
return verify(args[0]);
|
|
371
|
+
case "lookup":
|
|
372
|
+
return lookup(args[0]);
|
|
373
|
+
case "probe":
|
|
374
|
+
return probe(args[0]);
|
|
375
|
+
case "chains":
|
|
376
|
+
return showChains();
|
|
377
|
+
case "count":
|
|
378
|
+
return count(args[0]);
|
|
379
|
+
case "init":
|
|
380
|
+
return scaffoldInit();
|
|
381
|
+
default:
|
|
382
|
+
console.error(`${red("Unknown command:")} ${cmd}
|
|
383
|
+
`);
|
|
384
|
+
console.log(HELP);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
main().catch((err) => {
|
|
389
|
+
console.error(`${red("Error:")} ${err.message}`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
});
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { createPublicClient, http, getAddress } from "viem";
|
|
5
|
+
var REGISTRY_ADDRESS = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
|
|
6
|
+
var REGISTRY_ABI = [
|
|
7
|
+
{ inputs: [{ name: "agentId", type: "uint256" }], name: "ownerOf", outputs: [{ type: "address" }], stateMutability: "view", type: "function" },
|
|
8
|
+
{ inputs: [{ name: "agentId", type: "uint256" }], name: "tokenURI", outputs: [{ type: "string" }], stateMutability: "view", type: "function" },
|
|
9
|
+
{ inputs: [{ name: "owner", type: "address" }], name: "balanceOf", outputs: [{ type: "uint256" }], stateMutability: "view", type: "function" }
|
|
10
|
+
];
|
|
11
|
+
var TRANSFER_EVENT = {
|
|
12
|
+
type: "event",
|
|
13
|
+
name: "Transfer",
|
|
14
|
+
inputs: [
|
|
15
|
+
{ type: "address", indexed: true, name: "from" },
|
|
16
|
+
{ type: "address", indexed: true, name: "to" },
|
|
17
|
+
{ type: "uint256", indexed: true, name: "tokenId" }
|
|
18
|
+
]
|
|
19
|
+
};
|
|
20
|
+
var SUPPORTED_CHAINS = {
|
|
21
|
+
1: { name: "Ethereum", rpc: "https://eth.llamarpc.com" },
|
|
22
|
+
10: { name: "Optimism", rpc: "https://mainnet.optimism.io" },
|
|
23
|
+
56: { name: "BNB Chain", rpc: "https://bsc-dataseed.binance.org" },
|
|
24
|
+
100: { name: "Gnosis", rpc: "https://rpc.gnosischain.com" },
|
|
25
|
+
130: { name: "Unichain", rpc: "https://mainnet.unichain.org" },
|
|
26
|
+
137: { name: "Polygon", rpc: "https://polygon-rpc.com" },
|
|
27
|
+
196: { name: "X Layer", rpc: "https://rpc.xlayer.tech" },
|
|
28
|
+
480: { name: "World Chain", rpc: "https://worldchain-mainnet.g.alchemy.com/public" },
|
|
29
|
+
690: { name: "Redstone", rpc: "https://rpc.redstonechain.com" },
|
|
30
|
+
8453: { name: "Base", rpc: "https://mainnet.base.org" },
|
|
31
|
+
42161: { name: "Arbitrum", rpc: "https://arb1.arbitrum.io/rpc" },
|
|
32
|
+
42170: { name: "Arbitrum Nova", rpc: "https://nova.arbitrum.io/rpc" },
|
|
33
|
+
43114: { name: "Avalanche", rpc: "https://api.avax.network/ext/bc/C/rpc" },
|
|
34
|
+
57073: { name: "Ink", rpc: "https://rpc-gel.inkonchain.com" },
|
|
35
|
+
81457: { name: "Blast", rpc: "https://rpc.blast.io" },
|
|
36
|
+
534352: { name: "Scroll", rpc: "https://rpc.scroll.io" },
|
|
37
|
+
7777777: { name: "Zora", rpc: "https://rpc.zora.energy" }
|
|
38
|
+
};
|
|
39
|
+
function parseGlobalId(id) {
|
|
40
|
+
const match = id.match(/^eip155:(\d+):(0x[a-fA-F0-9]{40})#(\d+)$/);
|
|
41
|
+
if (!match) throw new Error(`Invalid global ID format: ${id}
|
|
42
|
+
Expected: eip155:<chainId>:<registry>#<agentId>`);
|
|
43
|
+
return { chainId: Number(match[1]), registry: match[2], agentId: BigInt(match[3]) };
|
|
44
|
+
}
|
|
45
|
+
function getClient(chainId) {
|
|
46
|
+
const chain = SUPPORTED_CHAINS[chainId];
|
|
47
|
+
if (!chain) throw new Error(`Unsupported chain: ${chainId}. Run 'a3stack chains' to see supported chains.`);
|
|
48
|
+
return createPublicClient({ transport: http(chain.rpc) });
|
|
49
|
+
}
|
|
50
|
+
function bold(s) {
|
|
51
|
+
return `\x1B[1m${s}\x1B[0m`;
|
|
52
|
+
}
|
|
53
|
+
function green(s) {
|
|
54
|
+
return `\x1B[32m${s}\x1B[0m`;
|
|
55
|
+
}
|
|
56
|
+
function red(s) {
|
|
57
|
+
return `\x1B[31m${s}\x1B[0m`;
|
|
58
|
+
}
|
|
59
|
+
function dim(s) {
|
|
60
|
+
return `\x1B[2m${s}\x1B[0m`;
|
|
61
|
+
}
|
|
62
|
+
function cyan(s) {
|
|
63
|
+
return `\x1B[36m${s}\x1B[0m`;
|
|
64
|
+
}
|
|
65
|
+
function yellow(s) {
|
|
66
|
+
return `\x1B[33m${s}\x1B[0m`;
|
|
67
|
+
}
|
|
68
|
+
async function verify(globalId) {
|
|
69
|
+
console.log(`
|
|
70
|
+
${bold("\u{1F50D} Verifying agent identity")}
|
|
71
|
+
`);
|
|
72
|
+
const ref = parseGlobalId(globalId);
|
|
73
|
+
const chainName = SUPPORTED_CHAINS[ref.chainId]?.name ?? `Chain ${ref.chainId}`;
|
|
74
|
+
console.log(` Chain: ${cyan(chainName)} (${ref.chainId})`);
|
|
75
|
+
console.log(` Registry: ${dim(ref.registry)}`);
|
|
76
|
+
console.log(` Agent ID: ${bold("#" + ref.agentId.toString())}
|
|
77
|
+
`);
|
|
78
|
+
const client = getClient(ref.chainId);
|
|
79
|
+
let owner;
|
|
80
|
+
try {
|
|
81
|
+
owner = await client.readContract({
|
|
82
|
+
address: ref.registry,
|
|
83
|
+
abi: REGISTRY_ABI,
|
|
84
|
+
functionName: "ownerOf",
|
|
85
|
+
args: [ref.agentId]
|
|
86
|
+
});
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.log(` ${red("\u2717")} Agent not found or reverted
|
|
89
|
+
`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
console.log(` ${green("\u2713")} ${bold("Verified on-chain")}`);
|
|
93
|
+
console.log(` Owner: ${owner}`);
|
|
94
|
+
try {
|
|
95
|
+
const uri = await client.readContract({
|
|
96
|
+
address: ref.registry,
|
|
97
|
+
abi: REGISTRY_ABI,
|
|
98
|
+
functionName: "tokenURI",
|
|
99
|
+
args: [ref.agentId]
|
|
100
|
+
});
|
|
101
|
+
if (uri.startsWith("data:application/json")) {
|
|
102
|
+
const json = JSON.parse(Buffer.from(uri.split(",")[1], "base64").toString());
|
|
103
|
+
if (json.name) console.log(` Name: ${bold(json.name)}`);
|
|
104
|
+
if (json.description) console.log(` Desc: ${json.description}`);
|
|
105
|
+
} else if (uri.startsWith("ipfs://") || uri.startsWith("http")) {
|
|
106
|
+
console.log(` URI: ${dim(uri)}`);
|
|
107
|
+
try {
|
|
108
|
+
const fetchUrl = uri.startsWith("ipfs://") ? `https://gateway.pinata.cloud/ipfs/${uri.slice(7)}` : uri;
|
|
109
|
+
const res = await fetch(fetchUrl, { signal: AbortSignal.timeout(5e3) });
|
|
110
|
+
if (res.ok) {
|
|
111
|
+
const json = await res.json();
|
|
112
|
+
if (json.name) console.log(` Name: ${bold(json.name)}`);
|
|
113
|
+
if (json.description) console.log(` Desc: ${json.description}`);
|
|
114
|
+
if (json.services?.length) {
|
|
115
|
+
console.log(`
|
|
116
|
+
${bold("Services:")}`);
|
|
117
|
+
for (const s of json.services) {
|
|
118
|
+
console.log(` ${cyan("\u2192")} ${s.name}: ${s.endpoint}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
console.log();
|
|
128
|
+
}
|
|
129
|
+
async function lookup(wallet) {
|
|
130
|
+
const addr = getAddress(wallet);
|
|
131
|
+
console.log(`
|
|
132
|
+
${bold("\u{1F310} Scanning all chains")} for ${dim(addr)}
|
|
133
|
+
`);
|
|
134
|
+
const results = [];
|
|
135
|
+
const promises = Object.entries(SUPPORTED_CHAINS).map(async ([id, chain]) => {
|
|
136
|
+
const chainId = Number(id);
|
|
137
|
+
try {
|
|
138
|
+
const client = createPublicClient({ transport: http(chain.rpc) });
|
|
139
|
+
const balance = await client.readContract({
|
|
140
|
+
address: REGISTRY_ADDRESS,
|
|
141
|
+
abi: REGISTRY_ABI,
|
|
142
|
+
functionName: "balanceOf",
|
|
143
|
+
args: [addr]
|
|
144
|
+
});
|
|
145
|
+
if (balance === 0n) return;
|
|
146
|
+
const logs = await client.getLogs({
|
|
147
|
+
address: REGISTRY_ADDRESS,
|
|
148
|
+
event: TRANSFER_EVENT,
|
|
149
|
+
args: {
|
|
150
|
+
from: "0x0000000000000000000000000000000000000000",
|
|
151
|
+
to: addr
|
|
152
|
+
},
|
|
153
|
+
fromBlock: 0n,
|
|
154
|
+
toBlock: "latest"
|
|
155
|
+
});
|
|
156
|
+
for (const log of logs) {
|
|
157
|
+
const agentId = log.args.tokenId;
|
|
158
|
+
results.push({
|
|
159
|
+
chain: chain.name,
|
|
160
|
+
chainId,
|
|
161
|
+
agentId,
|
|
162
|
+
globalId: `eip155:${chainId}:${REGISTRY_ADDRESS}#${agentId}`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
await Promise.allSettled(promises);
|
|
169
|
+
if (results.length === 0) {
|
|
170
|
+
console.log(` No registrations found.
|
|
171
|
+
`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
results.sort((a, b) => a.chainId - b.chainId);
|
|
175
|
+
console.log(` Found ${green(results.length.toString())} registration(s):
|
|
176
|
+
`);
|
|
177
|
+
for (const r of results) {
|
|
178
|
+
console.log(` ${green("\u2713")} ${r.chain.padEnd(15)} ${yellow("#" + r.agentId.toString().padEnd(6))} ${dim(r.globalId)}`);
|
|
179
|
+
}
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
async function probe(globalId) {
|
|
183
|
+
console.log(`
|
|
184
|
+
${bold("\u{1F52C} Probing agent")} ${dim(globalId)}
|
|
185
|
+
`);
|
|
186
|
+
const ref = parseGlobalId(globalId);
|
|
187
|
+
const chainName = SUPPORTED_CHAINS[ref.chainId]?.name ?? `Chain ${ref.chainId}`;
|
|
188
|
+
const client = getClient(ref.chainId);
|
|
189
|
+
let owner;
|
|
190
|
+
try {
|
|
191
|
+
owner = await client.readContract({
|
|
192
|
+
address: ref.registry,
|
|
193
|
+
abi: REGISTRY_ABI,
|
|
194
|
+
functionName: "ownerOf",
|
|
195
|
+
args: [ref.agentId]
|
|
196
|
+
});
|
|
197
|
+
} catch {
|
|
198
|
+
console.log(` ${red("\u2717")} Agent not found
|
|
199
|
+
`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
console.log(` ${green("\u2713")} Identity verified on ${cyan(chainName)}`);
|
|
203
|
+
console.log(` Owner: ${dim(owner)}`);
|
|
204
|
+
try {
|
|
205
|
+
const uri = await client.readContract({
|
|
206
|
+
address: ref.registry,
|
|
207
|
+
abi: REGISTRY_ABI,
|
|
208
|
+
functionName: "tokenURI",
|
|
209
|
+
args: [ref.agentId]
|
|
210
|
+
});
|
|
211
|
+
let metadata = null;
|
|
212
|
+
if (uri.startsWith("data:application/json")) {
|
|
213
|
+
metadata = JSON.parse(Buffer.from(uri.split(",")[1], "base64").toString());
|
|
214
|
+
} else if (uri.startsWith("ipfs://") || uri.startsWith("http")) {
|
|
215
|
+
const fetchUrl = uri.startsWith("ipfs://") ? `https://gateway.pinata.cloud/ipfs/${uri.slice(7)}` : uri;
|
|
216
|
+
const res = await fetch(fetchUrl, { signal: AbortSignal.timeout(8e3) });
|
|
217
|
+
if (res.ok) metadata = await res.json();
|
|
218
|
+
}
|
|
219
|
+
if (metadata) {
|
|
220
|
+
console.log(` Name: ${bold(metadata.name ?? "unnamed")}`);
|
|
221
|
+
if (metadata.description) console.log(` Desc: ${metadata.description}`);
|
|
222
|
+
if (metadata.active !== void 0) console.log(` Active: ${metadata.active ? green("yes") : red("no")}`);
|
|
223
|
+
if (metadata.x402Support) console.log(` x402: ${green("accepts payments")}`);
|
|
224
|
+
if (metadata.paymentWallet) console.log(` Pay to: ${dim(metadata.paymentWallet)}`);
|
|
225
|
+
if (metadata.services?.length) {
|
|
226
|
+
console.log(`
|
|
227
|
+
${bold("Endpoints:")}`);
|
|
228
|
+
for (const s of metadata.services) {
|
|
229
|
+
console.log(` ${cyan("\u2192")} ${s.name.padEnd(8)} ${s.endpoint}`);
|
|
230
|
+
if (s.name.toUpperCase() === "MCP" && metadata.x402Support) {
|
|
231
|
+
try {
|
|
232
|
+
const r = await fetch(s.endpoint, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: { "Content-Type": "application/json" },
|
|
235
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "ping", id: 1 }),
|
|
236
|
+
signal: AbortSignal.timeout(5e3)
|
|
237
|
+
});
|
|
238
|
+
if (r.status === 402) {
|
|
239
|
+
console.log(` ${yellow("$")} Payment required (HTTP 402)`);
|
|
240
|
+
const header = r.headers.get("x-payment-required");
|
|
241
|
+
if (header) {
|
|
242
|
+
try {
|
|
243
|
+
const decoded = JSON.parse(Buffer.from(header, "base64").toString());
|
|
244
|
+
const first = Array.isArray(decoded.accepts) ? decoded.accepts[0] : decoded;
|
|
245
|
+
if (first.maxAmountRequired) console.log(` ${yellow("$")} Amount: ${first.maxAmountRequired} (${first.network ?? "unknown"})`);
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} else if (r.ok) {
|
|
250
|
+
console.log(` ${green("\u2713")} Endpoint reachable (free)`);
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
console.log(` ${dim("\u26A0 Endpoint not reachable")}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (metadata.registrations?.length) {
|
|
259
|
+
console.log(`
|
|
260
|
+
${bold("Cross-chain IDs:")}`);
|
|
261
|
+
for (const reg of metadata.registrations) {
|
|
262
|
+
console.log(` ${dim("\u2022")} Agent #${reg.agentId} @ ${dim(reg.agentRegistry)}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
console.log();
|
|
269
|
+
}
|
|
270
|
+
async function showChains() {
|
|
271
|
+
console.log(`
|
|
272
|
+
${bold("\u{1F310} Supported ERC-8004 chains")}
|
|
273
|
+
`);
|
|
274
|
+
console.log(` ${dim("Chain".padEnd(16))} ${dim("ID".padEnd(10))} ${dim("Registry")}
|
|
275
|
+
`);
|
|
276
|
+
for (const [id, chain] of Object.entries(SUPPORTED_CHAINS)) {
|
|
277
|
+
console.log(` ${chain.name.padEnd(16)} ${id.padEnd(10)} ${dim(REGISTRY_ADDRESS)}`);
|
|
278
|
+
}
|
|
279
|
+
console.log(`
|
|
280
|
+
${bold(Object.keys(SUPPORTED_CHAINS).length.toString())} chains \u2014 same registry address on all.
|
|
281
|
+
`);
|
|
282
|
+
}
|
|
283
|
+
async function count(chainIdStr) {
|
|
284
|
+
const chainIds = chainIdStr ? [Number(chainIdStr)] : Object.keys(SUPPORTED_CHAINS).map(Number);
|
|
285
|
+
console.log(`
|
|
286
|
+
${bold("\u{1F4CA} Agent count")} ${dim("(scanning Transfer events)")}
|
|
287
|
+
`);
|
|
288
|
+
const promises = chainIds.map(async (chainId) => {
|
|
289
|
+
const chain = SUPPORTED_CHAINS[chainId];
|
|
290
|
+
if (!chain) return { chainId, name: `Unknown (${chainId})`, count: -1 };
|
|
291
|
+
try {
|
|
292
|
+
const client = createPublicClient({ transport: http(chain.rpc) });
|
|
293
|
+
const logs = await client.getLogs({
|
|
294
|
+
address: REGISTRY_ADDRESS,
|
|
295
|
+
event: TRANSFER_EVENT,
|
|
296
|
+
args: { from: "0x0000000000000000000000000000000000000000" },
|
|
297
|
+
fromBlock: 0n,
|
|
298
|
+
toBlock: "latest"
|
|
299
|
+
});
|
|
300
|
+
return { chainId, name: chain.name, count: logs.length };
|
|
301
|
+
} catch {
|
|
302
|
+
return { chainId, name: chain.name, count: -1 };
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const results = await Promise.all(promises);
|
|
306
|
+
results.sort((a, b) => b.count - a.count);
|
|
307
|
+
for (const r of results) {
|
|
308
|
+
if (r.count >= 0) {
|
|
309
|
+
console.log(` ${r.name.padEnd(16)} ${yellow(r.count.toString().padStart(6))} agents`);
|
|
310
|
+
} else {
|
|
311
|
+
console.log(` ${r.name.padEnd(16)} ${dim(" error")}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
console.log();
|
|
315
|
+
}
|
|
316
|
+
function scaffoldInit() {
|
|
317
|
+
console.log(`
|
|
318
|
+
${bold("\u{1F680} Scaffold a new A3Stack agent")}
|
|
319
|
+
`);
|
|
320
|
+
console.log(`${dim("Coming in v0.2.0 \u2014 for now, install the packages directly:")}
|
|
321
|
+
`);
|
|
322
|
+
console.log(` ${cyan("npm install @a3stack/core viem")}
|
|
323
|
+
`);
|
|
324
|
+
console.log(` Then in your code:
|
|
325
|
+
`);
|
|
326
|
+
console.log(` ${dim('import { A3Stack } from "@a3stack/core";')}`);
|
|
327
|
+
console.log(` ${dim("")}`);
|
|
328
|
+
console.log(` ${dim("const agent = new A3Stack({")}`);
|
|
329
|
+
console.log(` ${dim(" privateKey: process.env.PRIVATE_KEY,")}`);
|
|
330
|
+
console.log(` ${dim(" chainId: 8453, // Base")}`);
|
|
331
|
+
console.log(` ${dim("});")}`);
|
|
332
|
+
console.log(` ${dim("")}`);
|
|
333
|
+
console.log(` ${dim('await agent.register({ name: "my-agent" });')}`);
|
|
334
|
+
console.log(` ${dim('console.log("Registered:", agent.globalId);')}
|
|
335
|
+
`);
|
|
336
|
+
console.log(` Docs: ${cyan("https://a3stack.arcabot.ai")}
|
|
337
|
+
`);
|
|
338
|
+
}
|
|
339
|
+
var HELP = `
|
|
340
|
+
${bold("a3stack")} \u2014 identity, payments, and data for AI agents
|
|
341
|
+
|
|
342
|
+
${bold("Commands:")}
|
|
343
|
+
${cyan("verify")} <globalId> Verify an agent's on-chain identity
|
|
344
|
+
${cyan("lookup")} <wallet> Find all chain registrations for a wallet
|
|
345
|
+
${cyan("probe")} <globalId> Discover an agent's capabilities & endpoints
|
|
346
|
+
${cyan("chains")} List supported ERC-8004 chains
|
|
347
|
+
${cyan("count")} [chainId] Count registered agents (all chains or one)
|
|
348
|
+
${cyan("init")} Scaffold a new agent project
|
|
349
|
+
|
|
350
|
+
${bold("Examples:")}
|
|
351
|
+
npx a3stack verify "eip155:8453:${REGISTRY_ADDRESS}#2376"
|
|
352
|
+
npx a3stack lookup 0xYOUR_WALLET_ADDRESS
|
|
353
|
+
npx a3stack probe "eip155:8453:${REGISTRY_ADDRESS}#2376"
|
|
354
|
+
npx a3stack count 8453
|
|
355
|
+
|
|
356
|
+
${dim(`v0.1.0 \u2022 https://a3stack.arcabot.ai \u2022 github.com/arcabotai/a3stack`)}
|
|
357
|
+
`;
|
|
358
|
+
var [cmd, ...args] = process.argv.slice(2);
|
|
359
|
+
async function main() {
|
|
360
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
361
|
+
console.log(HELP);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
365
|
+
console.log("a3stack v0.1.0");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
switch (cmd) {
|
|
369
|
+
case "verify":
|
|
370
|
+
return verify(args[0]);
|
|
371
|
+
case "lookup":
|
|
372
|
+
return lookup(args[0]);
|
|
373
|
+
case "probe":
|
|
374
|
+
return probe(args[0]);
|
|
375
|
+
case "chains":
|
|
376
|
+
return showChains();
|
|
377
|
+
case "count":
|
|
378
|
+
return count(args[0]);
|
|
379
|
+
case "init":
|
|
380
|
+
return scaffoldInit();
|
|
381
|
+
default:
|
|
382
|
+
console.error(`${red("Unknown command:")} ${cmd}
|
|
383
|
+
`);
|
|
384
|
+
console.log(HELP);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
main().catch((err) => {
|
|
389
|
+
console.error(`${red("Error:")} ${err.message}`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "a3stack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A3Stack CLI — identity, payments, and data for AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"a3stack": "./bin/a3stack.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"dist/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/cli.ts --format esm --outDir dist",
|
|
15
|
+
"postbuild": "cp dist/cli.js bin/a3stack.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"viem": "^2.39.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"tsup": "^8.0.0",
|
|
22
|
+
"typescript": "^5.4.0"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"ai",
|
|
29
|
+
"agent",
|
|
30
|
+
"erc-8004",
|
|
31
|
+
"identity",
|
|
32
|
+
"x402",
|
|
33
|
+
"payments",
|
|
34
|
+
"mcp"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/arcabotai/a3stack"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://a3stack.arcabot.ai"
|
|
42
|
+
}
|