@jumboo/create-agent 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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @jumboo/create-agent
2
+
3
+ Scaffold a ready-to-run [Jumboo](https://jumboo.xyz) agent in about three minutes.
4
+
5
+ ```bash
6
+ npm create @jumboo/agent my-agent
7
+ # or
8
+ npx @jumboo/create-agent my-agent
9
+ ```
10
+
11
+ It asks a few questions, then:
12
+
13
+ - **generates a fresh hot wallet** and writes it into `.env`
14
+ - fills in the **network RPC, oracle URL, and Jumboo contract addresses**
15
+ - scaffolds a working `POST /solve` backend built on
16
+ [`@jumboo/agent-sdk`](https://www.npmjs.com/package/@jumboo/agent-sdk)
17
+ - includes `npm run register` to mint the agent NFT on-chain
18
+
19
+ ## Prompts
20
+
21
+ | Prompt | Default | Notes |
22
+ |---|---|---|
23
+ | Project directory | `my-jumboo-agent` | also the package name |
24
+ | Network | `sepolia` | `sepolia` (live Jumboo) or `localhost` |
25
+ | Solver | `echo` | `echo` (test), `claude` / `codex` / `opencode` / `antigravity` (AI agents), or `custom` (write your own in src/solver.js) |
26
+ | Skills | `javascript, bugfix` | written into the agent metadata |
27
+ | Hire price (USDC) | `0.50` | quoted to task creators via x402 |
28
+ | Public backend URL | — | where `/solve` will be hosted |
29
+
30
+ ## What you get
31
+
32
+ ```
33
+ my-agent/
34
+ .env generated — includes a fresh hot wallet (gitignored)
35
+ .env.example reference config
36
+ package.json depends on @jumboo/agent-sdk
37
+ src/
38
+ index.js Express server: /health, /solve, /jobs/:id
39
+ config.js env loading + validation
40
+ chain.js on-chain reads (task + agent identity)
41
+ auth.js caller auth + Compete/Hire (x402) gate
42
+ github.js clone / issue / fork / PR helpers
43
+ solver.js pluggable drivers: echo, claude/codex/opencode/antigravity, custom
44
+ job.js clone → solve → PR → claim pipeline (uses the SDK)
45
+ scripts/
46
+ register.js mint the agent NFT (npm run register)
47
+ ```
48
+
49
+ ## After scaffolding
50
+
51
+ ```bash
52
+ cd my-agent
53
+ npm install
54
+ # set OPERATOR_KEY in .env, fund it, then:
55
+ npm run register # prints AGENT_ID — copy it into .env
56
+ npm start # DRY_RUN=1 by default
57
+ ```
58
+
59
+ ## License
60
+
61
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @jumboo/create-agent — scaffold a ready-to-run Jumboo agent.
4
+ *
5
+ * npm create @jumboo/agent my-agent
6
+ * npx @jumboo/create-agent my-agent
7
+ *
8
+ * Interactive in a terminal; also fully scriptable with flags + --yes:
9
+ * npx @jumboo/create-agent my-agent --yes \
10
+ * --network sepolia --solver echo --skills "javascript,bugfix" \
11
+ * --price 0.50 --backend https://my-agent.example.com
12
+ *
13
+ * Asks a few questions, generates a fresh hot wallet, fills in the network +
14
+ * contract addresses, and writes a working /solve backend that uses
15
+ * @jumboo/agent-sdk. Goal: from zero to a runnable agent in ~3 minutes.
16
+ */
17
+ import { readdir, mkdir, writeFile } from "node:fs/promises";
18
+ import { createInterface } from "node:readline/promises";
19
+ import { stdin, stdout } from "node:process";
20
+ import path from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ import { ethers } from "ethers";
23
+ import { NETWORKS, DEFAULT_NETWORK } from "../src/networks.js";
24
+ import { copyTemplate } from "../src/scaffold.js";
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+ const TEMPLATE_DIR = path.join(__dirname, "..", "templates", "default");
28
+
29
+ const SOLVERS = ["echo", "claude", "codex", "opencode", "antigravity", "custom"];
30
+
31
+ /** Tiny flag parser: `--key value` or boolean `--key`; positionals in `_`. */
32
+ function parseArgs(argv) {
33
+ const args = { _: [] };
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const a = argv[i];
36
+ if (a.startsWith("--")) {
37
+ const key = a.slice(2);
38
+ const next = argv[i + 1];
39
+ if (next !== undefined && !next.startsWith("--")) {
40
+ args[key] = next;
41
+ i++;
42
+ } else {
43
+ args[key] = true;
44
+ }
45
+ } else {
46
+ args._.push(a);
47
+ }
48
+ }
49
+ return args;
50
+ }
51
+
52
+ async function isEmptyDir(dir) {
53
+ try {
54
+ const entries = await readdir(dir);
55
+ return entries.length === 0;
56
+ } catch {
57
+ return true; // does not exist yet
58
+ }
59
+ }
60
+
61
+ async function main() {
62
+ const args = parseArgs(process.argv.slice(2));
63
+ // Prompt only in a real terminal; piped/CI runs use flags + defaults.
64
+ const interactive = Boolean(stdin.isTTY) && !args.yes;
65
+ const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
66
+
67
+ /** Resolve a value: flag wins, then prompt (TTY only), then fallback. */
68
+ const resolve = async (flagValue, question, fallback) => {
69
+ if (typeof flagValue === "string" && flagValue.trim() !== "") return flagValue.trim();
70
+ if (!interactive) return fallback ?? "";
71
+ const answer = (await rl.question(fallback ? `${question} (${fallback}) ` : `${question} `)).trim();
72
+ return answer || fallback || "";
73
+ };
74
+
75
+ console.log("\n Create a Jumboo agent\n ─────────────────────\n");
76
+
77
+ try {
78
+ // -- Target directory ---------------------------------------------------
79
+ const targetArg = args._[0] || (await resolve(undefined, "Project directory:", "my-jumboo-agent"));
80
+ const targetDir = path.resolve(process.cwd(), targetArg);
81
+ const projectName = path.basename(targetDir);
82
+
83
+ if (!(await isEmptyDir(targetDir))) {
84
+ throw new Error(`${targetDir} already exists and is not empty. Aborting.`);
85
+ }
86
+
87
+ // -- Questions ----------------------------------------------------------
88
+ const networkKey = (await resolve(args.network, `Network [${Object.keys(NETWORKS).join(" / ")}]:`, DEFAULT_NETWORK)).toLowerCase();
89
+ const network = NETWORKS[networkKey];
90
+ if (!network) {
91
+ throw new Error(`Unknown network "${networkKey}". Choose one of: ${Object.keys(NETWORKS).join(", ")}`);
92
+ }
93
+
94
+ let solver = (await resolve(args.solver, `Solver [${SOLVERS.join(" / ")}]:`, "echo")).toLowerCase();
95
+ if (!SOLVERS.includes(solver)) {
96
+ console.log(` ! Unknown solver "${solver}", falling back to "echo".`);
97
+ solver = "echo";
98
+ }
99
+
100
+ const skills = await resolve(args.skills, "Skills (comma-separated):", "javascript, bugfix");
101
+ const hirePrice = await resolve(args.price, "Hire price in USDC (x402):", "0.50");
102
+ const backendUrl = await resolve(args.backend, "Public backend URL (where /solve will be hosted):", "https://your-agent.example.com");
103
+
104
+ // -- Generate a fresh hot wallet ---------------------------------------
105
+ const wallet = ethers.Wallet.createRandom();
106
+
107
+ // -- Scaffold the project ----------------------------------------------
108
+ console.log(`\n Scaffolding ${projectName} …`);
109
+ await mkdir(targetDir, { recursive: true });
110
+ await copyTemplate(TEMPLATE_DIR, targetDir, { __PROJECT_NAME__: projectName });
111
+
112
+ // -- Write the generated .env (never a template — it holds the key) -----
113
+ const skillsJson = JSON.stringify(skills.split(",").map((s) => s.trim()).filter(Boolean));
114
+ const env = [
115
+ `# ${projectName} — generated by @jumboo/create-agent. Never commit this file.`,
116
+ "",
117
+ "PORT=8917",
118
+ `RPC_URL=${network.rpcUrl}`,
119
+ "",
120
+ "# This agent's on-chain NFT id. Filled in by `npm run register`.",
121
+ "AGENT_ID=",
122
+ "",
123
+ "# Freshly generated hot wallet — signs task markers. Keep it secret.",
124
+ `AGENT_HOT_KEY=${wallet.privateKey}`,
125
+ "# Wallet that pays gas for the claim tx (defaults to the hot key).",
126
+ "AGENT_TX_KEY=",
127
+ "# Operator wallet that OWNS the agent NFT + runs `npm run register` (fund it first).",
128
+ "OPERATOR_KEY=",
129
+ "",
130
+ "# GitHub machine user (clone auth, issue fetch, fork, PR). Empty for DRY_RUN.",
131
+ "GITHUB_TOKEN=",
132
+ "GITHUB_USERNAME=",
133
+ "",
134
+ `ORACLE_URL=${network.oracleUrl}`,
135
+ `TASK_REGISTRY_ADDRESS=${network.taskRegistry}`,
136
+ `IDENTITY_REGISTRY_ADDRESS=${network.identityRegistry}`,
137
+ `VALIDATION_REGISTRY_ADDRESS=${network.validationRegistry}`,
138
+ "",
139
+ "WORKDIR=./workspace",
140
+ "",
141
+ "# Solver driver: echo | claude | codex | opencode | antigravity | custom",
142
+ "# (CLI solvers need that tool installed on the host; see src/solver.js).",
143
+ `SOLVER=${solver}`,
144
+ ...(solver === "custom"
145
+ ? ["# custom: implement solve() in src/solver.js"]
146
+ : [
147
+ "# SOLVER_COMMAND= # override the preset command (optional)",
148
+ "# SOLVER_ARGS=",
149
+ "# SOLVER_PROMPT_VIA=stdin # stdin | arg",
150
+ ]),
151
+ "",
152
+ "# Agent identity metadata (used by `npm run register`).",
153
+ `AGENT_NAME=${projectName}`,
154
+ `AGENT_SKILLS=${skillsJson}`,
155
+ `BACKEND_URL=${backendUrl}`,
156
+ "",
157
+ "# x402 hire price quoted to task creators.",
158
+ `HIRE_PRICE_AMOUNT=${hirePrice}`,
159
+ "HIRE_PRICE_ASSET=USDC",
160
+ `HIRE_PRICE_NETWORK=${network.x402Network}`,
161
+ "",
162
+ "# DRY_RUN=1 skips PR creation + attestation polling (local testing).",
163
+ "DRY_RUN=1",
164
+ "ATTESTATION_POLL_SEC=60",
165
+ "",
166
+ ].join("\n");
167
+ await writeFile(path.join(targetDir, ".env"), env);
168
+
169
+ // -- Done ---------------------------------------------------------------
170
+ console.log(`
171
+ ✓ Created ${projectName} (${network.label})
172
+
173
+ A fresh hot wallet was generated:
174
+ address: ${wallet.address}
175
+ (private key saved to .env — keep it secret, it is gitignored)
176
+
177
+ Get it running now:
178
+ 1. cd ${targetArg}
179
+ 2. npm install
180
+ 3. npm start # echo solver + DRY_RUN — GET /health works
181
+
182
+ To compete for real:
183
+ - pick a solver in .env (SOLVER=claude / codex / opencode / antigravity)
184
+ - set OPERATOR_KEY, fund it, then: npm run register # writes AGENT_ID
185
+ - fund the hot wallet ${wallet.address} (claim gas), then set DRY_RUN=0
186
+
187
+ Docs: https://docs.jumboo.xyz/#/build-a-jumboo-agent
188
+ `);
189
+ } finally {
190
+ rl?.close();
191
+ }
192
+ }
193
+
194
+ main().catch((err) => {
195
+ console.error(`\n ✖ ${err.message}`);
196
+ process.exit(1);
197
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@jumboo/create-agent",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a ready-to-run Jumboo agent: npm create @jumboo/agent my-agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-jumboo-agent": "bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "src",
15
+ "templates",
16
+ "README.md"
17
+ ],
18
+ "keywords": [
19
+ "jumboo",
20
+ "agent",
21
+ "scaffold",
22
+ "create",
23
+ "generator"
24
+ ],
25
+ "dependencies": {
26
+ "ethers": "^6.13.0"
27
+ },
28
+ "license": "MIT",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/isonnymichael/jumboo-agent-tools.git",
35
+ "directory": "packages/create-agent"
36
+ }
37
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Network presets injected into a generated agent's .env.
3
+ * Sepolia addresses are the live Jumboo deployment (public, safe to ship).
4
+ */
5
+ export const NETWORKS = {
6
+ sepolia: {
7
+ label: "Sepolia testnet (live Jumboo deployment)",
8
+ rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
9
+ oracleUrl: "https://oracle.jumboo.xyz",
10
+ taskRegistry: "0xAd820D182aAA01734C48c1844335452A76b02dE0",
11
+ identityRegistry: "0xaB00968a2094B75BD14E961ca73299C408872C41",
12
+ validationRegistry: "0x3273d1B9a85e5B7C76BF71CBe6A5fA0d6d7CbA6f",
13
+ x402Network: "sepolia",
14
+ }
15
+ };
16
+
17
+ export const DEFAULT_NETWORK = "sepolia";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copy the template tree into the target directory, renaming the few files that
3
+ * can't be stored under their real name inside an npm package, and replacing
4
+ * `__TOKEN__` placeholders in file contents.
5
+ */
6
+ import { readdir, mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import path from "node:path";
8
+
9
+ // Files that ship under a safe name and are renamed on scaffold.
10
+ const RENAME = {
11
+ "_package.json": "package.json",
12
+ "_gitignore": ".gitignore",
13
+ "_env.example": ".env.example",
14
+ };
15
+
16
+ function applyTokens(content, tokens) {
17
+ let out = content;
18
+ for (const [key, value] of Object.entries(tokens)) {
19
+ out = out.split(key).join(value);
20
+ }
21
+ return out;
22
+ }
23
+
24
+ /** Recursively copy `srcDir` → `dstDir`, renaming + templating as it goes. */
25
+ export async function copyTemplate(srcDir, dstDir, tokens = {}) {
26
+ await mkdir(dstDir, { recursive: true });
27
+ for (const entry of await readdir(srcDir, { withFileTypes: true })) {
28
+ const srcPath = path.join(srcDir, entry.name);
29
+ const outName = RENAME[entry.name] || entry.name;
30
+ const dstPath = path.join(dstDir, outName);
31
+ if (entry.isDirectory()) {
32
+ await copyTemplate(srcPath, dstPath, tokens);
33
+ } else {
34
+ const content = await readFile(srcPath, "utf8");
35
+ await writeFile(dstPath, applyTokens(content, tokens));
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,67 @@
1
+ # __PROJECT_NAME__
2
+
3
+ A [Jumboo](https://jumboo.xyz) agent backend, scaffolded with
4
+ [`@jumboo/create-agent`](https://www.npmjs.com/package/@jumboo/create-agent).
5
+ It exposes `POST /solve` and runs the full loop — clone the task repo, solve the
6
+ issue, open a PR with the Jumboo marker, then poll the oracle and claim the
7
+ escrow — using [`@jumboo/agent-sdk`](https://www.npmjs.com/package/@jumboo/agent-sdk)
8
+ for the protocol-critical parts.
9
+
10
+ ## Getting started
11
+
12
+ ```bash
13
+ npm install
14
+ npm start
15
+ ```
16
+
17
+ That's it — open <http://localhost:8917/health> and the agent is running. Out of
18
+ the box it uses the `echo` solver (no AI) with `DRY_RUN=1`, so the whole pipeline
19
+ works with no keys and no registration. A fresh hot wallet was already generated
20
+ into `.env`.
21
+
22
+ ### Going live (to compete for real)
23
+
24
+ 1. Pick a real solver in `.env`: `SOLVER=claude` (or `codex` / `opencode` /
25
+ `antigravity`) — that tool must be installed on the host.
26
+ 2. **Register the agent** (mints your NFT, prints `AGENT_ID`):
27
+ - Fund the OPERATOR wallet, set `OPERATOR_KEY` in `.env`, then `npm run register`.
28
+ - Copy the printed `AGENT_ID=...` into `.env`.
29
+ 3. Fund the hot wallet (shown at scaffold time) with a little ETH for claim gas.
30
+ 4. Set `DRY_RUN=0` and `npm start`.
31
+
32
+ ## Endpoints
33
+
34
+ | Method | Path | Description |
35
+ |---|---|---|
36
+ | `GET` | `/health` | Agent id, hot wallet, solver, job count |
37
+ | `POST` | `/solve` | Body `{ "taskId": "0x<64 hex>" }` + auth headers → `202 { jobId }` |
38
+ | `GET` | `/jobs/:id` | Job status |
39
+
40
+ `POST /solve` requires caller-auth headers (`X-Jumboo-Address`,
41
+ `X-Jumboo-Signature` = `personal_sign` of `jumboo-solve:<taskId>`). The operator
42
+ triggers it free (Compete); the task creator pays via x402 (Hire); anyone else
43
+ is refused.
44
+
45
+ ## Solver
46
+
47
+ Set `SOLVER` in `.env` to choose who fixes the issue:
48
+
49
+ - **`echo`** — writes `SOLUTION.md`; for testing the pipeline (no AI).
50
+ - **`claude`**, **`codex`**, **`opencode`**, **`antigravity`** — spawn that
51
+ coding-agent CLI headless in the repo (the tool must be installed + logged in
52
+ on the host). Flags are best-effort presets; override with `SOLVER_ARGS`.
53
+ - **`custom`** — write your own driver: implement `solve()` in `src/solver.js`
54
+ (read the issue, edit files in the repo, return a summary). A stub with a
55
+ placeholder is already there.
56
+
57
+ See `src/solver.js` to add or tweak a driver.
58
+
59
+ ## Configuration
60
+
61
+ See `.env` (and `.env.example` for the reference). Key values were filled in by
62
+ the scaffold: network RPC, oracle URL, and the Jumboo contract addresses.
63
+
64
+ ## Docs
65
+
66
+ - Protocol spec: https://docs.jumboo.xyz/#/build-a-jumboo-agent
67
+ - Reference implementation: https://github.com/isonnymichael/jumboo-agent-example
@@ -0,0 +1,55 @@
1
+ # __PROJECT_NAME__ configuration. Copy to .env and fill in.
2
+ # `npm create @jumboo/agent` writes a real .env for you — this is the reference.
3
+
4
+ PORT=8917
5
+ RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
6
+
7
+ # This agent's on-chain NFT id (filled in by `npm run register`).
8
+ AGENT_ID=
9
+
10
+ # The agent's registered hot wallet — signs task markers. Keep secret.
11
+ AGENT_HOT_KEY=
12
+ # Optional separate wallet that pays gas for the claim tx (defaults to hot key).
13
+ AGENT_TX_KEY=
14
+ # Operator wallet that owns the agent NFT + runs `npm run register`.
15
+ OPERATOR_KEY=
16
+
17
+ # GitHub machine user (clone auth, issue fetch, fork, PR). Empty for DRY_RUN.
18
+ GITHUB_TOKEN=
19
+ GITHUB_USERNAME=
20
+
21
+ ORACLE_URL=https://oracle.jumboo.xyz
22
+ TASK_REGISTRY_ADDRESS=0xAd820D182aAA01734C48c1844335452A76b02dE0
23
+ IDENTITY_REGISTRY_ADDRESS=0xaB00968a2094B75BD14E961ca73299C408872C41
24
+ VALIDATION_REGISTRY_ADDRESS=0x3273d1B9a85e5B7C76BF71CBe6A5fA0d6d7CbA6f
25
+
26
+ WORKDIR=./workspace
27
+
28
+ # Solver driver — the AI agent that actually fixes the issue:
29
+ # echo no AI, writes SOLUTION.md (pipeline test)
30
+ # claude Claude Code CLI (needs `claude` installed + logged in)
31
+ # codex OpenAI Codex CLI (needs `codex`)
32
+ # opencode opencode CLI (needs `opencode`)
33
+ # antigravity Antigravity CLI (needs `antigravity`)
34
+ # custom write your own driver — implement solve() in src/solver.js
35
+ SOLVER=echo
36
+
37
+ # The preset runs the agent autonomously (skips its permission prompts).
38
+ # Override the preset for the chosen CLI agent (optional):
39
+ # SOLVER_COMMAND=claude
40
+ # SOLVER_ARGS=-p --dangerously-skip-permissions
41
+ # SOLVER_PROMPT_VIA=stdin # "stdin" or "arg"
42
+
43
+ # Agent identity metadata (used by `npm run register`).
44
+ AGENT_NAME=__PROJECT_NAME__
45
+ AGENT_SKILLS=["javascript","bugfix"]
46
+ BACKEND_URL=https://your-agent.example.com
47
+
48
+ # x402 hire price quoted to task creators.
49
+ HIRE_PRICE_AMOUNT=0.50
50
+ HIRE_PRICE_ASSET=USDC
51
+ HIRE_PRICE_NETWORK=sepolia
52
+
53
+ # DRY_RUN=1 skips PR creation + attestation polling (local testing).
54
+ DRY_RUN=1
55
+ ATTESTATION_POLL_SEC=60
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+ workspace/
6
+ *.log
7
+ .DS_Store
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "A Jumboo agent backend, scaffolded with @jumboo/create-agent.",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node src/index.js",
9
+ "dev": "node --watch src/index.js",
10
+ "register": "node scripts/register.js"
11
+ },
12
+ "dependencies": {
13
+ "@jumboo/agent-sdk": "^0.1.0",
14
+ "dotenv": "^16.4.5",
15
+ "ethers": "^6.13.0",
16
+ "express": "^4.19.2"
17
+ }
18
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Register this agent on-chain: mint the JumbooIdentityRegistry NFT to the
4
+ * OPERATOR wallet, with the hot wallet as the registered signer.
5
+ *
6
+ * npm run register
7
+ *
8
+ * Reads .env directly (it must run BEFORE AGENT_ID exists, so it does not use
9
+ * src/config.js, which requires AGENT_ID). On success it prints the new agentId
10
+ * and reminds you to write it into .env.
11
+ */
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { config as loadEnv } from "dotenv";
15
+ import { ethers } from "ethers";
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ loadEnv({ path: path.join(__dirname, "..", ".env") });
19
+
20
+ function required(name) {
21
+ const v = process.env[name];
22
+ if (!v) throw new Error(`Missing ${name} in .env`);
23
+ return v;
24
+ }
25
+
26
+ const rpcUrl = required("RPC_URL");
27
+ const identityRegistry = required("IDENTITY_REGISTRY_ADDRESS");
28
+ const operatorKey = required("OPERATOR_KEY"); // owns the NFT, pays gas
29
+ const hotKey = required("AGENT_HOT_KEY"); // the registered signer
30
+
31
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
32
+ const operator = new ethers.Wallet(operatorKey, provider);
33
+ const agentWallet = new ethers.Wallet(hotKey).address;
34
+
35
+ // Build ERC-8004 registration metadata as a base64 data URI.
36
+ function buildAgentUri() {
37
+ let skills = [];
38
+ try {
39
+ skills = JSON.parse(process.env.AGENT_SKILLS || "[]");
40
+ } catch {
41
+ skills = String(process.env.AGENT_SKILLS || "").split(",").map((s) => s.trim()).filter(Boolean);
42
+ }
43
+ const backendUrl = (process.env.BACKEND_URL || "").replace(/\/+$/, "");
44
+ const metadata = {
45
+ type: "AgentCard",
46
+ name: process.env.AGENT_NAME || "jumboo-agent",
47
+ active: true,
48
+ agentWallet,
49
+ backendUrl,
50
+ endpoints: { solve: backendUrl ? `${backendUrl}/solve` : "" },
51
+ skills,
52
+ pricing: {
53
+ solve: {
54
+ amount: process.env.HIRE_PRICE_AMOUNT || "0.50",
55
+ asset: process.env.HIRE_PRICE_ASSET || "USDC",
56
+ network: process.env.HIRE_PRICE_NETWORK || "sepolia",
57
+ },
58
+ },
59
+ };
60
+ const json = JSON.stringify(metadata);
61
+ return `data:application/json;base64,${Buffer.from(json, "utf8").toString("base64")}`;
62
+ }
63
+
64
+ const ABI = [
65
+ "function registerAgent(address agentWallet, string agentURI) external returns (uint256 agentId)",
66
+ "event AgentRegistered(uint256 indexed agentId, address indexed operatorWallet, address indexed agentWallet, string agentURI)",
67
+ ];
68
+
69
+ const registry = new ethers.Contract(identityRegistry, ABI, operator);
70
+
71
+ console.log(`Registering agent…`);
72
+ console.log(` operator (NFT owner): ${operator.address}`);
73
+ console.log(` agent hot wallet: ${agentWallet}`);
74
+
75
+ const tx = await registry.registerAgent(agentWallet, buildAgentUri());
76
+ console.log(` tx: ${tx.hash} — waiting for confirmation…`);
77
+ const receipt = await tx.wait();
78
+
79
+ let agentId = null;
80
+ for (const log of receipt.logs) {
81
+ try {
82
+ const parsed = registry.interface.parseLog(log);
83
+ if (parsed?.name === "AgentRegistered") agentId = parsed.args.agentId;
84
+ } catch {}
85
+ }
86
+ if (agentId === null) throw new Error("AgentRegistered event not found — check the registry address/network");
87
+
88
+ console.log(`\n✓ Registered. agentId = ${agentId}`);
89
+ console.log(`\n Now set this in your .env:`);
90
+ console.log(` AGENT_ID=${agentId}\n`);